Fix WU historical current observation merge
This commit is contained in:
@@ -790,6 +790,7 @@ def _analyze(
|
||||
taf = raw.get("taf", {})
|
||||
mgm = raw.get("mgm") or {}
|
||||
settlement_current = raw.get("settlement_current") or {}
|
||||
wunderground_current = raw.get("wunderground_current") or {}
|
||||
ens_raw = raw.get("ensemble", {})
|
||||
mm = raw.get("multi_model", {})
|
||||
if not isinstance(om, dict):
|
||||
@@ -800,6 +801,8 @@ def _analyze(
|
||||
mgm = {}
|
||||
if not isinstance(settlement_current, dict):
|
||||
settlement_current = {}
|
||||
if not isinstance(wunderground_current, dict):
|
||||
wunderground_current = {}
|
||||
if not isinstance(ens_raw, dict):
|
||||
ens_raw = {}
|
||||
if not isinstance(mm, dict):
|
||||
@@ -1795,6 +1798,7 @@ def _analyze(
|
||||
"last_observation_local_date": metar.get("observation_local_date") if metar else None,
|
||||
"current_local_date": local_date_str,
|
||||
},
|
||||
"wunderground_current": wunderground_current,
|
||||
"settlement_station": network_snapshot.get("settlement_station") or {},
|
||||
"airport_primary": airport_primary_current,
|
||||
"airport_primary_today_obs": network_snapshot.get("airport_primary_today_obs") or [],
|
||||
@@ -1941,6 +1945,11 @@ def _analyze_summary(city: str, force_refresh: bool = False) -> Dict[str, Any]:
|
||||
|
||||
jobs: Dict[str, Any] = {
|
||||
"settlement_current": lambda: _weather.fetch_settlement_current(city) or {},
|
||||
"wunderground_current": lambda: _weather.fetch_wunderground_historical(
|
||||
city,
|
||||
use_fahrenheit=is_f,
|
||||
utc_offset=default_utc_offset,
|
||||
) or {},
|
||||
"open_meteo": lambda: _weather.fetch_from_open_meteo(lat, lon, use_fahrenheit=is_f) or {},
|
||||
"multi_model": lambda: _weather.fetch_multi_model(lat, lon, city=city, use_fahrenheit=is_f) or {},
|
||||
}
|
||||
@@ -1969,8 +1978,11 @@ def _analyze_summary(city: str, force_refresh: bool = False) -> Dict[str, Any]:
|
||||
fetched[key] = future.result()
|
||||
|
||||
settlement_current = fetched.get("settlement_current") or {}
|
||||
wunderground_current = fetched.get("wunderground_current") or {}
|
||||
open_meteo = fetched.get("open_meteo") or {}
|
||||
mm = fetched.get("multi_model") or {}
|
||||
if not isinstance(wunderground_current, dict):
|
||||
wunderground_current = {}
|
||||
utc_offset = open_meteo.get("utc_offset")
|
||||
if utc_offset is None:
|
||||
utc_offset = default_utc_offset
|
||||
@@ -2233,6 +2245,7 @@ def _analyze_summary(city: str, force_refresh: bool = False) -> Dict[str, Any]:
|
||||
"obs_age_min": obs_age_min,
|
||||
"observation_status": "live" if cur_temp is not None else "missing",
|
||||
},
|
||||
"wunderground_current": wunderground_current,
|
||||
"deb": {
|
||||
"prediction": _sf(deb_val),
|
||||
"raw_prediction": _sf(deb_raw_val),
|
||||
|
||||
@@ -96,6 +96,7 @@ def _build_terminal_row(
|
||||
"temp_symbol": data.get("temp_symbol"),
|
||||
"current_temp": current.get("temp"),
|
||||
"current_max_so_far": current.get("max_so_far"),
|
||||
"wunderground_current": data.get("wunderground_current") or {},
|
||||
"metar_context": metar_context,
|
||||
"metar_today_obs": metar_context.get("today_obs") or [],
|
||||
"metar_recent_obs": metar_context.get("recent_obs") or [],
|
||||
@@ -207,6 +208,7 @@ def _build_quick_row(
|
||||
"risk_level": risk.get("level"),
|
||||
"current_temp": curr.get("temp"),
|
||||
"current_max_so_far": curr.get("max_so_far"),
|
||||
"wunderground_current": data.get("wunderground_current") or {},
|
||||
"deb_prediction": deb.get("prediction"),
|
||||
"model_cluster_sources": (
|
||||
daily_entry.get("models")
|
||||
|
||||
@@ -11,6 +11,14 @@ from loguru import logger
|
||||
import web.routes as legacy_routes
|
||||
|
||||
|
||||
async def _overlay_cached_wunderground(city: str, payload: Dict[str, Any]) -> Dict[str, Any]:
|
||||
return await run_in_threadpool(
|
||||
legacy_routes._overlay_latest_wunderground_current,
|
||||
city,
|
||||
payload,
|
||||
)
|
||||
|
||||
|
||||
def _build_cities_payload() -> Dict[str, Any]:
|
||||
out = []
|
||||
deb_recent_index = legacy_routes._build_recent_deb_performance_index()
|
||||
@@ -86,7 +94,7 @@ async def get_city_detail_payload(
|
||||
if cached_entry:
|
||||
if not legacy_routes._city_cache_is_fresh(cached_entry, legacy_routes.CITY_FULL_CACHE_TTL_SEC):
|
||||
return await run_in_threadpool(legacy_routes._refresh_city_full_cache, city, False)
|
||||
return cached_entry.get("payload") or {}
|
||||
return await _overlay_cached_wunderground(city, cached_entry.get("payload") or {})
|
||||
return await run_in_threadpool(legacy_routes._refresh_city_full_cache, city, False)
|
||||
if detail_mode == "panel":
|
||||
if force_refresh:
|
||||
@@ -95,7 +103,7 @@ async def get_city_detail_payload(
|
||||
if cached_entry:
|
||||
if not legacy_routes._city_cache_is_fresh(cached_entry, legacy_routes.CITY_PANEL_CACHE_TTL_SEC):
|
||||
return await run_in_threadpool(legacy_routes._refresh_city_panel_cache, city, False)
|
||||
return cached_entry.get("payload") or {}
|
||||
return await _overlay_cached_wunderground(city, cached_entry.get("payload") or {})
|
||||
return await run_in_threadpool(legacy_routes._refresh_city_panel_cache, city, False)
|
||||
if detail_mode == "nearby":
|
||||
if force_refresh:
|
||||
@@ -104,7 +112,7 @@ async def get_city_detail_payload(
|
||||
if cached_entry:
|
||||
if not legacy_routes._city_cache_is_fresh(cached_entry, legacy_routes.CITY_NEARBY_CACHE_TTL_SEC):
|
||||
return await run_in_threadpool(legacy_routes._refresh_city_nearby_cache, city, False)
|
||||
return cached_entry.get("payload") or {}
|
||||
return await _overlay_cached_wunderground(city, cached_entry.get("payload") or {})
|
||||
return await run_in_threadpool(legacy_routes._refresh_city_nearby_cache, city, False)
|
||||
if detail_mode == "market":
|
||||
if force_refresh:
|
||||
@@ -113,7 +121,7 @@ async def get_city_detail_payload(
|
||||
if cached_entry:
|
||||
if not legacy_routes._market_analysis_cache_is_fresh(cached_entry):
|
||||
return await run_in_threadpool(legacy_routes._refresh_city_market_cache, city, False)
|
||||
return cached_entry.get("payload") or {}
|
||||
return await _overlay_cached_wunderground(city, cached_entry.get("payload") or {})
|
||||
return await run_in_threadpool(legacy_routes._refresh_city_market_cache, city, False)
|
||||
return await run_in_threadpool(legacy_routes._analyze, city, force_refresh, False, detail_mode)
|
||||
|
||||
@@ -131,7 +139,7 @@ async def get_city_summary_payload(
|
||||
if cached_entry:
|
||||
if not legacy_routes._city_cache_is_fresh(cached_entry, legacy_routes.CITY_SUMMARY_CACHE_TTL_SEC):
|
||||
return await run_in_threadpool(legacy_routes._refresh_city_summary_cache, city, False)
|
||||
return cached_entry.get("payload") or {}
|
||||
return await _overlay_cached_wunderground(city, cached_entry.get("payload") or {})
|
||||
return await run_in_threadpool(legacy_routes._refresh_city_summary_cache, city, False)
|
||||
|
||||
|
||||
@@ -154,7 +162,7 @@ async def get_city_detail_aggregate_payload(
|
||||
if not legacy_routes._city_cache_is_fresh(cached_entry, legacy_routes.CITY_FULL_CACHE_TTL_SEC):
|
||||
data = await run_in_threadpool(legacy_routes._refresh_city_full_cache, city, False)
|
||||
else:
|
||||
data = cached_entry.get("payload") or {}
|
||||
data = await _overlay_cached_wunderground(city, cached_entry.get("payload") or {})
|
||||
else:
|
||||
data = await run_in_threadpool(legacy_routes._refresh_city_full_cache, city, False)
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ def build_city_summary_payload(data: Dict[str, Any]) -> Dict[str, Any]:
|
||||
),
|
||||
},
|
||||
"deb": dict(deb) if deb else {"prediction": None},
|
||||
"wunderground_current": data.get("wunderground_current") or {},
|
||||
"deviation_monitor": data.get("deviation_monitor") or {},
|
||||
"risk": {
|
||||
"level": data.get("risk", {}).get("level"),
|
||||
@@ -212,6 +213,7 @@ def build_city_detail_payload(
|
||||
),
|
||||
"airport_primary": data.get("airport_primary") or {},
|
||||
"airport_primary_today_obs": data.get("airport_primary_today_obs") or [],
|
||||
"wunderground_current": data.get("wunderground_current") or {},
|
||||
"official_nearby": data.get("official_nearby") or [],
|
||||
"official_network_source": data.get("official_network_source"),
|
||||
"official_network_status": data.get("official_network_status") or {},
|
||||
@@ -224,6 +226,7 @@ def build_city_detail_payload(
|
||||
"metar_recent_obs": data.get("metar_recent_obs") or [],
|
||||
"metar_today_obs": data.get("metar_today_obs") or [],
|
||||
"settlement_today_obs": data.get("settlement_today_obs") or [],
|
||||
"wunderground_today_obs": (data.get("wunderground_current") or {}).get("today_obs") or [],
|
||||
"hourly": data.get("hourly") or {},
|
||||
"mgm_hourly": (data.get("mgm") or {}).get("hourly", []),
|
||||
"forecast_daily": (data.get("forecast") or {}).get("daily", []),
|
||||
@@ -266,6 +269,7 @@ def build_city_detail_payload(
|
||||
"center_station_candidate": data.get("center_station_candidate"),
|
||||
"airport_vs_network_delta": data.get("airport_vs_network_delta"),
|
||||
"airport_current": data.get("airport_current") or {},
|
||||
"wunderground_current": data.get("wunderground_current") or {},
|
||||
"amos": data.get("amos") or {},
|
||||
"nearby_source": data.get("nearby_source")
|
||||
or (
|
||||
|
||||
@@ -2,6 +2,7 @@ from __future__ import annotations
|
||||
|
||||
import os
|
||||
import time
|
||||
from copy import deepcopy
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
|
||||
@@ -53,6 +54,7 @@ from web.core import (
|
||||
_resolve_auth_points, # noqa: F401 - compatibility export for tests and transitional routers
|
||||
_resolve_weekly_profile, # noqa: F401 - compatibility export for tests and transitional routers
|
||||
_sf,
|
||||
_weather,
|
||||
)
|
||||
|
||||
router = APIRouter()
|
||||
@@ -329,6 +331,52 @@ def _refresh_city_full_cache(city: str, force_refresh: bool = False) -> dict:
|
||||
return payload
|
||||
|
||||
|
||||
def _overlay_wunderground_current(payload: dict, wunderground: dict) -> dict:
|
||||
if not isinstance(payload, dict) or not isinstance(wunderground, dict) or not wunderground:
|
||||
return payload
|
||||
next_payload = deepcopy(payload)
|
||||
next_payload["wunderground_current"] = dict(wunderground)
|
||||
|
||||
official = next_payload.get("official")
|
||||
if isinstance(official, dict):
|
||||
official["wunderground_current"] = dict(wunderground)
|
||||
|
||||
timeseries = next_payload.get("timeseries")
|
||||
if isinstance(timeseries, dict):
|
||||
timeseries["wunderground_today_obs"] = list(wunderground.get("today_obs") or [])
|
||||
|
||||
return next_payload
|
||||
|
||||
|
||||
def _overlay_latest_wunderground_current(city: str, payload: dict) -> dict:
|
||||
if not isinstance(payload, dict):
|
||||
return payload
|
||||
city_key = str(city or payload.get("name") or payload.get("city") or "").strip().lower()
|
||||
if city_key not in CITIES:
|
||||
return deepcopy(payload)
|
||||
try:
|
||||
utc_offset = int(
|
||||
payload.get("utc_offset_seconds")
|
||||
or (payload.get("overview") or {}).get("utc_offset_seconds")
|
||||
or get_city_utc_offset_seconds(city_key)
|
||||
or 0
|
||||
)
|
||||
except Exception:
|
||||
utc_offset = get_city_utc_offset_seconds(city_key)
|
||||
try:
|
||||
latest_wu = _weather.fetch_wunderground_historical(
|
||||
city_key,
|
||||
use_fahrenheit=bool(CITIES[city_key].get("f")),
|
||||
utc_offset=utc_offset,
|
||||
)
|
||||
except Exception as exc:
|
||||
logger.warning("latest WU overlay failed city={} error={}", city_key, exc)
|
||||
return deepcopy(payload)
|
||||
if not isinstance(latest_wu, dict) or not latest_wu:
|
||||
return deepcopy(payload)
|
||||
return _overlay_wunderground_current(payload, latest_wu)
|
||||
|
||||
|
||||
|
||||
def _schedule_cache_refresh(
|
||||
background_tasks: BackgroundTasks,
|
||||
|
||||
Reference in New Issue
Block a user