diff --git a/frontend/components/dashboard/scan-terminal/LiveTemperatureThresholdChart.tsx b/frontend/components/dashboard/scan-terminal/LiveTemperatureThresholdChart.tsx
index c7a5fc25..27a2e8e5 100644
--- a/frontend/components/dashboard/scan-terminal/LiveTemperatureThresholdChart.tsx
+++ b/frontend/components/dashboard/scan-terminal/LiveTemperatureThresholdChart.tsx
@@ -31,6 +31,7 @@ import {
normObs,
prefersHighFrequencyRunwayResolution,
readSessionCache,
+ selectCompactSecondaryTemp,
selectDisplayRunwayTemp,
seedHourlyForecastFromRow,
shouldPollLiveChart,
@@ -635,6 +636,7 @@ export function LiveTemperatureThresholdChart({
}, [chartSeries, userToggledKeys, city, showRunwayDetails]);
const {
+ isHKO,
isShenzhen,
metarHeaderLabel,
metarHighLabel,
@@ -642,11 +644,17 @@ export function LiveTemperatureThresholdChart({
runwayHighLabel,
} = useMemo(() => getLiveObservationLabels(row, chartHourly), [row, chartHourly]);
- const { currentRunwayTemp, observedHighMetar, observedHighRunway } = useMemo(
+ const { currentMetarTemp, currentRunwayTemp, observedHighMetar, observedHighRunway } = useMemo(
() => getObservationDisplayMetrics(row, chartHourly, settlementPlate),
[row, chartHourly, settlementPlate],
);
const displayRunwayTemp = selectDisplayRunwayTemp(liveTemp, currentRunwayTemp, hasRunwayData);
+ const displayMetarTemp = selectCompactSecondaryTemp({
+ isHKO,
+ isShenzhen,
+ displayMetarTemp: currentMetarTemp,
+ observedHighMetar,
+ });
const wundergroundDailyHigh = getWundergroundDailyHigh(chartHourly);
const localDateStr = chartLocalDate || new Date().toISOString().slice(0, 10);
@@ -805,6 +813,7 @@ export function LiveTemperatureThresholdChart({
visible_series: activeSeries.map((item) => item.key),
live_temp: liveTemp,
current_runway_temp: currentRunwayTemp,
+ current_metar_temp: currentMetarTemp,
observed_high_metar: observedHighMetar,
observed_high_runway: observedHighRunway,
});
@@ -814,6 +823,7 @@ export function LiveTemperatureThresholdChart({
chartSeries,
city,
compact,
+ currentMetarTemp,
currentRunwayTemp,
detailError,
hasRunwayData,
@@ -960,6 +970,7 @@ export function LiveTemperatureThresholdChart({
metarHighLabel={metarHighLabel}
isShenzhen={isShenzhen}
displayRunwayTemp={displayRunwayTemp}
+ displayMetarTemp={displayMetarTemp}
observedHighMetar={observedHighMetar}
observedHighRunway={observedHighRunway}
wundergroundDailyHigh={wundergroundDailyHigh}
@@ -1034,4 +1045,5 @@ export const __getInitialDetailLoadDelayMsForTest = getInitialDetailLoadDelayMs;
export const __shouldFetchCityDetailForChartForTest = shouldFetchCityDetailForChart;
export const __shouldPollLiveChartForTest = shouldPollLiveChart;
export const __mergePatchIntoHourlyForTest = mergePatchIntoHourly;
+export const __selectCompactSecondaryTempForTest = selectCompactSecondaryTemp;
export const __selectDisplayRunwayTempForTest = selectDisplayRunwayTemp;
diff --git a/frontend/components/dashboard/scan-terminal/TemperatureStatsBars.tsx b/frontend/components/dashboard/scan-terminal/TemperatureStatsBars.tsx
index 37f3e4f7..4698f3ef 100644
--- a/frontend/components/dashboard/scan-terminal/TemperatureStatsBars.tsx
+++ b/frontend/components/dashboard/scan-terminal/TemperatureStatsBars.tsx
@@ -76,6 +76,7 @@ export function TemperatureStatsBars({
metarHighLabel,
isShenzhen,
displayRunwayTemp,
+ displayMetarTemp,
observedHighMetar,
observedHighRunway,
wundergroundDailyHigh,
@@ -97,6 +98,7 @@ export function TemperatureStatsBars({
metarHighLabel: string;
isShenzhen: boolean;
displayRunwayTemp: number | null;
+ displayMetarTemp: number | null;
observedHighMetar: number | null;
observedHighRunway: number | null;
wundergroundDailyHigh: number | null;
@@ -129,7 +131,7 @@ export function TemperatureStatsBars({
|
{labels.compactSecondary}:{" "}
- {temp(observedHighMetar, tempSymbol)}
+ {temp(displayMetarTemp, tempSymbol)}
) : (
diff --git a/frontend/components/dashboard/scan-terminal/__tests__/temperatureDefaultVisibilityPolicy.test.ts b/frontend/components/dashboard/scan-terminal/__tests__/temperatureDefaultVisibilityPolicy.test.ts
index 0cfbdf88..63271bd2 100644
--- a/frontend/components/dashboard/scan-terminal/__tests__/temperatureDefaultVisibilityPolicy.test.ts
+++ b/frontend/components/dashboard/scan-terminal/__tests__/temperatureDefaultVisibilityPolicy.test.ts
@@ -9,6 +9,7 @@ import {
__getVisibleTemperatureSeriesForTest,
__isTemperatureSeriesVisibleByDefaultForTest,
__mergePatchIntoHourlyForTest,
+ __selectCompactSecondaryTempForTest,
__selectDisplayRunwayTempForTest,
} from "@/components/dashboard/scan-terminal/LiveTemperatureThresholdChart";
import { __buildTemperatureTooltipRowsForTest } from "@/components/dashboard/scan-terminal/TemperatureTooltipContent";
@@ -976,6 +977,68 @@ export function runTests() {
"runway header should prefer runway-history current temp even when the latest detail payload lacks runway_obs snapshot",
);
+ const hongKongMetrics = __getObservationDisplayMetricsForTest(
+ {
+ city: "hong kong",
+ local_date: "2026-06-06",
+ local_time: "16:48",
+ tz_offset_seconds: 8 * 60 * 60,
+ current_temp: 30.4,
+ current_max_so_far: 30.4,
+ temp_symbol: "°C",
+ } as any,
+ {
+ localTime: "16:48",
+ times: ["00:00", "12:00", "18:00", "23:00"],
+ temps: [28.0, 31.0, 29.0, 28.0],
+ settlementTodayObs: [
+ { time: "14:00", temp: 30.4 },
+ { time: "16:40", temp: 28.1 },
+ ],
+ airportPrimary: {
+ temp: 28.7,
+ obs_time: "2026-06-06T16:48:00+08:00",
+ source_code: "cowin_obs",
+ source_label: "CoWIN 6087",
+ },
+ airportPrimaryTodayObs: [
+ { time: "16:47", temp: 28.6 },
+ { time: "16:48", temp: 28.7 },
+ ],
+ } as any,
+ null,
+ );
+ assert(
+ hongKongMetrics.currentRunwayTemp === 28.7,
+ "Hong Kong primary compact stat should stay on the latest CoWIN reference-station point",
+ );
+ assert(
+ (hongKongMetrics as any).currentMetarTemp === 28.1,
+ "Hong Kong secondary compact stat should have the latest HKO point separate from the HKO daily high",
+ );
+ assert(
+ hongKongMetrics.observedHighMetar === 30.4,
+ "Hong Kong HKO daily high should still be available for expanded daily-high summaries",
+ );
+ assert(
+ __selectCompactSecondaryTempForTest({
+ isHKO: true,
+ isShenzhen: false,
+ displayMetarTemp: (hongKongMetrics as any).currentMetarTemp,
+ observedHighMetar: hongKongMetrics.observedHighMetar,
+ }) === 28.1,
+ "Hong Kong compact HKO stat should render the latest HKO point, not the HKO daily high",
+ );
+ assert(
+ __selectCompactSecondaryTempForTest({
+ isHKO: false,
+ isShenzhen: false,
+ displayMetarTemp: 72.0,
+ observedHighMetar: 73.9,
+ }) === 73.9,
+ "non-HKO compact secondary stat should keep the existing daily-high behavior",
+ );
+
const wuhanRunwayChart = __buildTemperatureChartDataForTest(
{
city: "wuhan",
diff --git a/frontend/components/dashboard/scan-terminal/temperature-chart-logic.ts b/frontend/components/dashboard/scan-terminal/temperature-chart-logic.ts
index a47dabb9..f8312844 100644
--- a/frontend/components/dashboard/scan-terminal/temperature-chart-logic.ts
+++ b/frontend/components/dashboard/scan-terminal/temperature-chart-logic.ts
@@ -845,9 +845,14 @@ function getObservationDisplayMetrics(
null;
}
+ const currentMetarTemp =
+ latestSettlement ??
+ latestMetar ??
+ airportCurrentTemp ??
+ null;
const observedHighMetar = airportHigh ?? highSettlement ?? highMetar ?? rowMetarHigh ?? null;
- return { currentRunwayTemp, observedHighMetar, observedHighRunway };
+ return { currentMetarTemp, currentRunwayTemp, observedHighMetar, observedHighRunway };
}
function selectDisplayRunwayTemp(
@@ -859,6 +864,23 @@ function selectDisplayRunwayTemp(
return liveTemp;
}
+function selectCompactSecondaryTemp({
+ isHKO,
+ isShenzhen,
+ displayMetarTemp,
+ observedHighMetar,
+}: {
+ isHKO: boolean;
+ isShenzhen: boolean;
+ displayMetarTemp: number | null;
+ observedHighMetar: number | null;
+}) {
+ if (isHKO && !isShenzhen && displayMetarTemp !== null) {
+ return displayMetarTemp;
+ }
+ return observedHighMetar;
+}
+
function isSettlementRunway(row: ScanOpportunityRow | null, rwy: string) {
const cityKey = normalizeCityKey(row?.city);
const settlementPairs = SETTLEMENT_RUNWAY_PAIRS[cityKey] || [];
@@ -2485,6 +2507,7 @@ export {
normalizeCityKey,
prefersHighFrequencyRunwayResolution,
readSessionCache,
+ selectCompactSecondaryTemp,
selectDisplayRunwayTemp,
seedHourlyForecastFromRow,
seriesStats,