diff --git a/CHANGELOG.md b/CHANGELOG.md index e7449b6c..28b9a14f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## 1.5.5 - 2026-04-27 +- 新增 Qingdao / 青岛城市,结算锚点接入 Wunderground 青岛胶东国际机场 `ZSQD` 历史页,并补齐别名、时区、预热、官方来源和前端地区归类 - 城市决策卡新增 AI 机场报文解读缓存说明:页面内存缓存保留 loading / 流式片段 / 最终结果,`localStorage` 保存最终成功 payload,后端 AI 缓存不再因 `local_time` 变化失效 - 城市决策卡兜底文案明确标记“快速证据模式”,避免在 DeepSeek 未完整返回时误写成“AI 机场报文解读正常” - 城市决策卡流式 AI 解读改为只请求 METAR/官方观测核心解读与判断依据,最高温中枢、模型一致性和风险清单由后端规则补齐,减少等待时间 diff --git a/README.md b/README.md index 51800e75..ed28d849 100644 --- a/README.md +++ b/README.md @@ -97,7 +97,7 @@ flowchart LR ## Monitored Cities (52) - Europe / Middle East / Africa: Ankara, Istanbul, Moscow, London, Paris, Munich, Milan, Warsaw, Madrid, Tel Aviv, Amsterdam, Helsinki, Lagos, Cape Town, Jeddah -- APAC: Seoul, Busan, Hong Kong, Lau Fau Shan, Taipei, Shanghai, Beijing, Wuhan, Chengdu, Chongqing, Shenzhen, Guangzhou, Singapore, Tokyo, Kuala Lumpur, Jakarta, Manila, Wellington +- APAC: Seoul, Busan, Hong Kong, Lau Fau Shan, Taipei, Shanghai, Beijing, Qingdao, Wuhan, Chengdu, Chongqing, Shenzhen, Guangzhou, Singapore, Tokyo, Kuala Lumpur, Jakarta, Manila, Wellington - Americas: Toronto, New York, Los Angeles, San Francisco, Aurora, Austin, Houston, Chicago, Dallas, Miami, Atlanta, Seattle, Mexico City, Buenos Aires, Sao Paulo, Panama City - South Asia: Lucknow, Karachi, Masroor Air Base diff --git a/frontend/lib/dashboard-home-copy.ts b/frontend/lib/dashboard-home-copy.ts index 53acd96e..feb6a039 100644 --- a/frontend/lib/dashboard-home-copy.ts +++ b/frontend/lib/dashboard-home-copy.ts @@ -41,6 +41,7 @@ const CITY_NAME_ZH: Record = { Panama: "巴拿马城", "Panama City": "巴拿马城", Paris: "巴黎", + Qingdao: "青岛", "Sao Paulo": "圣保罗", "São Paulo": "圣保罗", Seattle: "西雅图", @@ -91,6 +92,7 @@ const AIRPORT_NAME_ZH: Record = { "New York": "拉瓜迪亚机场", "Panama City": "托库门国际机场", Paris: "戴高乐机场", + Qingdao: "青岛胶东国际机场", "Sao Paulo": "瓜鲁柳斯国际机场", "São Paulo": "瓜鲁柳斯国际机场", Seattle: "西雅图-塔科马国际机场", diff --git a/frontend/lib/dashboard-official-sources.ts b/frontend/lib/dashboard-official-sources.ts index 88b4c935..3e4701c2 100644 --- a/frontend/lib/dashboard-official-sources.ts +++ b/frontend/lib/dashboard-official-sources.ts @@ -617,6 +617,23 @@ const CITY_SPECIFIC_SOURCES: Record = { kind: "metar", }, ], + qingdao: [ + { + label: "Wunderground ZSQD", + href: "https://www.wunderground.com/history/daily/cn/qingdao/ZSQD", + kind: "agency", + }, + { + label: "青岛胶东国际机场", + href: "https://www.qdairport.com/", + kind: "airport", + }, + { + label: "ZSQD METAR", + href: "https://aviationweather.gov/data/metar/?id=ZSQD&decoded=1&taf=1", + kind: "metar", + }, + ], "kuala lumpur": [ { label: "Wunderground WMKK", diff --git a/frontend/lib/polymarket-market-links.ts b/frontend/lib/polymarket-market-links.ts index 20a4d9d4..e3a2816c 100644 --- a/frontend/lib/polymarket-market-links.ts +++ b/frontend/lib/polymarket-market-links.ts @@ -25,6 +25,7 @@ const CITY_TO_MARKET_SLUG: Record = { munich: "munich", "new york": "nyc", paris: "paris", + qingdao: "qingdao", "san francisco": "san-francisco", "sao paulo": "sao-paulo", seattle: "seattle", diff --git a/frontend/lib/scan-market-focus.ts b/frontend/lib/scan-market-focus.ts index d02bf3be..856d2ce3 100644 --- a/frontend/lib/scan-market-focus.ts +++ b/frontend/lib/scan-market-focus.ts @@ -107,6 +107,7 @@ const CITY_REGION_FALLBACK: Record = { chongqing: "asia_pacific", shenzhen: "asia_pacific", guangzhou: "asia_pacific", + qingdao: "asia_pacific", beijing: "asia_pacific", wuhan: "asia_pacific", }; diff --git a/src/data_collection/city_registry.py b/src/data_collection/city_registry.py index 18181cec..4bbe1b5b 100644 --- a/src/data_collection/city_registry.py +++ b/src/data_collection/city_registry.py @@ -715,6 +715,24 @@ CITY_REGISTRY = { "distance_km": 28.0, "warning": "市场现按 Wunderground 广州白云国际机场站整度°C口径结算;珠三角海陆风与午后对流会让峰值兑现时间出现摇摆。", }, + "qingdao": { + "name": "Qingdao", + "lat": 36.3620, + "lon": 120.0882, + "icao": "ZSQD", + "settlement_source": "wunderground", + "settlement_station_code": "ZSQD", + "settlement_station_label": "Qingdao Jiaodong International Airport Station", + "settlement_url": "https://www.wunderground.com/history/daily/cn/qingdao/ZSQD", + "tz_offset": 28800, + "use_fahrenheit": False, + "is_major": True, + "risk_level": "medium", + "risk_emoji": "🟡", + "airport_name": "青岛胶东国际机场", + "distance_km": 39.0, + "warning": "市场现按 Wunderground 青岛胶东国际机场站整度°C口径结算;沿海海风、低云和海雾会影响日内升温效率。", + }, "beijing": { "name": "Beijing", "lat": 40.0801, @@ -918,12 +936,17 @@ ALIASES = { "ckg": "chongqing", "cq": "chongqing", "szx": "shenzhen", "sz": "shenzhen", "can": "guangzhou", "gz": "guangzhou", "zggg": "guangzhou", + "tao": "qingdao", "qdo": "qingdao", "qingdao": "qingdao", "zsqd": "qingdao", "pek": "beijing", "bjs": "beijing", "bj": "beijing", "wuh": "wuhan", "wh": "wuhan", "成都": "chengdu", "重庆": "chongqing", "深圳": "shenzhen", "广州": "guangzhou", + "青岛": "qingdao", + "青島": "qingdao", + "青岛胶东": "qingdao", + "青島膠東": "qingdao", "北京": "beijing", "武汉": "wuhan", } diff --git a/src/data_collection/city_time.py b/src/data_collection/city_time.py index 74f288e6..b41d82e7 100644 --- a/src/data_collection/city_time.py +++ b/src/data_collection/city_time.py @@ -50,6 +50,7 @@ CITY_TIME_ZONES = { "new york": "America/New_York", "panama city": "America/Panama", "paris": "Europe/Paris", + "qingdao": "Asia/Shanghai", "san francisco": "America/Los_Angeles", "sao paulo": "America/Sao_Paulo", "seattle": "America/Los_Angeles", diff --git a/src/data_collection/country_networks.py b/src/data_collection/country_networks.py index bc2c5c81..ceaefcfa 100644 --- a/src/data_collection/country_networks.py +++ b/src/data_collection/country_networks.py @@ -13,6 +13,7 @@ CHINA_CMA_CITIES = { "chengdu", "chongqing", "guangzhou", + "qingdao", "shanghai", "shenzhen", "wuhan", diff --git a/src/data_collection/weather_sources.py b/src/data_collection/weather_sources.py index 91047946..31bfa133 100644 --- a/src/data_collection/weather_sources.py +++ b/src/data_collection/weather_sources.py @@ -54,6 +54,7 @@ class WeatherDataCollector(OpenMeteoCacheMixin, SettlementSourceMixin, MetarSour "chengdu": ["ZUUU", "ZUTF"], "chongqing": ["ZUCK", "ZUPS"], "guangzhou": ["ZGGG", "ZGSZ"], + "qingdao": ["ZSQD"], "shenzhen": ["ZGSZ", "ZGGG"], "beijing": ["ZBAA", "ZBAD"], "wuhan": ["ZHHH", "ZHES"], @@ -600,6 +601,9 @@ class WeatherDataCollector(OpenMeteoCacheMixin, SettlementSourceMixin, MetarSour "重庆": "Chongqing", "shenzhen": "Shenzhen", "深圳": "Shenzhen", + "qingdao": "Qingdao", + "青岛": "Qingdao", + "青島": "Qingdao", "beijing": "Beijing", "北京": "Beijing", "wuhan": "Wuhan", diff --git a/src/utils/prewarm_dashboard.py b/src/utils/prewarm_dashboard.py index 65cf46d2..8d1cc779 100644 --- a/src/utils/prewarm_dashboard.py +++ b/src/utils/prewarm_dashboard.py @@ -21,6 +21,7 @@ DEFAULT_CITIES = [ "beijing", "shenzhen", "guangzhou", + "qingdao", "wuhan", "chengdu", "chongqing", diff --git a/tests/test_country_networks.py b/tests/test_country_networks.py index 35e40b4e..d255efc8 100644 --- a/tests/test_country_networks.py +++ b/tests/test_country_networks.py @@ -17,17 +17,21 @@ class _DummyMetarSource(MetarSourceMixin): metar_fast_cache_ttl_sec = 60 -def test_new_south_asia_city_registry_entries_are_wired(): +def test_new_city_registry_entries_are_wired(): assert CITY_REGISTRY["manila"]["settlement_source"] == "wunderground" assert CITY_REGISTRY["manila"]["settlement_station_code"] == "RPLL" assert CITY_REGISTRY["karachi"]["settlement_source"] == "wunderground" assert CITY_REGISTRY["karachi"]["settlement_station_code"] == "OPKC" + assert CITY_REGISTRY["qingdao"]["settlement_source"] == "wunderground" + assert CITY_REGISTRY["qingdao"]["settlement_station_code"] == "ZSQD" assert CITY_REGISTRY["masroor air base"]["icao"] == "OPMR" assert ALIASES["rpll"] == "manila" assert ALIASES["opkc"] == "karachi" + assert ALIASES["zsqd"] == "qingdao" assert ALIASES["opmr"] == "masroor air base" assert CITIES["manila"]["lat"] == CITY_REGISTRY["manila"]["lat"] assert CITIES["karachi"]["lon"] == CITY_REGISTRY["karachi"]["lon"] + assert CITIES["qingdao"]["lat"] == CITY_REGISTRY["qingdao"]["lat"] assert CITIES["masroor air base"]["settlement_source"] == "metar" @@ -38,6 +42,7 @@ def test_city_time_zone_mapping_covers_every_city_and_dst_offsets(): april = datetime(2026, 4, 19, 6, 30, tzinfo=timezone.utc) assert get_city_utc_offset_seconds("guangzhou", april) == 28800 + assert get_city_utc_offset_seconds("qingdao", april) == 28800 assert get_city_utc_offset_seconds("ankara", april) == 10800 assert get_city_utc_offset_seconds("london", april) == 3600 assert get_city_utc_offset_seconds("paris", april) == 7200 diff --git a/tests/test_web_observability.py b/tests/test_web_observability.py index 27605f0c..bf71ad71 100644 --- a/tests/test_web_observability.py +++ b/tests/test_web_observability.py @@ -590,6 +590,7 @@ def test_cities_endpoint_includes_new_wunderground_cities(): names = {item["name"] for item in payload["cities"]} assert { "busan", + "qingdao", "panama city", "kuala lumpur", "jakarta", diff --git a/web/routes.py b/web/routes.py index bcdb01d6..70fea668 100644 --- a/web/routes.py +++ b/web/routes.py @@ -93,6 +93,7 @@ DEFAULT_PREWARM_CITIES = [ "beijing", "shenzhen", "guangzhou", + "qingdao", "wuhan", "chengdu", "chongqing", @@ -116,6 +117,7 @@ ASIA_CORE_CITIES = [ "shanghai", "beijing", "guangzhou", + "qingdao", "shenzhen", "chongqing", "chengdu",