Add Qingdao to the tradable city network
Qingdao needs the same airport-settlement path as the other Wunderground-backed APAC cities, so the registry, aliases, timezone, prewarm, official links, market focus, and tests now point to ZSQD / Qingdao Jiaodong International Airport. Constraint: User supplied Wunderground Qingdao/ZSQD settlement URL. Rejected: Add a partial registry-only entry | it would show in APIs without frontend links, prewarm coverage, or alias support. Confidence: high Scope-risk: narrow Reversibility: clean Tested: pytest tests/test_country_networks.py tests/test_web_observability.py::test_cities_endpoint_includes_new_wunderground_cities -q Tested: npm run build Not-tested: Live Wunderground fetch for ZSQD in production.
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
|
||||
## 1.5.5 - 2026-04-27
|
||||
|
||||
- 新增 Qingdao / 青岛城市,结算锚点接入 Wunderground 青岛胶东国际机场 `ZSQD` 历史页,并补齐别名、时区、预热、官方来源和前端地区归类
|
||||
- 城市决策卡新增 AI 机场报文解读缓存说明:页面内存缓存保留 loading / 流式片段 / 最终结果,`localStorage` 保存最终成功 payload,后端 AI 缓存不再因 `local_time` 变化失效
|
||||
- 城市决策卡兜底文案明确标记“快速证据模式”,避免在 DeepSeek 未完整返回时误写成“AI 机场报文解读正常”
|
||||
- 城市决策卡流式 AI 解读改为只请求 METAR/官方观测核心解读与判断依据,最高温中枢、模型一致性和风险清单由后端规则补齐,减少等待时间
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -41,6 +41,7 @@ const CITY_NAME_ZH: Record<string, string> = {
|
||||
Panama: "巴拿马城",
|
||||
"Panama City": "巴拿马城",
|
||||
Paris: "巴黎",
|
||||
Qingdao: "青岛",
|
||||
"Sao Paulo": "圣保罗",
|
||||
"São Paulo": "圣保罗",
|
||||
Seattle: "西雅图",
|
||||
@@ -91,6 +92,7 @@ const AIRPORT_NAME_ZH: Record<string, string> = {
|
||||
"New York": "拉瓜迪亚机场",
|
||||
"Panama City": "托库门国际机场",
|
||||
Paris: "戴高乐机场",
|
||||
Qingdao: "青岛胶东国际机场",
|
||||
"Sao Paulo": "瓜鲁柳斯国际机场",
|
||||
"São Paulo": "瓜鲁柳斯国际机场",
|
||||
Seattle: "西雅图-塔科马国际机场",
|
||||
|
||||
@@ -617,6 +617,23 @@ const CITY_SPECIFIC_SOURCES: Record<string, OfficialSourceLink[]> = {
|
||||
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",
|
||||
|
||||
@@ -25,6 +25,7 @@ const CITY_TO_MARKET_SLUG: Record<string, string> = {
|
||||
munich: "munich",
|
||||
"new york": "nyc",
|
||||
paris: "paris",
|
||||
qingdao: "qingdao",
|
||||
"san francisco": "san-francisco",
|
||||
"sao paulo": "sao-paulo",
|
||||
seattle: "seattle",
|
||||
|
||||
@@ -107,6 +107,7 @@ const CITY_REGION_FALLBACK: Record<string, MarketRegionKey> = {
|
||||
chongqing: "asia_pacific",
|
||||
shenzhen: "asia_pacific",
|
||||
guangzhou: "asia_pacific",
|
||||
qingdao: "asia_pacific",
|
||||
beijing: "asia_pacific",
|
||||
wuhan: "asia_pacific",
|
||||
};
|
||||
|
||||
@@ -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",
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -13,6 +13,7 @@ CHINA_CMA_CITIES = {
|
||||
"chengdu",
|
||||
"chongqing",
|
||||
"guangzhou",
|
||||
"qingdao",
|
||||
"shanghai",
|
||||
"shenzhen",
|
||||
"wuhan",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -21,6 +21,7 @@ DEFAULT_CITIES = [
|
||||
"beijing",
|
||||
"shenzhen",
|
||||
"guangzhou",
|
||||
"qingdao",
|
||||
"wuhan",
|
||||
"chengdu",
|
||||
"chongqing",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user