修正市场监控温度数据源:接入 NOAA MADIS 5分钟高频数据并优化展示

- 首尔/釜山:隐藏大号跑道温度值,改为"跑道温度"标签(跑道表面温度 ≠ 空气温度)
- US 城市:MADIS HFMETAR 5分钟小数温度接入 airport_primary,前端优先读取
- 其他城市:整数值不再强制 toFixed(1) 追加虚假 .0 精度
- 后端:weather_sources.py 注入 madis_hfmetar_current 到 results
- 后端:country_networks.py 的 _airport_primary_from_raw 新增 MADIS 优先分支
- 文档:更新 AIRPORT_REALTIME_SOURCES.md 新增 11 个 US 城市
- 文档:更新 CLAUDE.md 补充市场监控、高频数据管道、Country Network Provider 架构

Tested: npx tsc --noEmit ✓  ruff check . ✓
This commit is contained in:
2569718930@qq.com
2026-05-14 16:00:55 +08:00
parent c9d03fd3e1
commit 96676e7097
7 changed files with 83 additions and 9 deletions
@@ -215,6 +215,13 @@
color: var(--color-border-default);
}
.root :global(.monitor-temp-runway) {
font-size: 18px;
font-weight: 600;
color: var(--color-text-muted);
letter-spacing: 0.02em;
}
/* ── Stats row ── */
.root :global(.monitor-stats) {
font-size: 14px;
@@ -421,6 +428,7 @@
.root :global(.scan-terminal.light .monitor-temp-unit),
.root :global(.scan-terminal.light .monitor-temp-missing),
.root :global(.scan-terminal.light .monitor-temp-runway),
.root :global(.scan-terminal.light .monitor-stat-missing) {
color: #94A3B8;
}
@@ -553,6 +561,7 @@
.root :global(.scan-terminal.light .monitor-temp-unit),
.root :global(.scan-terminal.light .monitor-temp-missing),
.root :global(.scan-terminal.light .monitor-temp-runway),
.root :global(.scan-terminal.light .monitor-stat-missing) {
color: #94A3B8;
}
@@ -476,7 +476,10 @@ export default function MonitorPanel({
}
const ac = detail.airport_current;
const cur = resolveMonitorTemperature(detail).value;
const tempInfo = resolveMonitorTemperature(detail);
const cur = tempInfo.value;
const curSource = tempInfo.source;
const isRunwayTemp = curSource === "amos_runway_median" || curSource === "amos_runway";
const max = resolveMaxSoFar(detail, key); // HKO cities fall back to current.max_so_far
const mtt = ac?.max_temp_time ?? detail.current?.max_temp_time ?? null;
const freshnessInfo = getObservationFreshness(detail);
@@ -533,12 +536,16 @@ export default function MonitorPanel({
<span className="monitor-obs-time">{obs}</span>
</div>
{/* Temperature */}
{/* Temperature — runway cities skip the large value (runway surface ≠ air temp) */}
<div className="monitor-temp-display">
{cur != null ? (
{isRunwayTemp ? (
<span className="monitor-temp-runway">
{t("Runway Temp", "跑道温度", lang)}
</span>
) : cur != null ? (
<>
<span className={`monitor-temp-value${newHigh ? " new-high" : warm ? " warm" : ""}${isFlashing ? " flashed" : ""}`}>
{cur.toFixed(1)}
{Number.isInteger(cur) ? cur.toFixed(0) : cur.toFixed(1)}
</span>
<span className="monitor-temp-unit">{tempSymbol}</span>
</>
@@ -553,7 +560,7 @@ export default function MonitorPanel({
<span className="monitor-stat-label">{t("Today's High", "今日实测高温", lang)}</span>
{max != null ? (
<>
<span className="monitor-high-value">{max.toFixed(1)}{tempSymbol}</span>
<span className="monitor-high-value">{Number.isInteger(max) ? max.toFixed(0) : max.toFixed(1)}{tempSymbol}</span>
{mtt && <span className="monitor-high-time">{mtt}</span>}
</>
) : (
@@ -4,6 +4,7 @@ export type MonitorTemperatureSource =
| "amos_runway_median"
| "amos_runway"
| "amos"
| "airport_primary"
| "airport_current"
| "current"
| "missing";
@@ -54,6 +55,9 @@ export function resolveMonitorTemperature(
return { source: "amos", value: amosTemp };
}
const airportPrimary = finiteNumber(detail?.airport_primary?.temp);
if (airportPrimary != null) return { source: "airport_primary", value: airportPrimary };
const airport = finiteNumber(detail?.airport_current?.temp);
if (airport != null) return { source: "airport_current", value: airport };