Clear stale Ankara source fields
This commit is contained in:
@@ -1843,18 +1843,34 @@ def test_stale_ankara_chart_data_overlays_latest_mgm_canonical(monkeypatch):
|
|||||||
"local_time": "13:12",
|
"local_time": "13:12",
|
||||||
"current": {
|
"current": {
|
||||||
"temp": 16.0,
|
"temp": 16.0,
|
||||||
|
"max_temp_time": "12:50",
|
||||||
|
"raw_max_so_far": 16.0,
|
||||||
|
"wu_settlement": 16,
|
||||||
"source_code": "metar",
|
"source_code": "metar",
|
||||||
"settlement_source": "metar",
|
"settlement_source": "metar",
|
||||||
"settlement_source_label": "METAR",
|
"settlement_source_label": "METAR",
|
||||||
"observed_at": "2026-06-14T09:50:00+00:00",
|
"observed_at": "2026-06-14T09:50:00+00:00",
|
||||||
|
"report_time": "2026-06-14T09:50:00+00:00",
|
||||||
|
"raw_metar": "METAR LTAC 140950Z 06013KT 9999 16/11 Q1015",
|
||||||
},
|
},
|
||||||
"airport_primary": {
|
"airport_primary": {
|
||||||
"temp": 16.0,
|
"temp": 16.0,
|
||||||
"source_code": "metar",
|
"source_code": "metar",
|
||||||
"source_label": "METAR",
|
"source_label": "METAR",
|
||||||
"obs_time": "2026-06-14T09:50:00+00:00",
|
"obs_time": "2026-06-14T09:50:00+00:00",
|
||||||
|
"report_time": "2026-06-14T09:50:00+00:00",
|
||||||
|
"raw_metar": "METAR LTAC 140950Z 06013KT 9999 16/11 Q1015",
|
||||||
},
|
},
|
||||||
"airport_primary_today_obs": [{"time": "12:50", "temp": 16.0}],
|
"airport_primary_today_obs": [{"time": "12:50", "temp": 16.0}],
|
||||||
|
"metar_today_obs": [{"time": "12:50", "temp": 16.0}],
|
||||||
|
"metar_recent_obs": [{"time": "12:50", "temp": 16.0}],
|
||||||
|
"metar_status": {
|
||||||
|
"available_for_today": True,
|
||||||
|
"stale_for_today": False,
|
||||||
|
"last_observation_local_date": "2026-06-14",
|
||||||
|
"current_local_date": "2026-06-14",
|
||||||
|
},
|
||||||
|
"mgm": {"temp": 16.7, "time": "12:50", "hourly": [{"time": "12:00", "temp": 16.5}]},
|
||||||
"hourly": {"times": ["2026-06-14T09:00:00Z"], "temps": [16.0]},
|
"hourly": {"times": ["2026-06-14T09:00:00Z"], "temps": [16.0]},
|
||||||
"deb": {"prediction": 23.0},
|
"deb": {"prediction": 23.0},
|
||||||
},
|
},
|
||||||
@@ -1901,6 +1917,16 @@ def test_stale_ankara_chart_data_overlays_latest_mgm_canonical(monkeypatch):
|
|||||||
assert payload["local_date"] == "2026-06-15"
|
assert payload["local_date"] == "2026-06-15"
|
||||||
assert payload["local_time"] == "17:20"
|
assert payload["local_time"] == "17:20"
|
||||||
assert payload["airport_primary_today_obs"] == [{"time": "17:20", "temp": 19.0}]
|
assert payload["airport_primary_today_obs"] == [{"time": "17:20", "temp": 19.0}]
|
||||||
|
assert "raw_metar" not in payload["current"]
|
||||||
|
assert "report_time" not in payload["airport_primary"]
|
||||||
|
assert payload["metar_today_obs"] == []
|
||||||
|
assert payload["metar_recent_obs"] == []
|
||||||
|
assert payload["metar_status"]["available_for_today"] is False
|
||||||
|
assert payload["metar_status"]["stale_for_today"] is True
|
||||||
|
assert payload["metar_status"]["current_local_date"] == "2026-06-15"
|
||||||
|
assert payload["mgm"]["temp"] == 19.0
|
||||||
|
assert payload["mgm"]["time"] == "17:20"
|
||||||
|
assert payload["mgm"].get("hourly") == []
|
||||||
assert payload["deb"]["prediction"] == 23.0
|
assert payload["deb"]["prediction"] == 23.0
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+109
-2
@@ -341,6 +341,31 @@ def _payload_observation_epoch(payload: Dict[str, Any]) -> Optional[int]:
|
|||||||
return max(epochs) if epochs else None
|
return max(epochs) if epochs else None
|
||||||
|
|
||||||
|
|
||||||
|
_SOURCE_BOUND_OBSERVATION_FIELDS = {
|
||||||
|
"altim",
|
||||||
|
"cloud_desc",
|
||||||
|
"clouds",
|
||||||
|
"clouds_raw",
|
||||||
|
"current_local_date",
|
||||||
|
"humidity",
|
||||||
|
"last_observation_local_date",
|
||||||
|
"max_temp_time",
|
||||||
|
"obs_time_epoch",
|
||||||
|
"pressure_hpa",
|
||||||
|
"raw_max_so_far",
|
||||||
|
"raw_metar",
|
||||||
|
"receipt_time",
|
||||||
|
"report_time",
|
||||||
|
"stale_for_today",
|
||||||
|
"visibility_km",
|
||||||
|
"visibility_mi",
|
||||||
|
"wind_dir",
|
||||||
|
"wind_speed_kt",
|
||||||
|
"wu_settlement",
|
||||||
|
"wx_desc",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def _float_or_none(value: Any) -> Optional[float]:
|
def _float_or_none(value: Any) -> Optional[float]:
|
||||||
try:
|
try:
|
||||||
if value is None or value == "":
|
if value is None or value == "":
|
||||||
@@ -385,6 +410,29 @@ def _latest_airport_primary_point(
|
|||||||
return [{"time": local_time, "temp": round(float(temp), 1)}]
|
return [{"time": local_time, "temp": round(float(temp), 1)}]
|
||||||
|
|
||||||
|
|
||||||
|
def _observation_source_code(block: Any) -> str:
|
||||||
|
if not isinstance(block, dict):
|
||||||
|
return ""
|
||||||
|
for key in ("source_code", "settlement_source", "source"):
|
||||||
|
value = str(block.get(key) or "").strip().lower()
|
||||||
|
if value:
|
||||||
|
return value
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
def _merge_latest_observation_block(base_block: Any, latest_block: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
|
base = dict(base_block) if isinstance(base_block, dict) else {}
|
||||||
|
latest_source = _observation_source_code(latest_block)
|
||||||
|
base_source = _observation_source_code(base)
|
||||||
|
if base_source and latest_source and base_source != latest_source:
|
||||||
|
base = {
|
||||||
|
key: value
|
||||||
|
for key, value in base.items()
|
||||||
|
if key not in _SOURCE_BOUND_OBSERVATION_FIELDS
|
||||||
|
}
|
||||||
|
return {**base, **latest_block}
|
||||||
|
|
||||||
|
|
||||||
def _replace_airport_primary_today_obs(
|
def _replace_airport_primary_today_obs(
|
||||||
payload: Dict[str, Any],
|
payload: Dict[str, Any],
|
||||||
points: List[Dict[str, Any]],
|
points: List[Dict[str, Any]],
|
||||||
@@ -395,6 +443,60 @@ def _replace_airport_primary_today_obs(
|
|||||||
official["airport_primary_today_obs"] = points
|
official["airport_primary_today_obs"] = points
|
||||||
|
|
||||||
|
|
||||||
|
def _clear_previous_day_observation_series(payload: Dict[str, Any], *, local_date: str) -> None:
|
||||||
|
for key in ("metar_today_obs", "metar_recent_obs", "settlement_today_obs"):
|
||||||
|
if key in payload:
|
||||||
|
payload[key] = []
|
||||||
|
timeseries = payload.get("timeseries")
|
||||||
|
if isinstance(timeseries, dict):
|
||||||
|
for key in ("metar_today_obs", "metar_recent_obs", "settlement_today_obs"):
|
||||||
|
if key in timeseries:
|
||||||
|
timeseries[key] = []
|
||||||
|
metar_status = payload.get("metar_status")
|
||||||
|
if isinstance(metar_status, dict):
|
||||||
|
metar_status["available_for_today"] = False
|
||||||
|
metar_status["stale_for_today"] = True
|
||||||
|
metar_status["current_local_date"] = local_date
|
||||||
|
|
||||||
|
|
||||||
|
def _sync_latest_mgm_summary(
|
||||||
|
payload: Dict[str, Any],
|
||||||
|
latest_payload: Dict[str, Any],
|
||||||
|
*,
|
||||||
|
local_time: str,
|
||||||
|
) -> None:
|
||||||
|
latest_current = latest_payload.get("current") if isinstance(latest_payload.get("current"), dict) else {}
|
||||||
|
latest_airport = (
|
||||||
|
latest_payload.get("airport_primary")
|
||||||
|
if isinstance(latest_payload.get("airport_primary"), dict)
|
||||||
|
else {}
|
||||||
|
)
|
||||||
|
latest_source = _observation_source_code(latest_current) or _observation_source_code(latest_airport)
|
||||||
|
if latest_source != "mgm":
|
||||||
|
return
|
||||||
|
temp = _float_or_none(latest_airport.get("temp") if latest_airport else None)
|
||||||
|
if temp is None:
|
||||||
|
temp = _float_or_none(latest_current.get("temp") if latest_current else None)
|
||||||
|
if temp is None:
|
||||||
|
return
|
||||||
|
payload["mgm"] = {
|
||||||
|
"temp": round(float(temp), 1),
|
||||||
|
"time": local_time,
|
||||||
|
"feels_like": round(float(temp), 1),
|
||||||
|
"humidity": None,
|
||||||
|
"wind_dir": None,
|
||||||
|
"wind_speed_ms": None,
|
||||||
|
"pressure": None,
|
||||||
|
"cloud_cover": None,
|
||||||
|
"rain_24h": None,
|
||||||
|
"today_high": None,
|
||||||
|
"today_low": None,
|
||||||
|
"station_code": latest_airport.get("station_code") or latest_current.get("station_code"),
|
||||||
|
"station_name": latest_airport.get("station_name") or latest_current.get("station_name"),
|
||||||
|
"hourly": [],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def _merge_latest_observation_payload(
|
def _merge_latest_observation_payload(
|
||||||
city: str,
|
city: str,
|
||||||
payload: Dict[str, Any],
|
payload: Dict[str, Any],
|
||||||
@@ -414,8 +516,7 @@ def _merge_latest_observation_payload(
|
|||||||
latest_block = latest_payload.get(key)
|
latest_block = latest_payload.get(key)
|
||||||
if not isinstance(latest_block, dict) or not latest_block:
|
if not isinstance(latest_block, dict) or not latest_block:
|
||||||
continue
|
continue
|
||||||
base_block = next_payload.get(key) if isinstance(next_payload.get(key), dict) else {}
|
next_payload[key] = _merge_latest_observation_block(next_payload.get(key), latest_block)
|
||||||
next_payload[key] = {**base_block, **latest_block}
|
|
||||||
if isinstance(latest_payload.get("canonical_temperature"), dict):
|
if isinstance(latest_payload.get("canonical_temperature"), dict):
|
||||||
next_payload["canonical_temperature"] = latest_payload["canonical_temperature"]
|
next_payload["canonical_temperature"] = latest_payload["canonical_temperature"]
|
||||||
if latest_payload.get("updated_at"):
|
if latest_payload.get("updated_at"):
|
||||||
@@ -435,6 +536,12 @@ def _merge_latest_observation_payload(
|
|||||||
local_time=local_context["local_time"],
|
local_time=local_context["local_time"],
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
_clear_previous_day_observation_series(next_payload, local_date=local_context["local_date"])
|
||||||
|
_sync_latest_mgm_summary(
|
||||||
|
next_payload,
|
||||||
|
latest_payload,
|
||||||
|
local_time=local_context["local_time"],
|
||||||
|
)
|
||||||
return next_payload
|
return next_payload
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user