feat: Implement multi-source weather data collection with caching, rate limiting, and disk persistence.
This commit is contained in:
+93
-72
@@ -1,76 +1,97 @@
|
|||||||
# Weather API Configuration
|
|
||||||
weather:
|
weather:
|
||||||
timeout: 30
|
timeout: 30
|
||||||
|
|
||||||
# Target Cities
|
|
||||||
cities:
|
cities:
|
||||||
- id: "london"
|
- id: london
|
||||||
city: "London"
|
city: London
|
||||||
country: "UK"
|
country: UK
|
||||||
latitude: 51.5074
|
latitude: 51.5074
|
||||||
longitude: -0.1278
|
longitude: -0.1278
|
||||||
- id: "paris"
|
- id: paris
|
||||||
city: "Paris"
|
city: Paris
|
||||||
country: "France"
|
country: France
|
||||||
latitude: 48.8566
|
latitude: 48.8566
|
||||||
longitude: 2.3522
|
longitude: 2.3522
|
||||||
- id: "ankara"
|
- id: ankara
|
||||||
city: "Ankara"
|
city: Ankara
|
||||||
country: "Turkey"
|
country: Turkey
|
||||||
latitude: 39.9334
|
latitude: 39.9334
|
||||||
longitude: 32.8597
|
longitude: 32.8597
|
||||||
- id: "new_york"
|
- id: new_york
|
||||||
city: "New York"
|
city: New York
|
||||||
country: "USA"
|
country: USA
|
||||||
latitude: 40.7128
|
latitude: 40.7128
|
||||||
longitude: -74.0060
|
longitude: -74.006
|
||||||
- id: "chicago"
|
- id: chicago
|
||||||
city: "Chicago"
|
city: Chicago
|
||||||
country: "USA"
|
country: USA
|
||||||
latitude: 41.8781
|
latitude: 41.8781
|
||||||
longitude: -87.6298
|
longitude: -87.6298
|
||||||
- id: "lucknow"
|
- id: lucknow
|
||||||
city: "Lucknow"
|
city: Lucknow
|
||||||
country: "India"
|
country: India
|
||||||
latitude: 26.7606
|
latitude: 26.7606
|
||||||
longitude: 80.8893
|
longitude: 80.8893
|
||||||
- id: "sao paulo"
|
- id: sao paulo
|
||||||
city: "São Paulo"
|
city: São Paulo
|
||||||
country: "Brazil"
|
country: Brazil
|
||||||
latitude: -23.4356
|
latitude: -23.4356
|
||||||
longitude: -46.4731
|
longitude: -46.4731
|
||||||
- id: "munich"
|
- id: munich
|
||||||
city: "Munich"
|
city: Munich
|
||||||
country: "Germany"
|
country: Germany
|
||||||
latitude: 48.3538
|
latitude: 48.3538
|
||||||
longitude: 11.7861
|
longitude: 11.7861
|
||||||
- id: "hong_kong"
|
- id: hong_kong
|
||||||
city: "Hong Kong"
|
city: Hong Kong
|
||||||
country: "China"
|
country: China
|
||||||
latitude: 22.3080
|
latitude: 22.308
|
||||||
longitude: 113.9185
|
longitude: 113.9185
|
||||||
- id: "shanghai"
|
- id: shanghai
|
||||||
city: "Shanghai"
|
city: Shanghai
|
||||||
country: "China"
|
country: China
|
||||||
latitude: 31.1434
|
latitude: 31.1434
|
||||||
longitude: 121.8052
|
longitude: 121.8052
|
||||||
- id: "singapore"
|
- id: singapore
|
||||||
city: "Singapore"
|
city: Singapore
|
||||||
country: "Singapore"
|
country: Singapore
|
||||||
latitude: 1.3644
|
latitude: 1.3644
|
||||||
longitude: 103.9915
|
longitude: 103.9915
|
||||||
- id: "tokyo"
|
- id: tokyo
|
||||||
city: "Tokyo"
|
city: Tokyo
|
||||||
country: "Japan"
|
country: Japan
|
||||||
latitude: 35.5523
|
latitude: 35.5523
|
||||||
longitude: 139.7798
|
longitude: 139.7798
|
||||||
- id: "tel_aviv"
|
- id: tel_aviv
|
||||||
city: "Tel Aviv"
|
city: Tel Aviv
|
||||||
country: "Israel"
|
country: Israel
|
||||||
latitude: 32.0114
|
latitude: 32.0114
|
||||||
longitude: 34.8867
|
longitude: 34.8867
|
||||||
# Logging
|
- id: chengdu
|
||||||
|
city: Chengdu
|
||||||
|
country: China
|
||||||
|
latitude: 30.5785
|
||||||
|
longitude: 103.9471
|
||||||
|
- id: chongqing
|
||||||
|
city: Chongqing
|
||||||
|
country: China
|
||||||
|
latitude: 29.7196
|
||||||
|
longitude: 106.6416
|
||||||
|
- id: shenzhen
|
||||||
|
city: Shenzhen
|
||||||
|
country: China
|
||||||
|
latitude: 22.6393
|
||||||
|
longitude: 113.8107
|
||||||
|
- id: beijing
|
||||||
|
city: Beijing
|
||||||
|
country: China
|
||||||
|
latitude: 40.0801
|
||||||
|
longitude: 116.5846
|
||||||
|
- id: wuhan
|
||||||
|
city: Wuhan
|
||||||
|
country: China
|
||||||
|
latitude: 30.7838
|
||||||
|
longitude: 114.2081
|
||||||
logging:
|
logging:
|
||||||
level: "INFO"
|
level: INFO
|
||||||
rotation: "10 MB"
|
rotation: 10 MB
|
||||||
retention: "10 days"
|
retention: 10 days
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import os
|
|||||||
import json
|
import json
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
import requests
|
import requests
|
||||||
from src.analysis.settlement_rounding import wu_round
|
from src.analysis.settlement_rounding import wu_round, apply_city_settlement, is_exact_settlement_city
|
||||||
|
|
||||||
# Cross-platform file locking
|
# Cross-platform file locking
|
||||||
import sys
|
import sys
|
||||||
@@ -442,8 +442,8 @@ def get_deb_accuracy(city_name):
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
total += 1
|
total += 1
|
||||||
deb_wu = wu_round(deb_pred)
|
deb_wu = apply_city_settlement(city_name, deb_pred)
|
||||||
actual_wu = wu_round(actual)
|
actual_wu = apply_city_settlement(city_name, actual)
|
||||||
if deb_wu == actual_wu:
|
if deb_wu == actual_wu:
|
||||||
hits += 1
|
hits += 1
|
||||||
errors.append(abs(deb_pred - actual))
|
errors.append(abs(deb_pred - actual))
|
||||||
@@ -507,13 +507,13 @@ def get_mu_accuracy(city_name):
|
|||||||
|
|
||||||
total += 1
|
total += 1
|
||||||
mu_errors.append(abs(mu_val - actual))
|
mu_errors.append(abs(mu_val - actual))
|
||||||
if wu_round(mu_val) == wu_round(actual):
|
if apply_city_settlement(city_name, mu_val) == apply_city_settlement(city_name, actual):
|
||||||
mu_hits += 1
|
mu_hits += 1
|
||||||
|
|
||||||
# Brier Score from probability snapshot
|
# Brier Score from probability snapshot
|
||||||
prob_snap = record.get("prob_snapshot", [])
|
prob_snap = record.get("prob_snapshot", [])
|
||||||
if prob_snap:
|
if prob_snap:
|
||||||
actual_wu = wu_round(actual)
|
actual_wu = apply_city_settlement(city_name, actual)
|
||||||
bs = 0.0
|
bs = 0.0
|
||||||
for entry in prob_snap:
|
for entry in prob_snap:
|
||||||
predicted_p = entry.get("p", 0)
|
predicted_p = entry.get("p", 0)
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import re
|
|||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
from typing import Any, Dict, List, Optional, Tuple
|
from typing import Any, Dict, List, Optional, Tuple
|
||||||
|
|
||||||
from src.analysis.settlement_rounding import wu_round
|
from src.analysis.settlement_rounding import wu_round, apply_city_settlement, is_exact_settlement_city
|
||||||
|
|
||||||
|
|
||||||
def _sf(v: Any) -> Optional[float]:
|
def _sf(v: Any) -> Optional[float]:
|
||||||
@@ -716,7 +716,7 @@ def _extract_market_snapshot(city_weather: Dict[str, Any]) -> Dict[str, Any]:
|
|||||||
market_url = f"https://polymarket.com/market/{slug}"
|
market_url = f"https://polymarket.com/market/{slug}"
|
||||||
|
|
||||||
anchor_today_high_c, anchor_model = _extract_multi_model_anchor_high_c(city_weather)
|
anchor_today_high_c, anchor_model = _extract_multi_model_anchor_high_c(city_weather)
|
||||||
anchor_settlement = wu_round(anchor_today_high_c)
|
anchor_settlement = apply_city_settlement(city, anchor_today_high_c)
|
||||||
forecast_bucket = _pick_bucket_for_forecast(
|
forecast_bucket = _pick_bucket_for_forecast(
|
||||||
rows=all_bucket_rows,
|
rows=all_bucket_rows,
|
||||||
forecast_settlement=anchor_settlement,
|
forecast_settlement=anchor_settlement,
|
||||||
|
|||||||
@@ -18,3 +18,24 @@ def wu_round(value: Optional[Number]) -> Optional[int]:
|
|||||||
return int(math.floor(x + 0.5))
|
return int(math.floor(x + 0.5))
|
||||||
return int(math.ceil(x - 0.5))
|
return int(math.ceil(x - 0.5))
|
||||||
|
|
||||||
|
|
||||||
|
def is_exact_settlement_city(city: str) -> bool:
|
||||||
|
"""是否为不四舍五入的精确结算城市"""
|
||||||
|
if not city:
|
||||||
|
return False
|
||||||
|
c = str(city).lower().strip()
|
||||||
|
return c in ["hong kong", "hk", "taipei", "tpe", "臺北", "台北", "香港"]
|
||||||
|
|
||||||
|
|
||||||
|
def apply_city_settlement(city: str, value: Optional[Number]) -> Optional[int]:
|
||||||
|
"""
|
||||||
|
根据城市返回最终的结算值:
|
||||||
|
- 香港/台北: 向下取整 (e.g. 28.9 -> 28)
|
||||||
|
- 其他: WU 规则四舍五入
|
||||||
|
"""
|
||||||
|
if value is None:
|
||||||
|
return None
|
||||||
|
if is_exact_settlement_city(city):
|
||||||
|
return int(math.floor(float(value)))
|
||||||
|
return wu_round(value)
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ from src.analysis.deb_algorithm import (
|
|||||||
update_daily_record,
|
update_daily_record,
|
||||||
_is_excluded_model_name,
|
_is_excluded_model_name,
|
||||||
)
|
)
|
||||||
from src.analysis.settlement_rounding import wu_round
|
from src.analysis.settlement_rounding import wu_round, apply_city_settlement, is_exact_settlement_city
|
||||||
from src.data_collection.city_registry import CITY_REGISTRY
|
from src.data_collection.city_registry import CITY_REGISTRY
|
||||||
from src.data_collection.city_risk_profiles import get_city_risk_profile
|
from src.data_collection.city_risk_profiles import get_city_risk_profile
|
||||||
|
|
||||||
@@ -429,7 +429,7 @@ def analyze_weather_trend(
|
|||||||
forecast_miss_deg = 0.0
|
forecast_miss_deg = 0.0
|
||||||
|
|
||||||
if is_dead_market:
|
if is_dead_market:
|
||||||
settled_wu = wu_round(max_so_far) if max_so_far is not None else 0
|
settled_wu = apply_city_settlement(city_name, max_so_far) if max_so_far is not None else 0
|
||||||
dead_msg = (
|
dead_msg = (
|
||||||
f"🎲 <b>结算预测</b>:已锁定 {settled_wu}{temp_symbol} "
|
f"🎲 <b>结算预测</b>:已锁定 {settled_wu}{temp_symbol} "
|
||||||
f"({settlement_source_label} 死盘确认)"
|
f"({settlement_source_label} 死盘确认)"
|
||||||
@@ -481,7 +481,7 @@ def analyze_weather_trend(
|
|||||||
|
|
||||||
# Probability Engine
|
# Probability Engine
|
||||||
probs_result = calculate_prob_distribution(
|
probs_result = calculate_prob_distribution(
|
||||||
mu, sigma, max_so_far, temp_symbol
|
mu, sigma, max_so_far, temp_symbol, city_name
|
||||||
)
|
)
|
||||||
mu = probs_result.get("mu", mu)
|
mu = probs_result.get("mu", mu)
|
||||||
probabilities = probs_result.get("probabilities", [])
|
probabilities = probs_result.get("probabilities", [])
|
||||||
@@ -514,7 +514,7 @@ def analyze_weather_trend(
|
|||||||
|
|
||||||
# === Settlement boundary ===
|
# === Settlement boundary ===
|
||||||
if max_so_far is not None:
|
if max_so_far is not None:
|
||||||
settled = wu_round(max_so_far)
|
settled = apply_city_settlement(city_name, max_so_far)
|
||||||
fractional = max_so_far - int(max_so_far)
|
fractional = max_so_far - int(max_so_far)
|
||||||
dist_to_boundary = abs(fractional - 0.5)
|
dist_to_boundary = abs(fractional - 0.5)
|
||||||
if dist_to_boundary <= 0.3:
|
if dist_to_boundary <= 0.3:
|
||||||
@@ -585,7 +585,7 @@ def analyze_weather_trend(
|
|||||||
if max_so_far is not None:
|
if max_so_far is not None:
|
||||||
ai_features.append(
|
ai_features.append(
|
||||||
f"🏔️ 今日实测最高温: {max_so_far}{temp_symbol} "
|
f"🏔️ 今日实测最高温: {max_so_far}{temp_symbol} "
|
||||||
f"({settlement_source_label}结算={wu_round(max_so_far)}{temp_symbol})。"
|
f"({settlement_source_label}结算={apply_city_settlement(city_name, max_so_far)}{temp_symbol})。"
|
||||||
)
|
)
|
||||||
if city_name:
|
if city_name:
|
||||||
_profile = get_city_risk_profile(city_name)
|
_profile = get_city_risk_profile(city_name)
|
||||||
@@ -634,7 +634,7 @@ def analyze_weather_trend(
|
|||||||
for t, p in sorted_probs[:4]
|
for t, p in sorted_probs[:4]
|
||||||
]
|
]
|
||||||
elif is_dead_market and max_so_far is not None:
|
elif is_dead_market and max_so_far is not None:
|
||||||
_prob_list = [{"value": wu_round(max_so_far), "probability": 1.0}]
|
_prob_list = [{"value": apply_city_settlement(city_name, max_so_far), "probability": 1.0}]
|
||||||
|
|
||||||
update_daily_record(
|
update_daily_record(
|
||||||
city_name,
|
city_name,
|
||||||
@@ -672,14 +672,14 @@ def analyze_weather_trend(
|
|||||||
"forecast_miss_deg": forecast_miss_deg,
|
"forecast_miss_deg": forecast_miss_deg,
|
||||||
"max_so_far": max_so_far,
|
"max_so_far": max_so_far,
|
||||||
"cur_temp": cur_temp,
|
"cur_temp": cur_temp,
|
||||||
"wu_settle": wu_round(max_so_far) if max_so_far is not None else None,
|
"wu_settle": apply_city_settlement(city_name, max_so_far) if max_so_far is not None else None,
|
||||||
}
|
}
|
||||||
display_str = "\n".join(insights) if insights else ""
|
display_str = "\n".join(insights) if insights else ""
|
||||||
return display_str, "\n".join(ai_features), structured
|
return display_str, "\n".join(ai_features), structured
|
||||||
|
|
||||||
|
|
||||||
def calculate_prob_distribution(
|
def calculate_prob_distribution(
|
||||||
mu: float, sigma: float, max_so_far: Optional[float], temp_symbol: str
|
mu: float, sigma: float, max_so_far: Optional[float], temp_symbol: str, city_name: str = ""
|
||||||
) -> Dict[str, Any]:
|
) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Generalized Gaussian probability distribution calculation.
|
Generalized Gaussian probability distribution calculation.
|
||||||
@@ -691,17 +691,26 @@ def calculate_prob_distribution(
|
|||||||
# 0.5 * (1 + erf( (x-m)/(s*sqrt(2)) ))
|
# 0.5 * (1 + erf( (x-m)/(s*sqrt(2)) ))
|
||||||
return 0.5 * (1 + math.erf((x - m) / (sigma * math.sqrt(2))))
|
return 0.5 * (1 + math.erf((x - m) / (sigma * math.sqrt(2))))
|
||||||
|
|
||||||
min_possible_wu = wu_round(max_so_far) if max_so_far is not None else -999
|
min_possible_wu = apply_city_settlement(city_name, max_so_far) if max_so_far is not None else -999
|
||||||
probs = {}
|
probs = {}
|
||||||
|
|
||||||
# Range: mu +/- 3 sigma or at least +/- 2 degrees
|
# Range: mu +/- 3 sigma or at least +/- 2 degrees
|
||||||
search_range = max(2, int(sigma * 2.5))
|
search_range = max(2, int(sigma * 2.5))
|
||||||
target_mu = wu_round(mu)
|
is_exact = is_exact_settlement_city(city_name)
|
||||||
|
target_mu = apply_city_settlement(city_name, mu)
|
||||||
|
if is_exact:
|
||||||
|
target_mu = int(math.floor(mu))
|
||||||
|
|
||||||
for n in range(target_mu - search_range, target_mu + search_range + 1):
|
for n in range(target_mu - search_range, target_mu + search_range + 1):
|
||||||
if n < min_possible_wu:
|
if n < min_possible_wu:
|
||||||
continue
|
continue
|
||||||
p = _norm_cdf(n + 0.5, mu, sigma) - _norm_cdf(n - 0.5, mu, sigma)
|
if is_exact:
|
||||||
|
# 向下取整的概率区间为 [n, n + 1)
|
||||||
|
p = _norm_cdf(n + 1.0, mu, sigma) - _norm_cdf(n, mu, sigma)
|
||||||
|
else:
|
||||||
|
# 常规四舍五入的概率区间为 [n - 0.5, n + 0.5)
|
||||||
|
p = _norm_cdf(n + 0.5, mu, sigma) - _norm_cdf(n - 0.5, mu, sigma)
|
||||||
|
|
||||||
if p > 0.01:
|
if p > 0.01:
|
||||||
probs[n] = p
|
probs[n] = p
|
||||||
|
|
||||||
@@ -713,9 +722,10 @@ def calculate_prob_distribution(
|
|||||||
norm_probs = {k: v / total_p for k, v in probs.items()}
|
norm_probs = {k: v / total_p for k, v in probs.items()}
|
||||||
sorted_probs = sorted(norm_probs.items(), key=lambda x: x[1], reverse=True)
|
sorted_probs = sorted(norm_probs.items(), key=lambda x: x[1], reverse=True)
|
||||||
for t, p in sorted_probs[:4]:
|
for t, p in sorted_probs[:4]:
|
||||||
|
rng_str = f"[{t}.0~{t+1}.0)" if is_exact else f"[{t-0.5}~{t+0.5})"
|
||||||
probabilities.append({
|
probabilities.append({
|
||||||
"value": int(t),
|
"value": int(t),
|
||||||
"range": f"[{t-0.5}~{t+0.5})",
|
"range": rng_str,
|
||||||
"probability": round(p, 3)
|
"probability": round(p, 3)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -357,6 +357,77 @@ CITY_REGISTRY = {
|
|||||||
"distance_km": 13.0,
|
"distance_km": 13.0,
|
||||||
"warning": "机场位于东北侧开阔区,午后混合层增强时与核心城区体感温度可能出现偏差。",
|
"warning": "机场位于东北侧开阔区,午后混合层增强时与核心城区体感温度可能出现偏差。",
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"chengdu": {
|
||||||
|
"name": "Chengdu",
|
||||||
|
"lat": 30.5785,
|
||||||
|
"lon": 103.9471,
|
||||||
|
"icao": "ZUUU",
|
||||||
|
"tz_offset": 28800,
|
||||||
|
"use_fahrenheit": False,
|
||||||
|
"is_major": True,
|
||||||
|
"risk_level": "medium",
|
||||||
|
"risk_emoji": "🟡",
|
||||||
|
"airport_name": "成都双流国际机场",
|
||||||
|
"distance_km": 16.0,
|
||||||
|
"warning": "盆地地形,夜间降温较慢,多云雾影响日照升温。",
|
||||||
|
},
|
||||||
|
"chongqing": {
|
||||||
|
"name": "Chongqing",
|
||||||
|
"lat": 29.7196,
|
||||||
|
"lon": 106.6416,
|
||||||
|
"icao": "ZUCK",
|
||||||
|
"tz_offset": 28800,
|
||||||
|
"use_fahrenheit": False,
|
||||||
|
"is_major": True,
|
||||||
|
"risk_level": "high",
|
||||||
|
"risk_emoji": "🔴",
|
||||||
|
"airport_name": "重庆江北国际机场",
|
||||||
|
"distance_km": 19.0,
|
||||||
|
"warning": "四大火炉之一,夏季持续高温,夜间温度偏高,湿度大。",
|
||||||
|
},
|
||||||
|
"shenzhen": {
|
||||||
|
"name": "Shenzhen",
|
||||||
|
"lat": 22.6393,
|
||||||
|
"lon": 113.8107,
|
||||||
|
"icao": "ZGSZ",
|
||||||
|
"tz_offset": 28800,
|
||||||
|
"use_fahrenheit": False,
|
||||||
|
"is_major": True,
|
||||||
|
"risk_level": "medium",
|
||||||
|
"risk_emoji": "🟡",
|
||||||
|
"airport_name": "深圳宝安国际机场",
|
||||||
|
"distance_km": 32.0,
|
||||||
|
"warning": "近海受海洋调节,热岛效应强,日夜温差较小。",
|
||||||
|
},
|
||||||
|
"beijing": {
|
||||||
|
"name": "Beijing",
|
||||||
|
"lat": 40.0801,
|
||||||
|
"lon": 116.5846,
|
||||||
|
"icao": "ZBAA",
|
||||||
|
"tz_offset": 28800,
|
||||||
|
"use_fahrenheit": False,
|
||||||
|
"is_major": True,
|
||||||
|
"risk_level": "medium",
|
||||||
|
"risk_emoji": "🟡",
|
||||||
|
"airport_name": "北京首都国际机场",
|
||||||
|
"distance_km": 25.0,
|
||||||
|
"warning": "北方内陆,夏季干热,冬季寒冷,春秋易受风沙影响。",
|
||||||
|
},
|
||||||
|
"wuhan": {
|
||||||
|
"name": "Wuhan",
|
||||||
|
"lat": 30.7838,
|
||||||
|
"lon": 114.2081,
|
||||||
|
"icao": "ZHHH",
|
||||||
|
"tz_offset": 28800,
|
||||||
|
"use_fahrenheit": False,
|
||||||
|
"is_major": True,
|
||||||
|
"risk_level": "high",
|
||||||
|
"risk_emoji": "🔴",
|
||||||
|
"airport_name": "武汉天河国际机场",
|
||||||
|
"distance_km": 26.0,
|
||||||
|
"warning": "江汉平原,水汽充足,夏季常现长时闷热高温。",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
ALIASES = {
|
ALIASES = {
|
||||||
@@ -406,4 +477,14 @@ ALIASES = {
|
|||||||
"華沙": "warsaw",
|
"華沙": "warsaw",
|
||||||
"马德里": "madrid",
|
"马德里": "madrid",
|
||||||
"馬德里": "madrid",
|
"馬德里": "madrid",
|
||||||
|
"ctu": "chengdu", "cd": "chengdu",
|
||||||
|
"ckg": "chongqing", "cq": "chongqing",
|
||||||
|
"szx": "shenzhen", "sz": "shenzhen",
|
||||||
|
"pek": "beijing", "bjs": "beijing", "bj": "beijing",
|
||||||
|
"wuh": "wuhan", "wh": "wuhan",
|
||||||
|
"成都": "chengdu",
|
||||||
|
"重庆": "chongqing",
|
||||||
|
"深圳": "shenzhen",
|
||||||
|
"北京": "beijing",
|
||||||
|
"武汉": "wuhan",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,6 +34,11 @@ class WeatherDataCollector:
|
|||||||
"seoul": ["RKSI", "RKSS"],
|
"seoul": ["RKSI", "RKSS"],
|
||||||
"hong kong": ["VHHH", "VMMC", "ZGSZ"],
|
"hong kong": ["VHHH", "VMMC", "ZGSZ"],
|
||||||
"taipei": ["RCSS", "RCTP"],
|
"taipei": ["RCSS", "RCTP"],
|
||||||
|
"chengdu": ["ZUUU", "ZUTF"],
|
||||||
|
"chongqing": ["ZUCK", "ZUPS"],
|
||||||
|
"shenzhen": ["ZGSZ", "ZGGG"],
|
||||||
|
"beijing": ["ZBAA", "ZBAD"],
|
||||||
|
"wuhan": ["ZHHH", "ZHES"],
|
||||||
"shanghai": ["ZSPD", "ZSSS", "ZSNB", "ZSHC"],
|
"shanghai": ["ZSPD", "ZSSS", "ZSNB", "ZSHC"],
|
||||||
"singapore": ["WSSS", "WSAP", "WMKK"],
|
"singapore": ["WSSS", "WSAP", "WMKK"],
|
||||||
"tokyo": ["RJTT", "RJAA", "RJAH", "RJTJ"],
|
"tokyo": ["RJTT", "RJAA", "RJAH", "RJTJ"],
|
||||||
@@ -1960,6 +1965,16 @@ class WeatherDataCollector:
|
|||||||
"taipei": "Taipei",
|
"taipei": "Taipei",
|
||||||
"台北": "Taipei",
|
"台北": "Taipei",
|
||||||
"臺北": "Taipei",
|
"臺北": "Taipei",
|
||||||
|
"chengdu": "Chengdu",
|
||||||
|
"成都": "Chengdu",
|
||||||
|
"chongqing": "Chongqing",
|
||||||
|
"重庆": "Chongqing",
|
||||||
|
"shenzhen": "Shenzhen",
|
||||||
|
"深圳": "Shenzhen",
|
||||||
|
"beijing": "Beijing",
|
||||||
|
"北京": "Beijing",
|
||||||
|
"wuhan": "Wuhan",
|
||||||
|
"武汉": "Wuhan",
|
||||||
"shanghai": "Shanghai",
|
"shanghai": "Shanghai",
|
||||||
"上海": "Shanghai",
|
"上海": "Shanghai",
|
||||||
"singapore": "Singapore",
|
"singapore": "Singapore",
|
||||||
|
|||||||
Reference in New Issue
Block a user