修正市场监控新高提醒:统一数据源并修复 HKO/跑道城市逻辑
- resolveMaxSoFar:HKO 城市优先使用 current.max_so_far(天文台结算锚点) - trendClass:跑道城市跳过比较(跑道表面温度 vs 空气温度无意义) - newHigh badge / audio alert:跑道城市屏蔽新高判断 - 所有调用处传入 key 参数确保 HKO fallback 生效
This commit is contained in:
@@ -92,9 +92,12 @@ function playNewHighBeep(): void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function trendClass(detail: CityDetail | undefined): "rising" | "falling" | "flat" {
|
function trendClass(detail: CityDetail | undefined, key?: string): "rising" | "falling" | "flat" {
|
||||||
|
const { source } = resolveMonitorTemperature(detail);
|
||||||
|
// Runway surface temp vs air temp comparison is meaningless
|
||||||
|
if (source === "amos_runway_median" || source === "amos_runway") return "flat";
|
||||||
const cur = resolveMonitorTemperature(detail).value;
|
const cur = resolveMonitorTemperature(detail).value;
|
||||||
const max = resolveMaxSoFar(detail);
|
const max = resolveMaxSoFar(detail, key);
|
||||||
if (cur != null && max != null && cur >= max + 0.3) return "rising";
|
if (cur != null && max != null && cur >= max + 0.3) return "rising";
|
||||||
if (cur != null && max != null && cur < max - 1.0) return "falling";
|
if (cur != null && max != null && cur < max - 1.0) return "falling";
|
||||||
return "flat";
|
return "flat";
|
||||||
@@ -106,27 +109,33 @@ function trendSymbol(tr: "rising" | "falling" | "flat") {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolve today's observed high temperature.
|
* Resolve today's observed high temperature.
|
||||||
* Aligned with Telegram bot (_get_airport_daily_high):
|
|
||||||
* only airport_current.max_so_far — no fallback.
|
|
||||||
*
|
*
|
||||||
* Exception — HKO cities (Hong Kong / Lau Fau Shan):
|
* HKO cities (Hong Kong / Lau Fau Shan):
|
||||||
* The HKO observatory IS the settlement station, so current.max_so_far
|
* current.max_so_far from HKO observatory IS the settlement anchor.
|
||||||
* is equivalent to the airport obs. Use it as a fallback when
|
* airport_current (METAR) is secondary — prefer HKO first.
|
||||||
* airport_current.max_so_far is absent.
|
*
|
||||||
|
* All other cities:
|
||||||
|
* airport_current.max_so_far is the authoritative settlement reference.
|
||||||
*/
|
*/
|
||||||
function resolveMaxSoFar(
|
function resolveMaxSoFar(
|
||||||
detail: CityDetail | undefined,
|
detail: CityDetail | undefined,
|
||||||
key?: string,
|
key?: string,
|
||||||
): number | null {
|
): number | null {
|
||||||
const v = detail?.airport_current?.max_so_far ?? null;
|
// HKO: settlement station takes priority over airport METAR
|
||||||
if (v != null) return Math.round(v * 10) / 10;
|
|
||||||
|
|
||||||
// HKO fallback: current.max_so_far is authoritative for HKO stations
|
|
||||||
if (key === "hong kong" || key === "lau fau shan") {
|
if (key === "hong kong" || key === "lau fau shan") {
|
||||||
const hko = detail?.current?.max_so_far ?? null;
|
const hko = detail?.current?.max_so_far ?? null;
|
||||||
if (hko != null) return Math.round(hko * 10) / 10;
|
if (hko != null) return Math.round(hko * 10) / 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const v = detail?.airport_current?.max_so_far ?? null;
|
||||||
|
if (v != null) return Math.round(v * 10) / 10;
|
||||||
|
|
||||||
|
// Non-HKO fallback to current.max_so_far
|
||||||
|
if (key !== "hong kong" && key !== "lau fau shan") {
|
||||||
|
const cur = detail?.current?.max_so_far ?? null;
|
||||||
|
if (cur != null) return Math.round(cur * 10) / 10;
|
||||||
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -415,8 +424,10 @@ export default function MonitorPanel({
|
|||||||
if (alerted._day !== today) alerted = { _day: today };
|
if (alerted._day !== today) alerted = { _day: today };
|
||||||
|
|
||||||
for (const { key, detail } of sorted) {
|
for (const { key, detail } of sorted) {
|
||||||
const cur = resolveMonitorTemperature(detail).value;
|
const { source, value: cur } = resolveMonitorTemperature(detail);
|
||||||
const max = resolveMaxSoFar(detail, key); // HKO cities fall back to current.max_so_far
|
// Skip runway cities: runway surface temp ≠ air temp
|
||||||
|
if (source === "amos_runway_median" || source === "amos_runway") continue;
|
||||||
|
const max = resolveMaxSoFar(detail, key);
|
||||||
if (cur != null && max != null && cur >= max + 0.3) {
|
if (cur != null && max != null && cur >= max + 0.3) {
|
||||||
// Key: city + rounded temp, so we only beep once per 0.1°C step
|
// Key: city + rounded temp, so we only beep once per 0.1°C step
|
||||||
const id = `${key}|${(Math.round(cur * 10) / 10).toFixed(1)}`;
|
const id = `${key}|${(Math.round(cur * 10) / 10).toFixed(1)}`;
|
||||||
@@ -517,9 +528,10 @@ export default function MonitorPanel({
|
|||||||
: ac?.obs_age_min ?? null;
|
: ac?.obs_age_min ?? null;
|
||||||
const freshness = getMonitorFreshnessLevel(freshnessInfo, age);
|
const freshness = getMonitorFreshnessLevel(freshnessInfo, age);
|
||||||
const tempSymbol = detail.temp_symbol || "°C"; // °F for US cities
|
const tempSymbol = detail.temp_symbol || "°C"; // °F for US cities
|
||||||
const newHigh = cur != null && max != null && cur >= max + 0.3;
|
// Runway surface temp vs air temp comparison is meaningless
|
||||||
const warm = !newHigh && cur != null && cur >= 30;
|
const newHigh = !isRunwayTemp && cur != null && max != null && cur >= max + 0.3;
|
||||||
const tr = trendClass(detail);
|
const warm = !newHigh && !isRunwayTemp && cur != null && cur >= 30;
|
||||||
|
const tr = trendClass(detail, key);
|
||||||
const rwPairs = detail.amos?.runway_obs?.runway_pairs ?? [];
|
const rwPairs = detail.amos?.runway_obs?.runway_pairs ?? [];
|
||||||
const rwTemps = detail.amos?.runway_obs?.temperatures ?? [];
|
const rwTemps = detail.amos?.runway_obs?.temperatures ?? [];
|
||||||
const isFlashing = flashingKeys.has(key);
|
const isFlashing = flashingKeys.has(key);
|
||||||
|
|||||||
Reference in New Issue
Block a user