CoWIN 6087 历史持久化 + 跑道曲线聚合 + 模型区间修复 + HKO 曲线对调 + 毛玻璃加载层

- weather_sources: CoWIN 1min 观测写入 official_intraday_observations_store
- country_networks: cowin_obs 源从 DB 回读 1min 历史,不复用 VHHH METAR
- analysis_service: 跑道实测 36h 历史按跑道分组,透传至前端
- scan_terminal_city_row: model_cluster_sources fallback 修复 + runway_plate_history
- 前端: CoWIN 6087 升级为主轴实线,HKO 退为虚线,VHHH METAR 轻虚线
- 前端: Loading 升级为毛玻璃全卡蒙层
This commit is contained in:
2569718930@qq.com
2026-05-26 09:13:39 +08:00
parent 09a8e1bfbe
commit 45365df694
6 changed files with 149 additions and 38 deletions
+34
View File
@@ -1532,8 +1532,42 @@ def _analyze(
}
# ── Assemble result ──
runway_plate_history = {}
icao = risk.get("icao", "")
if icao:
try:
from src.database.db_manager import DBManager
raw_runway_obs = DBManager().get_runway_obs_recent(icao, minutes=36 * 60)
for r in raw_runway_obs:
rw = r.get("runway")
if not rw:
continue
temp_val = r.get("target_runway_max")
if temp_val is None:
temp_val = r.get("tdz_temp")
if temp_val is not None:
temp_val = float(temp_val)
if is_f:
temp_val = round(temp_val * 9.0 / 5.0 + 32.0, 1)
else:
temp_val = round(temp_val, 1)
time_val = r.get("otime_utc") or r.get("created_at")
if not time_val:
continue
if rw not in runway_plate_history:
runway_plate_history[rw] = []
runway_plate_history[rw].append({
"time": time_val,
"temp": temp_val
})
except Exception:
logger.exception("Failed to fetch runway plate history for icao={}", icao)
city_meta = CITIES.get(city, {}) or {}
result = {
"runway_plate_history": runway_plate_history,
"detail_depth": (
"panel"
if is_panel_mode
+3 -1
View File
@@ -114,7 +114,7 @@ def _build_terminal_row(
"distribution_bias": scan.get("distribution_bias"),
"distribution_preview": scan.get("distribution_preview") or row.get("distribution_preview") or [],
"distribution_full": scan.get("distribution_full") or scan.get("distribution_preview") or row.get("distribution_preview") or [],
"model_cluster_sources": daily_entry.get("models") if isinstance(daily_entry.get("models"), dict) else data.get("multi_model"),
"model_cluster_sources": daily_entry.get("models") if isinstance(daily_entry.get("models"), dict) else data.get("multi_model", {}).get("forecasts"),
"window_phase": row.get("window_phase") or scan.get("window_phase"),
"window_score": row.get("window_score") if row.get("window_score") is not None else scan.get("window_score"),
"signal_status": scan.get("signal_status"),
@@ -129,6 +129,7 @@ def _build_terminal_row(
"amos": data.get("amos") or None,
"top_buckets": scan.get("top_buckets") or [],
"all_buckets": scan.get("all_buckets") or [],
"runway_plate_history": data.get("runway_plate_history") or {},
}
@@ -215,6 +216,7 @@ def _build_quick_row(
"is_primary_signal": True,
"accepting_orders": False,
"row_id": row_id,
"runway_plate_history": data.get("runway_plate_history") or {},
}
# Compute a simple edge: model top probability vs neutral
best_model_prob = max(
+1
View File
@@ -125,6 +125,7 @@ def build_city_detail_payload(
or _build_intraday_meteorology(data),
"vertical_profile_signal": data.get("vertical_profile_signal") or {},
"taf": data.get("taf") or {},
"runway_plate_history": data.get("runway_plate_history") or {},
"risk": data.get("risk"),
"settlement_station": data.get("settlement_station") or {},