From 383011f9fe0d44297eaee9cfb90b10c84d7ead7b Mon Sep 17 00:00:00 2001 From: "2569718930@qq.com" <2569718930@qq.com> Date: Mon, 15 Jun 2026 20:20:31 +0800 Subject: [PATCH] Filter runway history by observation time --- src/database/db_manager.py | 7 +++-- tests/test_observation_collector.py | 35 ++++++++++++++++++++-- tests/test_web_observability.py | 4 +-- web/analysis_service.py | 2 +- web/services/city_api.py | 2 +- web/services/latest_observation_overlay.py | 2 +- 6 files changed, 43 insertions(+), 9 deletions(-) diff --git a/src/database/db_manager.py b/src/database/db_manager.py index 226e6691..280d0fe9 100644 --- a/src/database/db_manager.py +++ b/src/database/db_manager.py @@ -4257,8 +4257,11 @@ class DBManager: wind_dir, wind_speed, rvr, mor, humidity, otime_utc, created_at FROM runway_obs_log - WHERE icao = ? AND created_at >= datetime('now', ? || ' minutes') - ORDER BY created_at ASC + WHERE icao = ? + AND datetime(COALESCE(NULLIF(otime_utc, ''), created_at)) + >= datetime('now', ? || ' minutes') + ORDER BY datetime(COALESCE(NULLIF(otime_utc, ''), created_at)) ASC, + created_at ASC """, (str(icao).strip().upper(), str(-int(minutes))), ).fetchall() diff --git a/tests/test_observation_collector.py b/tests/test_observation_collector.py index 5fcd19b9..3c0aff5d 100644 --- a/tests/test_observation_collector.py +++ b/tests/test_observation_collector.py @@ -1,4 +1,5 @@ from concurrent.futures import ThreadPoolExecutor, TimeoutError +from datetime import datetime, timedelta, timezone import sqlite3 import threading import time @@ -270,11 +271,12 @@ def test_observation_collector_persists_amsc_runway_history(tmp_path): observation_store=db, ) + observed_at = datetime.now(timezone.utc).replace(microsecond=0).isoformat() record = ObservationRecord( source="amsc_awos", city="chengdu", value=28.4, - observed_at="2026-06-15T10:51:00+00:00", + observed_at=observed_at, observed_at_local="2026-06-15 18:51:00", station_code="ZUUU", station_name="Chengdu Shuangliu", @@ -313,11 +315,40 @@ def test_observation_collector_persists_amsc_runway_history(tmp_path): rows = db.get_runway_obs_recent("ZUUU", minutes=60) assert [row["runway"] for row in rows] == ["02L/20R", "02R/20L"] - assert rows[0]["otime_utc"] == "2026-06-15T10:51:00+00:00" + assert rows[0]["otime_utc"] == observed_at assert rows[0]["target_runway_max"] == 28.4 assert rows[0]["wind_dir"] == 130 +def test_runway_obs_recent_filters_by_observation_time_not_insert_time(tmp_path): + from src.database.db_manager import DBManager + + db = DBManager(str(tmp_path / "polyweather.db")) + now = datetime.now(timezone.utc).replace(microsecond=0) + stale_observed_at = (now - timedelta(hours=3)).isoformat() + fresh_observed_at = now.isoformat() + + db.append_runway_obs( + icao="ZUUU", + city="chengdu", + runway="02L/20R", + target_runway_max=24.0, + otime_utc=stale_observed_at, + ) + db.append_runway_obs( + icao="ZUUU", + city="chengdu", + runway="02R/20L", + target_runway_max=25.0, + otime_utc=fresh_observed_at, + ) + + rows = db.get_runway_obs_recent("ZUUU", minutes=60) + + assert [row["runway"] for row in rows] == ["02R/20L"] + assert rows[0]["otime_utc"] == fresh_observed_at + + def test_raw_observation_failure_preserves_last_success_and_increments_errors(tmp_path): from src.database.db_manager import DBManager diff --git a/tests/test_web_observability.py b/tests/test_web_observability.py index 61547b71..3ad46b85 100644 --- a/tests/test_web_observability.py +++ b/tests/test_web_observability.py @@ -1128,7 +1128,7 @@ def test_chart_scope_overlays_collector_runway_history_from_db(monkeypatch): def get_runway_obs_recent(self, icao, minutes=60): assert icao == "ZSPD" - assert minutes == 36 * 60 + assert minutes == 24 * 60 return [ { "runway": "35R/17L", @@ -1163,7 +1163,7 @@ def test_chart_data_force_refresh_overlays_collector_runway_history(monkeypatch) class FakeCache: def get_runway_obs_recent(self, icao, minutes=60): assert icao == "ZUUU" - assert minutes == 36 * 60 + assert minutes == 24 * 60 return [ { "runway": "02L/20R", diff --git a/web/analysis_service.py b/web/analysis_service.py index e48f28c1..14488f71 100644 --- a/web/analysis_service.py +++ b/web/analysis_service.py @@ -1761,7 +1761,7 @@ def _analyze( if isinstance(icao, str) and icao: try: from src.database.db_manager import DBManager - raw_runway_obs = DBManager().get_runway_obs_recent(icao, minutes=36 * 60) + raw_runway_obs = DBManager().get_runway_obs_recent(icao, minutes=24 * 60) for r in raw_runway_obs: rw = r.get("runway") if not rw: diff --git a/web/services/city_api.py b/web/services/city_api.py index 6303f90b..47ba71d5 100644 --- a/web/services/city_api.py +++ b/web/services/city_api.py @@ -330,7 +330,7 @@ def _overlay_cached_runway_history_from_db(city: str, payload: Dict[str, Any]) - return payload try: - rows = legacy_routes._CACHE_DB.get_runway_obs_recent(icao, minutes=36 * 60) + rows = legacy_routes._CACHE_DB.get_runway_obs_recent(icao, minutes=24 * 60) except Exception as exc: logger.debug("chart runway DB overlay skipped city={} icao={}: {}", normalized_city, icao, exc) return payload diff --git a/web/services/latest_observation_overlay.py b/web/services/latest_observation_overlay.py index 86dd8738..29e42d6d 100644 --- a/web/services/latest_observation_overlay.py +++ b/web/services/latest_observation_overlay.py @@ -327,7 +327,7 @@ def _append_amsc_runway_history_from_raw_store( if not callable(lister): return False try: - rows = lister("amsc_awos", city, minutes=36 * 60, limit=3000) + rows = lister("amsc_awos", city, minutes=24 * 60, limit=1500) except Exception as exc: logger.debug("latest AMSC raw history overlay skipped city={}: {}", city, exc) return False