fix multi-model chart time axis

This commit is contained in:
2569718930@qq.com
2026-06-11 20:31:07 +08:00
parent 43cc917861
commit 63038cfaea
2 changed files with 60 additions and 4 deletions
@@ -84,6 +84,36 @@ export function runTests() {
assert(correctedDebSeries, "corrected hourly DEB path should still render as DEB Forecast");
assert(correctedDebValues.includes(28.0), `chart should use backend deb.hourly_path before rebuilding a shifted curve; got ${correctedDebValues.join(",")}`);
const independentModelTimelineChart = buildFullDayChartData(
{
city: "madrid",
local_date: "2026-06-11",
local_time: "20:00",
temp_symbol: "°C",
tz_offset_seconds: 2 * 3600,
} as any,
{
forecastTodayHigh: 31,
debPrediction: 31,
localDate: "2026-06-11",
localTime: "20:00",
times: ["08:00", "09:00", "10:00"],
temps: [20, 25, 22],
modelTimes: ["15:00", "16:00", "17:00"],
modelCurves: {
ECMWF: [24, 32, 25],
},
} as any,
true,
);
const independentModelPeak = independentModelTimelineChart.data.find(
(point) => point.model_curve_ECMWF === 32,
);
assert(
independentModelPeak?.label === "16:00:00",
`multi-model curves must use models_hourly.times instead of hourly.times; got ${independentModelPeak?.label}`,
);
const correctedDetailChart = getTemperatureChartData(
{
name: "shanghai",
@@ -1199,6 +1199,7 @@ type HourlyForecast = {
localTime?: string | null;
times: string[];
temps: Array<number | null>;
modelTimes?: string[];
modelCurves?: Record<string, Array<number | null>>;
runwayPlateHistory?: Record<string, Array<Record<string, unknown>>>;
runwayBandHistory?: Array<{ time: string; high_temp: number; low_temp: number; avg_temp: number }>;
@@ -1253,6 +1254,7 @@ function seedHourlyForecastFromRow(row: ScanOpportunityRow | null): HourlyForeca
localTime: row.local_time || null,
times: [],
temps: [],
modelTimes: undefined,
modelCurves: undefined,
runwayPlateHistory: seededRunwayPlateHistory,
runwayBandHistory: undefined,
@@ -1368,6 +1370,7 @@ function parseHourlyForecastFromCityDetail(json: CityDetail | null): HourlyForec
localTime: json.local_time || null,
times: hourlySource.times || [],
temps: hourlySource.temps || [],
modelTimes: (json.models_hourly ?? (json as any)?.timeseries?.models_hourly)?.times || undefined,
modelCurves: (json.models_hourly ?? (json as any)?.timeseries?.models_hourly)?.curves || undefined,
runwayPlateHistory: (json as any)?.runway_plate_history || (json.amos as any)?.runway_plate_history || undefined,
runwayBandHistory: (json as any)?.runway_band_history || undefined,
@@ -2161,6 +2164,14 @@ function addHourlyTimesToTimeline(
});
}
function resolveModelCurveTimes(
hourly: HourlyForecast,
modelTemps: Array<number | null>,
) {
if (hourly?.modelTimes?.length) return hourly.modelTimes;
return hourly?.times?.length === modelTemps.length ? hourly.times : [];
}
function probabilityBucketValue(bucket: ProbabilityBucket) {
return validNumber(bucket.value ?? (bucket as any).temp ?? (bucket as any).temperature);
}
@@ -2354,9 +2365,16 @@ function buildFullDayChartData(
if (debTimes.length && debTemps.length) {
addHourlyTimesToTimeline(timelineSet, debTimes, debTemps, tzOffset, localDateStr, localDayBounds);
}
if (hourly?.times?.length && hourly?.modelCurves) {
if (hourly?.modelCurves) {
Object.values(hourly.modelCurves).forEach((modelTemps) => {
addHourlyTimesToTimeline(timelineSet, hourly.times, modelTemps, tzOffset, localDateStr, localDayBounds);
addHourlyTimesToTimeline(
timelineSet,
resolveModelCurveTimes(hourly, modelTemps),
modelTemps,
tzOffset,
localDateStr,
localDayBounds,
);
});
}
@@ -2460,12 +2478,20 @@ function buildFullDayChartData(
}
// Per-model curves
if (hourly?.times?.length && hourly.modelCurves) {
if (hourly?.modelCurves) {
const modelColors = ["#2563eb", "#7c3aed", "#059669", "#d97706", "#dc2626", "#0891b2"];
Object.keys(hourly.modelCurves).forEach((model, idx) => {
const modelTemps = hourly.modelCurves![model];
if (!modelTemps?.length) return;
const vals = valuesForHourlyTimes(n, indexByTs, hourly.times, modelTemps, tzOffset, localDateStr, localDayBounds);
const vals = valuesForHourlyTimes(
n,
indexByTs,
resolveModelCurveTimes(hourly, modelTemps),
modelTemps,
tzOffset,
localDateStr,
localDayBounds,
);
if (vals.some((v) => v !== null)) {
series.push({
key: `model_curve_${model}`,