From 343c5c9c2fadf5f6ba4d3cc3ed97c8a10e8d73e1 Mon Sep 17 00:00:00 2001 From: "2569718930@qq.com" <2569718930@qq.com> Date: Sun, 5 Apr 2026 01:23:10 +0800 Subject: [PATCH] Add new Wunderground-backed cities and source links --- frontend/lib/dashboard-official-sources.ts | 102 +++++++++++++++++ src/data_collection/city_registry.py | 122 +++++++++++++++++++++ src/data_collection/weather_sources.py | 24 ++++ tests/test_web_observability.py | 15 +++ 4 files changed, 263 insertions(+) diff --git a/frontend/lib/dashboard-official-sources.ts b/frontend/lib/dashboard-official-sources.ts index 12adbdb3..2996850c 100644 --- a/frontend/lib/dashboard-official-sources.ts +++ b/frontend/lib/dashboard-official-sources.ts @@ -92,6 +92,23 @@ const CITY_SPECIFIC_SOURCES: Record = { kind: "metar", }, ], + busan: [ + { + label: "Wunderground RKPK", + href: "https://www.wunderground.com/history/daily/kr/busan/RKPK", + kind: "agency", + }, + { + label: "金海国际机场", + href: "https://www.airport.co.kr/gimhaeeng/index.do", + kind: "airport", + }, + { + label: "RKPK METAR", + href: "https://aviationweather.gov/data/metar/?id=RKPK&decoded=1&taf=1", + kind: "metar", + }, + ], istanbul: [ { label: "MGM", @@ -526,6 +543,91 @@ const CITY_SPECIFIC_SOURCES: Record = { kind: "metar", }, ], + "kuala lumpur": [ + { + label: "Wunderground WMKK", + href: "https://www.wunderground.com/history/daily/my/sepang-district/WMKK", + kind: "agency", + }, + { + label: "吉隆坡国际机场", + href: "https://airports.malaysiaairports.com.my/klia", + kind: "airport", + }, + { + label: "WMKK METAR", + href: "https://aviationweather.gov/data/metar/?id=WMKK&decoded=1&taf=1", + kind: "metar", + }, + ], + jakarta: [ + { + label: "Wunderground WIHH", + href: "https://www.wunderground.com/history/daily/id/jakarta/WIHH", + kind: "agency", + }, + { + label: "Halim Perdanakusuma Airport", + href: "https://www.angkasapura2.co.id/en/airport/read/HLP", + kind: "airport", + }, + { + label: "WIHH METAR", + href: "https://aviationweather.gov/data/metar/?id=WIHH&decoded=1&taf=1", + kind: "metar", + }, + ], + helsinki: [ + { + label: "Wunderground EFHK", + href: "https://www.wunderground.com/history/daily/fi/vantaa/EFHK", + kind: "agency", + }, + { + label: "Helsinki Airport", + href: "https://www.finavia.fi/en/airports/helsinki-airport", + kind: "airport", + }, + { + label: "EFHK METAR", + href: "https://aviationweather.gov/data/metar/?id=EFHK&decoded=1&taf=1", + kind: "metar", + }, + ], + amsterdam: [ + { + label: "Wunderground EHAM", + href: "https://www.wunderground.com/history/daily/nl/schiphol/EHAM", + kind: "agency", + }, + { + label: "Amsterdam Airport Schiphol", + href: "https://www.schiphol.nl/en/", + kind: "airport", + }, + { + label: "EHAM METAR", + href: "https://aviationweather.gov/data/metar/?id=EHAM&decoded=1&taf=1", + kind: "metar", + }, + ], + "panama city": [ + { + label: "Wunderground MPMG", + href: "https://www.wunderground.com/history/daily/pa/panama-city/MPMG", + kind: "agency", + }, + { + label: "Marcos A. Gelabert Airport", + href: "https://www.aeropuertos.net/aeropuerto-internacional-marcos-a-gelabert/", + kind: "airport", + }, + { + label: "MPMG METAR", + href: "https://aviationweather.gov/data/metar/?id=MPMG&decoded=1&taf=1", + kind: "metar", + }, + ], beijing: [ { label: "中国天气网", diff --git a/src/data_collection/city_registry.py b/src/data_collection/city_registry.py index 08cdb0fb..3a473b57 100644 --- a/src/data_collection/city_registry.py +++ b/src/data_collection/city_registry.py @@ -92,6 +92,24 @@ CITY_REGISTRY = { "distance_km": 48.8, "warning": "距离太远,海洋性vs大陆性气候差异大。", }, + "busan": { + "name": "Busan", + "lat": 35.1795, + "lon": 128.9382, + "icao": "RKPK", + "settlement_source": "wunderground", + "settlement_station_code": "RKPK", + "settlement_station_label": "Busan/Gimhae International Airport Station", + "settlement_url": "https://www.wunderground.com/history/daily/kr/busan/RKPK", + "tz_offset": 32400, + "use_fahrenheit": False, + "is_major": True, + "risk_level": "medium", + "risk_emoji": "🟡", + "airport_name": "金海国际机场", + "distance_km": 14.0, + "warning": "市场现按 Wunderground 金海国际机场站整度°C口径结算;海风与沿海云量会影响午后峰值。", + }, "hong kong": { "name": "Hong Kong", "lat": 22.3019, @@ -175,6 +193,42 @@ CITY_REGISTRY = { "distance_km": 17.5, "warning": "全年温度振幅较小,主要受午后阵雨时段影响。", }, + "kuala lumpur": { + "name": "Kuala Lumpur", + "lat": 2.7456, + "lon": 101.7072, + "icao": "WMKK", + "settlement_source": "wunderground", + "settlement_station_code": "WMKK", + "settlement_station_label": "Kuala Lumpur International Airport Station", + "settlement_url": "https://www.wunderground.com/history/daily/my/sepang-district/WMKK", + "tz_offset": 28800, + "use_fahrenheit": False, + "is_major": True, + "risk_level": "medium", + "risk_emoji": "🟡", + "airport_name": "吉隆坡国际机场", + "distance_km": 45.0, + "warning": "市场现按 Wunderground 吉隆坡国际机场站整度°C口径结算;午后对流和热带湿热会让峰值窗口偏短。", + }, + "jakarta": { + "name": "Jakarta", + "lat": -6.2666, + "lon": 106.8910, + "icao": "WIHH", + "settlement_source": "wunderground", + "settlement_station_code": "WIHH", + "settlement_station_label": "Jakarta/Halim Perdanakusuma Airport Station", + "settlement_url": "https://www.wunderground.com/history/daily/id/jakarta/WIHH", + "tz_offset": 25200, + "use_fahrenheit": False, + "is_major": True, + "risk_level": "medium", + "risk_emoji": "🟡", + "airport_name": "Halim Perdanakusuma Airport", + "distance_km": 12.0, + "warning": "市场现按 Wunderground Halim 机场站整度°C口径结算;热带午后阵雨常在峰值前后快速压制气温。", + }, "tokyo": { "name": "Tokyo", "lat": 35.5523, @@ -486,6 +540,42 @@ CITY_REGISTRY = { "distance_km": 10.0, "warning": "平原地形下辐射冷却显著,清晨与夜间温度回落可能偏快。", }, + "helsinki": { + "name": "Helsinki", + "lat": 60.3172, + "lon": 24.9633, + "icao": "EFHK", + "settlement_source": "wunderground", + "settlement_station_code": "EFHK", + "settlement_station_label": "Helsinki Airport Station", + "settlement_url": "https://www.wunderground.com/history/daily/fi/vantaa/EFHK", + "tz_offset": 7200, + "use_fahrenheit": False, + "is_major": True, + "risk_level": "medium", + "risk_emoji": "🟡", + "airport_name": "赫尔辛基机场", + "distance_km": 17.0, + "warning": "市场现按 Wunderground 赫尔辛基机场站整度°C口径结算;高纬度日照变化快,冷平流会明显影响日峰值。", + }, + "amsterdam": { + "name": "Amsterdam", + "lat": 52.3086, + "lon": 4.7639, + "icao": "EHAM", + "settlement_source": "wunderground", + "settlement_station_code": "EHAM", + "settlement_station_label": "Amsterdam Airport Schiphol Station", + "settlement_url": "https://www.wunderground.com/history/daily/nl/schiphol/EHAM", + "tz_offset": 3600, + "use_fahrenheit": False, + "is_major": True, + "risk_level": "medium", + "risk_emoji": "🟡", + "airport_name": "阿姆斯特丹史基浦机场", + "distance_km": 17.0, + "warning": "市场现按 Wunderground 史基浦机场站整度°C口径结算;北海海风和平流层云容易限制午后升温。", + }, "madrid": { "name": "Madrid", "lat": 40.4722, @@ -576,6 +666,24 @@ CITY_REGISTRY = { "distance_km": 26.0, "warning": "江汉平原,水汽充足,夏季常现长时闷热高温。", }, + "panama city": { + "name": "Panama City", + "lat": 8.9733, + "lon": -79.5556, + "icao": "MPMG", + "settlement_source": "wunderground", + "settlement_station_code": "MPMG", + "settlement_station_label": "Panama City/Marcos A. Gelabert Airport Station", + "settlement_url": "https://www.wunderground.com/history/daily/pa/panama-city/MPMG", + "tz_offset": -18000, + "use_fahrenheit": False, + "is_major": True, + "risk_level": "medium", + "risk_emoji": "🟡", + "airport_name": "Marcos A. Gelabert Airport", + "distance_km": 6.0, + "warning": "市场现按 Wunderground Marcos A. Gelabert 机场站整度°C口径结算;热带对流常让峰值在午后被快速截断。", + }, } ALIASES = { @@ -593,8 +701,14 @@ ALIASES = { "seo": "seoul", "hkg": "hong kong", "hk": "hong kong", "lfs": "lau fau shan", "laufaushan": "lau fau shan", "tpe": "taipei", "tp": "taipei", "taipei": "taipei", + "pus": "busan", "bus": "busan", "rkpk": "busan", "sha": "shanghai", "sh": "shanghai", "sin": "singapore", "sg": "singapore", "tok": "tokyo", "tyo": "tokyo", + "kul": "kuala lumpur", "wmkk": "kuala lumpur", "sepang": "kuala lumpur", + "jkt": "jakarta", "wihh": "jakarta", "halim": "jakarta", + "hel": "helsinki", "efhk": "helsinki", "vantaa": "helsinki", + "ams": "amsterdam", "eham": "amsterdam", "schiphol": "amsterdam", + "pty": "panama city", "mpmg": "panama city", "panamacity": "panama city", "tlv": "tel aviv", "telaviv": "tel aviv", "ba": "buenos aires", "wel": "wellington", "luc": "lucknow", "sp": "sao paulo", "mun": "munich", @@ -626,8 +740,12 @@ ALIASES = { "流浮山": "lau fau shan", "台北": "taipei", "臺北": "taipei", + "釜山": "busan", "上海": "shanghai", "新加坡": "singapore", + "吉隆坡": "kuala lumpur", + "雅加达": "jakarta", + "雅加達": "jakarta", "东京": "tokyo", "東京": "tokyo", "特拉维夫": "tel aviv", @@ -640,6 +758,10 @@ ALIASES = { "米蘭": "milan", "华沙": "warsaw", "華沙": "warsaw", + "赫尔辛基": "helsinki", + "赫爾辛基": "helsinki", + "阿姆斯特丹": "amsterdam", + "巴拿马城": "panama city", "马德里": "madrid", "馬德里": "madrid", "ctu": "chengdu", "cd": "chengdu", diff --git a/src/data_collection/weather_sources.py b/src/data_collection/weather_sources.py index 27d110ab..a60b4433 100644 --- a/src/data_collection/weather_sources.py +++ b/src/data_collection/weather_sources.py @@ -44,6 +44,7 @@ class WeatherDataCollector(OpenMeteoCacheMixin, SettlementSourceMixin, MetarSour "mexico city": ["MMMX", "MMSM", "MMTO"], "paris": ["LFPG", "LFPO", "LFPB"], "seoul": ["RKSI", "RKSS"], + "busan": ["RKPK", "RKSS"], "hong kong": ["VHHH", "VMMC", "ZGSZ"], "taipei": ["RCSS", "RCTP"], "chengdu": ["ZUUU", "ZUTF"], @@ -53,11 +54,16 @@ class WeatherDataCollector(OpenMeteoCacheMixin, SettlementSourceMixin, MetarSour "wuhan": ["ZHHH", "ZHES"], "shanghai": ["ZSPD", "ZSSS", "ZSNB", "ZSHC"], "singapore": ["WSSS", "WSAP", "WMKK"], + "kuala lumpur": ["WMKK", "WMSA"], + "jakarta": ["WIHH", "WIII"], "tokyo": ["RJTT", "RJAA", "RJAH", "RJTJ"], "tel aviv": ["LLBG"], "milan": ["LIMC", "LIML", "LIME", "LIPO"], "toronto": ["CYYZ", "CYTZ", "CYKF"], "warsaw": ["EPWA", "EPMO", "EPLL"], + "helsinki": ["EFHK", "EETN"], + "amsterdam": ["EHAM", "EHRD"], + "panama city": ["MPMG", "MPTO"], "madrid": ["LEMD", "LETO", "LEGT"], "chicago": ["KORD", "KMDW", "KPWK", "KDPA"], "dallas": ["KDAL", "KDFW", "KADS", "KGKY"], @@ -445,6 +451,9 @@ class WeatherDataCollector(OpenMeteoCacheMixin, SettlementSourceMixin, MetarSour "伊斯坦布尔": "Istanbul", "seoul": "Seoul", "首尔": "Seoul", + "busan": "Busan", + "pusan": "Busan", + "釜山": "Busan", "hong kong": "Hong Kong", "hong kong international airport": "Hong Kong", "香港": "Hong Kong", @@ -472,6 +481,21 @@ class WeatherDataCollector(OpenMeteoCacheMixin, SettlementSourceMixin, MetarSour "上海": "Shanghai", "singapore": "Singapore", "新加坡": "Singapore", + "kuala lumpur": "Kuala Lumpur", + "sepang": "Kuala Lumpur", + "吉隆坡": "Kuala Lumpur", + "jakarta": "Jakarta", + "雅加达": "Jakarta", + "雅加達": "Jakarta", + "helsinki": "Helsinki", + "vantaa": "Helsinki", + "赫尔辛基": "Helsinki", + "赫爾辛基": "Helsinki", + "amsterdam": "Amsterdam", + "schiphol": "Amsterdam", + "阿姆斯特丹": "Amsterdam", + "panama city": "Panama City", + "巴拿马城": "Panama City", "tokyo": "Tokyo", "东京": "Tokyo", "東京": "Tokyo", diff --git a/tests/test_web_observability.py b/tests/test_web_observability.py index c1a59a4f..1d614714 100644 --- a/tests/test_web_observability.py +++ b/tests/test_web_observability.py @@ -54,6 +54,21 @@ def test_cities_endpoint_uses_denver_display_name_for_aurora_market(): assert aurora["display_name"] == "Denver" +def test_cities_endpoint_includes_new_wunderground_cities(): + response = client.get("/api/cities") + assert response.status_code == 200 + payload = response.json() + names = {item["name"] for item in payload["cities"]} + assert { + "busan", + "panama city", + "kuala lumpur", + "jakarta", + "helsinki", + "amsterdam", + }.issubset(names) + + def test_payment_runtime_endpoint_returns_shape(): response = client.get('/api/payments/runtime') assert response.status_code == 200