From 618bac8b57d3f1804c71cead86ebcf2878b0a15f Mon Sep 17 00:00:00 2001 From: "2569718930@qq.com" <2569718930@qq.com> Date: Fri, 29 May 2026 02:10:07 +0800 Subject: [PATCH] feat: add live temperature threshold chart logic and components for runway monitoring --- .../LiveTemperatureThresholdChart.tsx | 12 ++++- ...temperatureDefaultVisibilityPolicy.test.ts | 48 +++++++++++++++++++ .../scan-terminal/temperature-chart-logic.ts | 8 ++-- 3 files changed, 62 insertions(+), 6 deletions(-) diff --git a/frontend/components/dashboard/scan-terminal/LiveTemperatureThresholdChart.tsx b/frontend/components/dashboard/scan-terminal/LiveTemperatureThresholdChart.tsx index fd799279..404c1a44 100644 --- a/frontend/components/dashboard/scan-terminal/LiveTemperatureThresholdChart.tsx +++ b/frontend/components/dashboard/scan-terminal/LiveTemperatureThresholdChart.tsx @@ -444,7 +444,17 @@ export function LiveTemperatureThresholdChart({ }, [row, chartHourly, tzOffset, chartLocalDate]); const runwayPlates = useMemo(() => buildRunwayPlates(chartHourly?.amos, row, settlementObs), [chartHourly?.amos, row, settlementObs]); - const hasRunwayData = runwayPlates.length > 0; + const hasRunwaySeries = useMemo( + () => + series.some( + (item) => + item.key.startsWith("runway_") && + item.key !== "runway_max" && + item.values.some((value) => validNumber(value) !== null), + ), + [series], + ); + const hasRunwayData = runwayPlates.length > 0 || hasRunwaySeries; const settlementPlate = useMemo(() => runwayPlates.find((p) => p.isSettlement), [runwayPlates]); const chartSeries = useMemo(() => { diff --git a/frontend/components/dashboard/scan-terminal/__tests__/temperatureDefaultVisibilityPolicy.test.ts b/frontend/components/dashboard/scan-terminal/__tests__/temperatureDefaultVisibilityPolicy.test.ts index 865a824b..a3ed4c9f 100644 --- a/frontend/components/dashboard/scan-terminal/__tests__/temperatureDefaultVisibilityPolicy.test.ts +++ b/frontend/components/dashboard/scan-terminal/__tests__/temperatureDefaultVisibilityPolicy.test.ts @@ -10,6 +10,7 @@ import { __mergePatchIntoHourlyForTest, __selectDisplayRunwayTempForTest, } from "@/components/dashboard/scan-terminal/LiveTemperatureThresholdChart"; +import { __buildTemperatureTooltipRowsForTest } from "@/components/dashboard/scan-terminal/TemperatureTooltipContent"; function assert(condition: unknown, message: string): asserts condition { if (!condition) throw new Error(message); @@ -369,6 +370,23 @@ export function runTests() { !englishSettlement?.label.includes("结算跑道"), `${city} English settlement runway label should not leak Chinese`, ); + const englishRunwayPoint = englishChart.data.find( + (point: any) => point[runwayKey(settlementRwy)] !== null && point[runwayKey(settlementRwy)] !== undefined, + ); + const tooltipRows = __buildTemperatureTooltipRowsForTest( + englishRunwayPoint as any, + englishChart.data, + englishChart.series, + ); + const tooltipRunway = tooltipRows.find((item) => item.key === runwayKey(settlementRwy)); + assert( + tooltipRunway?.label.includes("Settlement Runway"), + `${city} English tooltip runway label should be translated`, + ); + assert( + !tooltipRunway?.label.includes("结算跑道"), + `${city} English tooltip runway label should not leak Chinese`, + ); }); const shenzhen = __buildTemperatureChartDataForTest( @@ -785,6 +803,36 @@ export function runTests() { "live aggregate temp should not override runway-history current temp when runway data is rendered", ); + const wuhanRunwayMetrics = __getObservationDisplayMetricsForTest( + { + city: "wuhan", + local_date: "2026-05-27", + local_time: "13:54", + tz_offset_seconds: 8 * 60 * 60, + current_temp: 25.0, + temp_symbol: "°C", + } as any, + { + localTime: "13:54", + times: ["00:00", "12:00", "18:00", "23:00"], + temps: [22.0, 30.0, 29.0, 25.0], + runwayPlateHistory: { + "04/22": [ + { time: "13:52", temp: 28.6 }, + { time: "13:54", temp: 28.8 }, + ], + }, + } as any, + ); + assert( + wuhanRunwayMetrics.currentRunwayTemp === 28.8, + "runway header metrics should use the latest settlement runway history point", + ); + assert( + __selectDisplayRunwayTempForTest(25.0, wuhanRunwayMetrics.currentRunwayTemp, false) === 28.8, + "runway header should prefer runway-history current temp even when the latest detail payload lacks runway_obs snapshot", + ); + const newYorkMetrics = __getObservationDisplayMetricsForTest( { city: "new york", diff --git a/frontend/components/dashboard/scan-terminal/temperature-chart-logic.ts b/frontend/components/dashboard/scan-terminal/temperature-chart-logic.ts index 13e1d432..ca0c3262 100644 --- a/frontend/components/dashboard/scan-terminal/temperature-chart-logic.ts +++ b/frontend/components/dashboard/scan-terminal/temperature-chart-logic.ts @@ -713,12 +713,10 @@ function getObservationDisplayMetrics( function selectDisplayRunwayTemp( liveTemp: number | null, currentRunwayTemp: number | null, - hasRunwayData: boolean, + _hasRunwayData: boolean, ) { - if (hasRunwayData && currentRunwayTemp !== null) { - return currentRunwayTemp; - } - return liveTemp ?? currentRunwayTemp; + if (currentRunwayTemp !== null) return currentRunwayTemp; + return liveTemp; } function isSettlementRunway(row: ScanOpportunityRow | null, rwy: string) {