diff --git a/src/utils/telegram_push.py b/src/utils/telegram_push.py index 8d00141f..5b987cf7 100644 --- a/src/utils/telegram_push.py +++ b/src/utils/telegram_push.py @@ -1409,6 +1409,25 @@ def _airport_push_cache_max_age_sec(city: str) -> int: return max(90, interval * 2) +def _cached_payload_observation_epoch(payload: Dict[str, Any]) -> Optional[int]: + amos = payload.get("amos") or {} + airport_primary = payload.get("airport_primary") or {} + airport_current = payload.get("airport_current") or {} + current = payload.get("current") or {} + candidates = [ + amos.get("observation_time"), + airport_primary.get("obs_time"), + airport_current.get("obs_time"), + current.get("obs_time"), + ] + parsed = [ + timestamp + for timestamp in (_parse_observation_time_epoch(value) for value in candidates) + if timestamp is not None + ] + return max(parsed) if parsed else None + + def _read_cached_airport_city_weather(city: str, max_age_sec: Optional[int] = None) -> Optional[Dict[str, Any]]: """Read web city cache for Telegram without triggering collection.""" normalized_city = (city or "").strip().lower() @@ -1416,7 +1435,7 @@ def _read_cached_airport_city_weather(city: str, max_age_sec: Optional[int] = No return None max_age = int(max_age_sec if max_age_sec is not None else _airport_push_cache_max_age_sec(normalized_city)) now_ts = time.time() - stale_candidate: Optional[Tuple[float, Dict[str, Any]]] = None + candidates: List[Tuple[int, int, float, str, Dict[str, Any]]] = [] try: db = DBManager() for kind in ("full", "panel"): @@ -1428,6 +1447,7 @@ def _read_cached_airport_city_weather(city: str, max_age_sec: Optional[int] = No if updated_at_ts <= 0 or not isinstance(payload, dict): continue age_sec = now_ts - updated_at_ts + is_fresh = age_sec <= max_age if age_sec > max_age: logger.debug( "airport push cache stale city={} kind={} age_sec={} max_age_sec={}", @@ -1436,21 +1456,32 @@ def _read_cached_airport_city_weather(city: str, max_age_sec: Optional[int] = No round(age_sec, 1), max_age, ) - if stale_candidate is None or updated_at_ts > stale_candidate[0]: - stale_candidate = (updated_at_ts, dict(payload)) - continue - logger.debug( - "airport push cache hit city={} kind={} age_sec={}", - normalized_city, - kind, - round(max(0.0, age_sec), 1), + observation_ts = _cached_payload_observation_epoch(payload) + candidates.append( + ( + int(observation_ts or 0), + 1 if is_fresh else 0, + updated_at_ts, + kind, + dict(payload), + ) ) - return dict(payload) except Exception as exc: logger.debug("airport push city cache read failed city={}: {}", normalized_city, exc) - if stale_candidate is not None: - logger.debug("airport push using stale cache city={}", normalized_city) - return stale_candidate[1] + if candidates: + observation_ts, is_fresh, updated_at_ts, kind, payload = max( + candidates, + key=lambda item: (item[0], item[1], item[2]), + ) + logger.debug( + "airport push cache selected city={} kind={} age_sec={} observation_ts={} fresh={}", + normalized_city, + kind, + round(max(0.0, now_ts - updated_at_ts), 1), + observation_ts or None, + bool(is_fresh), + ) + return payload return None diff --git a/tests/test_telegram_hashtags.py b/tests/test_telegram_hashtags.py index 2c228e51..e2cc456e 100644 --- a/tests/test_telegram_hashtags.py +++ b/tests/test_telegram_hashtags.py @@ -251,6 +251,41 @@ def test_high_freq_airport_push_prefers_fresh_city_cache(monkeypatch): assert bot.messages +def test_airport_push_prefers_cache_with_newest_observation(monkeypatch): + import src.utils.telegram_push as telegram_push + + now = telegram_push.time.time() + + class FakeDB: + def get_city_cache(self, kind, city): + assert city == "shanghai" + if kind == "full": + return { + "updated_at_ts": now, + "payload": { + "current": {"temp": 31.0}, + "airport_primary": {"temp": 31.0, "obs_time": "15:00"}, + }, + } + return { + "updated_at_ts": now - 120, + "payload": { + "current": {"temp": 31.4}, + "amos": {"observation_time": "2026-06-11T07:42:00+00:00"}, + "airport_primary": { + "temp": 31.4, + "obs_time": "2026-06-11T07:42:00+00:00", + }, + }, + } + + monkeypatch.setattr(telegram_push, "DBManager", lambda: FakeDB()) + + city_weather = telegram_push._read_cached_airport_city_weather("shanghai") + + assert city_weather["airport_primary"]["temp"] == 31.4 + + def test_airport_push_fallback_analysis_does_not_force_observation_refresh(monkeypatch): import src.utils.telegram_push as telegram_push import web.app as web_app