feat: add default visibility policies and chart data logic for temperature scan terminal
This commit is contained in:
+50
@@ -161,6 +161,56 @@ export function runTests() {
|
|||||||
"DEB peak auto window should not expand beyond 12 hours",
|
"DEB peak auto window should not expand beyond 12 hours",
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const postPeakWindowChart = __buildTemperatureChartDataForTest(
|
||||||
|
{
|
||||||
|
city: "beijing",
|
||||||
|
local_date: "2026-05-26",
|
||||||
|
local_time: "21:10",
|
||||||
|
tz_offset_seconds: 8 * 60 * 60,
|
||||||
|
deb_prediction: 35,
|
||||||
|
runway_plate_history: {
|
||||||
|
"19/01": [
|
||||||
|
{ time: "2026-05-26T10:00:00+08:00", temp: 29.8 },
|
||||||
|
{ time: "2026-05-26T15:00:00+08:00", temp: 34.9 },
|
||||||
|
{ time: "2026-05-26T21:00:00+08:00", temp: 28.6 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
} as any,
|
||||||
|
{
|
||||||
|
localTime: "21:10",
|
||||||
|
times: [
|
||||||
|
"00:00", "01:00", "02:00", "03:00", "04:00", "05:00",
|
||||||
|
"06:00", "07:00", "08:00", "09:00", "10:00", "11:00",
|
||||||
|
"12:00", "13:00", "14:00", "15:00", "16:00", "17:00",
|
||||||
|
"18:00", "19:00", "20:00", "21:00", "22:00", "23:00",
|
||||||
|
],
|
||||||
|
temps: [
|
||||||
|
20, 20.5, 21, 21.5, 22, 23,
|
||||||
|
24, 25, 26, 27, 29, 31,
|
||||||
|
32, 33, 34.2, 35, 34.4, 33.3,
|
||||||
|
31.8, 30.2, 28.5, 27, 25.5, 24,
|
||||||
|
],
|
||||||
|
debPrediction: 35,
|
||||||
|
} as any,
|
||||||
|
"1D",
|
||||||
|
);
|
||||||
|
const postPeakWindowRange = __getDebPeakWindowRangeForTest(
|
||||||
|
postPeakWindowChart.data,
|
||||||
|
postPeakWindowChart.series as any,
|
||||||
|
);
|
||||||
|
assert(postPeakWindowRange, "post-peak default chart view should still derive from the DEB peak window");
|
||||||
|
const postPeakWindowRows = postPeakWindowChart.data.slice(postPeakWindowRange![0], postPeakWindowRange![1] + 1);
|
||||||
|
const postPeakWindowStart = postPeakWindowRows[0].ts;
|
||||||
|
const postPeakWindowEnd = postPeakWindowRows[postPeakWindowRows.length - 1].ts;
|
||||||
|
assert(
|
||||||
|
postPeakWindowEnd >= Date.UTC(2026, 4, 26, 21, 0, 0),
|
||||||
|
"After the peak window, default high-temperature view should extend to the latest live observation",
|
||||||
|
);
|
||||||
|
assert(
|
||||||
|
postPeakWindowEnd - postPeakWindowStart <= 12 * 60 * 60 * 1000,
|
||||||
|
"Post-peak high-temperature view should keep a bounded 12-hour window",
|
||||||
|
);
|
||||||
|
|
||||||
assert(
|
assert(
|
||||||
__isTemperatureSeriesVisibleByDefaultForTest("paris", "model_curve_AROME HD"),
|
__isTemperatureSeriesVisibleByDefaultForTest("paris", "model_curve_AROME HD"),
|
||||||
"Paris AROME HD should be the only default-visible model curve exception",
|
"Paris AROME HD should be the only default-visible model curve exception",
|
||||||
|
|||||||
@@ -1445,6 +1445,33 @@ function buildChartDomain(
|
|||||||
return [Number((min - pad).toFixed(1)), Number((max + pad).toFixed(1))];
|
return [Number((min - pad).toFixed(1)), Number((max + pad).toFixed(1))];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isLiveObservationSeries(series: EvidenceSeries) {
|
||||||
|
if (series.key === "hourly_forecast") return false;
|
||||||
|
if (series.key.startsWith("model_curve_")) return false;
|
||||||
|
if (series.key.startsWith("model_summary_")) return false;
|
||||||
|
if (["deb_prediction", "max_temp", "min_temp"].includes(series.key)) return false;
|
||||||
|
|
||||||
|
const source = String(series.source || "").toLowerCase();
|
||||||
|
if (source.includes("forecast") || source.includes("multi-model") || source === "deb") return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function latestLiveObservationTimestamp(
|
||||||
|
data: Array<Record<string, any>>,
|
||||||
|
series: EvidenceSeries[],
|
||||||
|
) {
|
||||||
|
let latest: number | null = null;
|
||||||
|
series.filter(isLiveObservationSeries).forEach((item) => {
|
||||||
|
item.values.forEach((value, index) => {
|
||||||
|
if (validNumber(value) === null) return;
|
||||||
|
const ts = typeof data[index]?.ts === "number" ? data[index].ts : null;
|
||||||
|
if (ts === null) return;
|
||||||
|
latest = latest === null ? ts : Math.max(latest, ts);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return latest;
|
||||||
|
}
|
||||||
|
|
||||||
function getDebPeakWindowRange(
|
function getDebPeakWindowRange(
|
||||||
data: Array<Record<string, any>>,
|
data: Array<Record<string, any>>,
|
||||||
series: EvidenceSeries[],
|
series: EvidenceSeries[],
|
||||||
@@ -1487,6 +1514,7 @@ function getDebPeakWindowRange(
|
|||||||
let startTs = debPoints[hotStartPoint].ts - 1.5 * hour;
|
let startTs = debPoints[hotStartPoint].ts - 1.5 * hour;
|
||||||
let endTs = debPoints[hotEndPoint].ts + 2 * hour;
|
let endTs = debPoints[hotEndPoint].ts + 2 * hour;
|
||||||
const centerTs = peak.ts;
|
const centerTs = peak.ts;
|
||||||
|
const latestObsTs = latestLiveObservationTimestamp(data, series);
|
||||||
|
|
||||||
if (endTs - startTs < targetSpan) {
|
if (endTs - startTs < targetSpan) {
|
||||||
startTs = centerTs - targetSpan / 2;
|
startTs = centerTs - targetSpan / 2;
|
||||||
@@ -1496,6 +1524,12 @@ function getDebPeakWindowRange(
|
|||||||
startTs = centerTs - maxSpan / 2;
|
startTs = centerTs - maxSpan / 2;
|
||||||
endTs = centerTs + maxSpan / 2;
|
endTs = centerTs + maxSpan / 2;
|
||||||
}
|
}
|
||||||
|
if (latestObsTs !== null && latestObsTs > endTs && latestObsTs > debPoints[hotEndPoint].ts) {
|
||||||
|
endTs = Math.min(lastTs, latestObsTs);
|
||||||
|
if (endTs - startTs > maxSpan) {
|
||||||
|
startTs = Math.max(firstTs, endTs - maxSpan);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (startTs < firstTs) {
|
if (startTs < firstTs) {
|
||||||
endTs = Math.min(lastTs, endTs + firstTs - startTs);
|
endTs = Math.min(lastTs, endTs + firstTs - startTs);
|
||||||
|
|||||||
Reference in New Issue
Block a user