fix multi-model chart time axis
This commit is contained in:
@@ -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}`,
|
||||
|
||||
Reference in New Issue
Block a user