移除未使用的 Groq 和 Meteoblue 服务代码及配置
This commit is contained in:
@@ -2,7 +2,6 @@ import { Locale } from "@/lib/i18n";
|
||||
import {
|
||||
AiAnalysisStructured,
|
||||
CityDetail,
|
||||
HistoryPoint,
|
||||
NearbyStation,
|
||||
} from "@/lib/dashboard-types";
|
||||
import {
|
||||
@@ -1715,142 +1714,6 @@ export function getShortTermNowcastLines(
|
||||
return rows;
|
||||
}
|
||||
|
||||
export function getHistorySummary(
|
||||
history: HistoryPoint[],
|
||||
cityLocalDate?: string | null,
|
||||
) {
|
||||
const toFinite = (value: unknown): number | null => {
|
||||
const numeric = Number(value);
|
||||
return Number.isFinite(numeric) ? numeric : null;
|
||||
};
|
||||
const isExcludedModel = (name: string) =>
|
||||
String(name || "").toLowerCase().includes("meteoblue");
|
||||
|
||||
const cutoff = new Date();
|
||||
cutoff.setHours(0, 0, 0, 0);
|
||||
cutoff.setDate(cutoff.getDate() - 14);
|
||||
|
||||
const recentData = history.filter((row) => {
|
||||
if (!row?.date) return false;
|
||||
const rowDate = new Date(`${row.date}T00:00:00`);
|
||||
return !Number.isNaN(rowDate.getTime()) && rowDate >= cutoff;
|
||||
});
|
||||
|
||||
const settledData = recentData.filter((row) => {
|
||||
if (!row?.date) return false;
|
||||
return cityLocalDate
|
||||
? row.date < cityLocalDate
|
||||
: row.date < new Date().toISOString().slice(0, 10);
|
||||
});
|
||||
const comparableSettledData = settledData.filter((row) => {
|
||||
const actual = toFinite(row.actual);
|
||||
const deb = toFinite(row.deb);
|
||||
return actual != null && deb != null;
|
||||
});
|
||||
|
||||
let hits = 0;
|
||||
const debErrors: number[] = [];
|
||||
const modelErrors: Record<string, number[]> = {};
|
||||
|
||||
comparableSettledData.forEach((row) => {
|
||||
const actual = toFinite(row.actual);
|
||||
const deb = toFinite(row.deb);
|
||||
if (actual == null || deb == null) return;
|
||||
debErrors.push(Math.abs(actual - deb));
|
||||
if (wuRound(actual) === wuRound(deb)) {
|
||||
hits += 1;
|
||||
}
|
||||
|
||||
const forecasts = row.forecasts || {};
|
||||
Object.entries(forecasts).forEach(([modelName, modelValue]) => {
|
||||
if (isExcludedModel(modelName)) return;
|
||||
const mv = toFinite(modelValue);
|
||||
if (actual == null || mv == null) return;
|
||||
if (!modelErrors[modelName]) {
|
||||
modelErrors[modelName] = [];
|
||||
}
|
||||
modelErrors[modelName].push(Math.abs(actual - mv));
|
||||
});
|
||||
});
|
||||
|
||||
const modelMaeList = Object.entries(modelErrors)
|
||||
.map(([name, errors]) => ({
|
||||
mae:
|
||||
errors.length > 0
|
||||
? errors.reduce((sum, value) => sum + value, 0) / errors.length
|
||||
: Number.POSITIVE_INFINITY,
|
||||
model: name,
|
||||
sampleCount: errors.length,
|
||||
}))
|
||||
.filter((row) => Number.isFinite(row.mae) && row.sampleCount > 0)
|
||||
.sort((a, b) => a.mae - b.mae);
|
||||
|
||||
const primaryModelMaeList = modelMaeList.filter((row) => row.sampleCount >= 2);
|
||||
const bestModel = (primaryModelMaeList[0] || modelMaeList[0]) ?? null;
|
||||
const bestModelName = bestModel?.model || null;
|
||||
const bestModelMae = bestModel ? Number(bestModel.mae.toFixed(1)) : null;
|
||||
const bestModelSeries = recentData.map((row) =>
|
||||
bestModelName ? toFinite(row.forecasts?.[bestModelName]) : null,
|
||||
);
|
||||
|
||||
let debWinDaysVsBest = 0;
|
||||
let debVsBestComparableDays = 0;
|
||||
if (bestModelName) {
|
||||
comparableSettledData.forEach((row) => {
|
||||
const actual = toFinite(row.actual);
|
||||
const deb = toFinite(row.deb);
|
||||
const bestModelVal = toFinite(row.forecasts?.[bestModelName]);
|
||||
if (actual == null || deb == null || bestModelVal == null) return;
|
||||
debVsBestComparableDays += 1;
|
||||
if (Math.abs(deb - actual) <= Math.abs(bestModelVal - actual)) {
|
||||
debWinDaysVsBest += 1;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const mgmSettledCount = settledData.reduce((count, row) => {
|
||||
return toFinite(row.mgm) != null ? count + 1 : count;
|
||||
}, 0);
|
||||
const mgmSeriesComplete =
|
||||
settledData.length >= 2 && mgmSettledCount === settledData.length;
|
||||
const mgmSeries = mgmSeriesComplete
|
||||
? recentData.map((row) => row.mgm ?? null)
|
||||
: recentData.map(() => null);
|
||||
|
||||
return {
|
||||
dates: recentData.map((row) => row.date),
|
||||
debMae: debErrors.length
|
||||
? Number(
|
||||
(
|
||||
debErrors.reduce((sum, value) => sum + value, 0) / debErrors.length
|
||||
).toFixed(1),
|
||||
)
|
||||
: null,
|
||||
debs: recentData.map((row) => row.deb),
|
||||
bestModelName,
|
||||
bestModelMae,
|
||||
bestModelSeries,
|
||||
modelMaeRanks: modelMaeList.map((row) => ({
|
||||
model: row.model,
|
||||
mae: Number(row.mae.toFixed(1)),
|
||||
sampleCount: row.sampleCount,
|
||||
})),
|
||||
debWinDaysVsBest,
|
||||
debVsBestComparableDays,
|
||||
debWinRateVsBest:
|
||||
debVsBestComparableDays > 0
|
||||
? Number(((debWinDaysVsBest / debVsBestComparableDays) * 100).toFixed(0))
|
||||
: null,
|
||||
hitRate: debErrors.length
|
||||
? Number(((hits / debErrors.length) * 100).toFixed(0))
|
||||
: null,
|
||||
mgmSeriesComplete,
|
||||
mgms: mgmSeries,
|
||||
recentData,
|
||||
settledCount: comparableSettledData.length,
|
||||
actuals: recentData.map((row) => row.actual),
|
||||
};
|
||||
}
|
||||
|
||||
function toFiniteNumber(value: unknown): number | null {
|
||||
const numeric = Number(value);
|
||||
|
||||
Reference in New Issue
Block a user