From 25122fed4ac68b5d4d22369e01f918e1c5c413ea Mon Sep 17 00:00:00 2001 From: "2569718930@qq.com" <2569718930@qq.com> Date: Sat, 18 Apr 2026 16:28:08 +0800 Subject: [PATCH] Use realtime METAR cluster for Moscow nearby maps --- src/data_collection/country_networks.py | 15 ++++++--------- src/data_collection/metar_sources.py | 1 + src/data_collection/russia_station_sources.py | 2 +- src/data_collection/weather_sources.py | 12 ++++++------ tests/test_country_networks.py | 10 +++++----- 5 files changed, 19 insertions(+), 21 deletions(-) diff --git a/src/data_collection/country_networks.py b/src/data_collection/country_networks.py index 8a61416e..fcaf6315 100644 --- a/src/data_collection/country_networks.py +++ b/src/data_collection/country_networks.py @@ -43,7 +43,7 @@ def _provider_code_for_city(city: str) -> str: if normalized in {"busan", "seoul"}: return "korea_kma" if normalized == "moscow": - return "russia_station_web" + return "russia_metar_cluster" if settlement_source == "hko": return "hongkong_hko" if settlement_source == "cwa": @@ -138,6 +138,7 @@ def _metar_cluster_rows(raw: Dict[str, Any]) -> List[Dict[str, Any]]: temp=row.get("temp"), lat=row.get("lat"), lon=row.get("lon"), + obs_time=row.get("obs_time"), source_code="metar_cluster", source_label="METAR cluster", is_official=False, @@ -530,22 +531,18 @@ class KoreaKmaNetworkProvider(CountryNetworkProvider): class RussiaStationWebNetworkProvider(CountryNetworkProvider): def __init__(self) -> None: - super().__init__("russia_station_web", "Russia station web") + super().__init__("russia_metar_cluster", "Moscow METAR network") def official_nearby_current(self, city: str, raw: Dict[str, Any]) -> List[Dict[str, Any]]: - rows = _ru_rows(raw, city) - if rows: - return rows return _metar_cluster_rows(raw) def official_network_status(self, city: str, raw: Dict[str, Any]) -> Dict[str, Any]: rows = self.official_nearby_current(city, raw) - has_ru = bool(_ru_rows(raw, city)) return { "provider_code": self.provider_code, "provider_label": self.provider_label, - "available": has_ru, - "mode": "official_web_crawl" if has_ru else ("fallback_metar_cluster" if rows else "reference_only"), + "available": bool(rows), + "mode": "realtime_metar_cluster" if rows else "reference_only", "row_count": len(rows), } @@ -574,7 +571,7 @@ def get_country_network_provider(city: str) -> CountryNetworkProvider: return TurkeyMgmNetworkProvider() if provider_code == "korea_kma": return KoreaKmaNetworkProvider() - if provider_code == "russia_station_web": + if provider_code == "russia_metar_cluster": return RussiaStationWebNetworkProvider() if provider_code == "japan_jma": return JapanJmaNetworkProvider() diff --git a/src/data_collection/metar_sources.py b/src/data_collection/metar_sources.py index 3ec7710b..23210051 100644 --- a/src/data_collection/metar_sources.py +++ b/src/data_collection/metar_sources.py @@ -445,6 +445,7 @@ class MetarSourceMixin: "wind_dir": obs.get("wdir"), "wind_speed": obs.get("wspd"), "wind_speed_kt": obs.get("wspd"), + "obs_time": obs.get("obsTime") or obs.get("reportTime"), "raw_metar": obs.get("rawOb"), } ) diff --git a/src/data_collection/russia_station_sources.py b/src/data_collection/russia_station_sources.py index 873186d7..f300d74f 100644 --- a/src/data_collection/russia_station_sources.py +++ b/src/data_collection/russia_station_sources.py @@ -221,7 +221,7 @@ class RussiaStationSourceMixin: obs_time = parsed.get("obs_time") max_stale_sec = max( 0, - int(getattr(self, "ru_station_max_stale_sec", 72 * 3600)), + int(getattr(self, "ru_station_max_stale_sec", 4 * 3600)), ) if obs_time and max_stale_sec > 0: try: diff --git a/src/data_collection/weather_sources.py b/src/data_collection/weather_sources.py index 77e9c7da..6fde2555 100644 --- a/src/data_collection/weather_sources.py +++ b/src/data_collection/weather_sources.py @@ -37,7 +37,7 @@ class WeatherDataCollector(OpenMeteoCacheMixin, SettlementSourceMixin, MetarSour CITY_METAR_CLUSTERS = { "buenos aires": ["SAEZ", "SABE", "SADP", "SADF", "SADL", "SADJ"], "istanbul": ["LTFM", "LTBA", "LTFJ"], - "moscow": ["UUWW", "UUEE", "UUDD"], + "moscow": ["UUWW", "UUEE", "UUDD", "UUBW", "UUMO"], "london": ["EGLL", "EGLC", "EGKK", "EGSS", "EGGW"], "new york": ["KLGA", "KJFK", "KEWR", "KTEB", "KHPN"], "los angeles": ["KLAX", "KBUR", "KLGB", "KSNA", "KVNY"], @@ -213,7 +213,7 @@ class WeatherDataCollector(OpenMeteoCacheMixin, SettlementSourceMixin, MetarSour os.getenv("RU_STATION_CACHE_TTL_SEC", "300") ) self.ru_station_max_stale_sec = int( - os.getenv("RU_STATION_MAX_STALE_SEC", str(72 * 3600)) + os.getenv("RU_STATION_MAX_STALE_SEC", str(4 * 3600)) ) self._ru_station_cache: Dict[str, Dict] = {} self._ru_station_cache_lock = threading.Lock() @@ -891,10 +891,10 @@ class WeatherDataCollector(OpenMeteoCacheMixin, SettlementSourceMixin, MetarSour ) if not official_rows: return - results["ru_official_nearby"] = official_rows - if "mgm_nearby" not in results: - results["mgm_nearby"] = official_rows - results["nearby_source"] = "ru_station_web" + # Pogodaiklimat station rows are SYNOP/archive-style reference observations, + # not realtime enough for the map. Keep them out of official_nearby/mgm_nearby + # so Moscow uses the live METAR cluster for nearby map temperatures. + results["ru_reference_nearby"] = official_rows def _attach_warsaw_official_nearby( self, results: Dict, use_fahrenheit: bool diff --git a/tests/test_country_networks.py b/tests/test_country_networks.py index 33134f56..a3039d95 100644 --- a/tests/test_country_networks.py +++ b/tests/test_country_networks.py @@ -140,7 +140,7 @@ def test_hko_provider_marks_explicit_official_station_as_anchor(): assert snapshot["official_nearby"][0]["station_code"] == "LFS" -def test_russia_provider_prefers_official_web_rows_when_available(): +def test_moscow_provider_uses_realtime_metar_cluster_not_station_archive_rows(): raw = { "metar": { "observation_time": "2026-04-06T10:00:00.000Z", @@ -171,11 +171,11 @@ def test_russia_provider_prefers_official_web_rows_when_available(): snapshot = build_country_network_snapshot("moscow", raw) - assert snapshot["provider_code"] == "russia_station_web" + assert snapshot["provider_code"] == "russia_metar_cluster" assert snapshot["official_network_status"]["available"] is True - assert snapshot["official_network_status"]["mode"] == "official_web_crawl" - assert snapshot["official_nearby"][0]["source_code"] == "ru_station_web" - assert snapshot["official_nearby"][0]["is_official"] is True + assert snapshot["official_network_status"]["mode"] == "realtime_metar_cluster" + assert snapshot["official_nearby"][0]["source_code"] == "metar_cluster" + assert snapshot["official_nearby"][0]["is_airport_station"] is True def test_city_detail_payload_exposes_airport_and_official_network_layers():