feat: implement interactive temperature chart dashboard with runway trend visualization and multi-source data collection support

This commit is contained in:
2569718930@qq.com
2026-05-27 01:16:49 +08:00
parent c4119c1aab
commit 2670e2f8ee
13 changed files with 308 additions and 52 deletions
+20 -2
View File
@@ -32,6 +32,15 @@ def _safe_float(value: Any) -> Optional[float]:
return None
def _display_temp_from_c(temp_c: Any, use_fahrenheit: bool) -> Optional[float]:
numeric = _safe_float(temp_c)
if numeric is None:
return None
if use_fahrenheit:
return round(numeric * 9.0 / 5.0 + 32.0, 1)
return round(numeric, 1)
def _parse_obs_datetime(
value: Any,
epoch_value: Any = None,
@@ -248,11 +257,18 @@ def _airport_primary_from_raw(city: str, raw: Dict[str, Any]) -> Dict[str, Any]:
# High-frequency realtime sources take priority over plain METAR.
madis = raw.get("madis_hfmetar_current") or {}
if madis.get("temp_c") is not None:
madis_temp_c = _safe_float(madis.get("temp_c"))
madis_temp = _safe_float(madis.get("temp"))
if madis_temp is None and madis_temp_c is not None:
madis_temp = _display_temp_from_c(
madis_temp_c,
bool(meta.get("use_fahrenheit")),
)
if madis_temp is not None:
return _normalize_station_row(
station_code=meta.get("icao") or madis.get("icao"),
station_label=meta.get("airport_name") or meta.get("icao"),
temp=madis["temp_c"],
temp=madis_temp,
obs_time=madis.get("obs_time") or metar.get("observation_time"),
source_code="madis_hfmetar",
source_label="NOAA MADIS",
@@ -260,6 +276,8 @@ def _airport_primary_from_raw(city: str, raw: Dict[str, Any]) -> Dict[str, Any]:
is_airport_station=True,
is_settlement_anchor=False,
extra={
"temp_c": madis_temp_c,
"unit": "fahrenheit" if bool(meta.get("use_fahrenheit")) else "celsius",
"max_so_far": _safe_float(current.get("max_temp_so_far")),
"max_temp_time": current.get("max_temp_time"),
"obs_age_min": None,
+30 -6
View File
@@ -925,6 +925,16 @@ class WeatherDataCollector(OpenMeteoCacheMixin, SettlementSourceMixin, MetarSour
def _uses_fahrenheit(self, city_lower: str) -> bool:
return city_lower in self.US_CITIES
@staticmethod
def _display_temp_from_c(temp_c: Any, use_fahrenheit: bool) -> Optional[float]:
try:
numeric = float(temp_c)
except (TypeError, ValueError):
return None
if use_fahrenheit:
return round(numeric * 9.0 / 5.0 + 32.0, 1)
return round(numeric, 1)
def _supports_aviationweather(self, city_lower: str) -> bool:
city_meta = self.CITY_REGISTRY.get(str(city_lower or "").strip().lower(), {}) or {}
settlement_source = str(city_meta.get("settlement_source") or "").strip().lower()
@@ -1451,7 +1461,7 @@ class WeatherDataCollector(OpenMeteoCacheMixin, SettlementSourceMixin, MetarSour
logger.warning("AMSC AWOS attach failed city={}: {}", city_lower, exc)
def _attach_madis_hfmetar_data(
self, results: Dict, city_lower: str
self, results: Dict, city_lower: str, use_fahrenheit: bool
) -> None:
"""Fetch MADIS HFMETAR data and attach matching US station observations."""
# Only run for US cities (ICAO starts with K)
@@ -1476,19 +1486,33 @@ class WeatherDataCollector(OpenMeteoCacheMixin, SettlementSourceMixin, MetarSour
# Find this city's station in the MADIS results
for obs in obs_list:
if obs["icao"] == icao:
temp_c = self._display_temp_from_c(obs.get("temp_c"), False)
display_temp = self._display_temp_from_c(
temp_c,
use_fahrenheit,
)
if temp_c is None or display_temp is None:
continue
display_unit = "fahrenheit" if use_fahrenheit else "celsius"
# Inject into results so it flows through to airport_primary
results["madis_hfmetar_current"] = obs
results["madis_hfmetar_current"] = {
**obs,
"temp_c": temp_c,
"temp": display_temp,
"unit": display_unit,
}
self._emit_temperature_patch_if_changed(
city_lower,
obs.get("temp_c"),
display_temp,
obs.get("obs_time"),
source="madis_hfmetar",
extra={"temp_c": temp_c, "unit": display_unit},
)
try:
DBManager().append_airport_obs(
icao=icao,
city=city_lower,
temp_c=obs["temp_c"],
temp_c=temp_c,
wind_kt=obs.get("wind_kt"),
pressure_hpa=obs.get("pressure_hpa"),
obs_time=obs["obs_time"] or datetime.now().isoformat(),
@@ -1686,7 +1710,7 @@ class WeatherDataCollector(OpenMeteoCacheMixin, SettlementSourceMixin, MetarSour
)
self._attach_korean_amos_data(results, city_lower, use_fahrenheit)
self._attach_china_amsc_awos_data(results, city_lower, use_fahrenheit)
self._attach_madis_hfmetar_data(results, city_lower)
self._attach_madis_hfmetar_data(results, city_lower, use_fahrenheit)
self._attach_singapore_mss_data(results, city_lower)
self._attach_israel_ims_data(results, city_lower)
self._attach_saudi_ncm_data(results, city_lower)
@@ -1738,7 +1762,7 @@ class WeatherDataCollector(OpenMeteoCacheMixin, SettlementSourceMixin, MetarSour
)
self._attach_korean_amos_data(results, city_lower, use_fahrenheit)
self._attach_china_amsc_awos_data(results, city_lower, use_fahrenheit)
self._attach_madis_hfmetar_data(results, city_lower)
self._attach_madis_hfmetar_data(results, city_lower, use_fahrenheit)
self._attach_singapore_mss_data(results, city_lower)
self._attach_israel_ims_data(results, city_lower)
self._attach_saudi_ncm_data(results, city_lower)