feat: Add multi-source weather data collection with a new Flask API and refine the bot's forecast display logic.

This commit is contained in:
2569718930@qq.com
2026-03-11 06:33:25 +08:00
parent 5d943f2aae
commit f7e7e0ea20
3 changed files with 88 additions and 3 deletions
+33 -3
View File
@@ -464,17 +464,21 @@ def start_bot():
# 今天对比
today_t = _sf(max_temps[0]) if max_temps else None
fallback_source = None
metar_only_fallback = False
if today_t is None:
for source_name, candidate in (
("MB", mb_high),
("NWS", nws_high),
("MGM", mgm_high),
("METAR", metar_max_so_far),
):
if candidate is not None:
today_t = candidate
fallback_source = source_name
break
if today_t is None and metar_max_so_far is not None:
# Last-resort display only: do not treat METAR as a forecast source
today_t = metar_max_so_far
metar_only_fallback = True
today_t_display = (
f"{today_t:.1f}" if isinstance(today_t, (int, float)) else "N/A"
)
@@ -511,6 +515,10 @@ def start_bot():
)
if fallback_source and fallback_source not in sources:
sources.append(fallback_source)
if metar_only_fallback:
if not sources:
sources = ["Model unavailable"]
comp_parts.append(f"METAR实测回退: {metar_max_so_far:.1f}{temp_symbol}")
if not sources:
sources = ["N/A"]
@@ -534,9 +542,9 @@ def start_bot():
)
# 明后天
mgm_daily = mgm.get("daily_forecasts", {}) or {}
if len(dates) > 1:
future_forecasts = []
mgm_daily = mgm.get("daily_forecasts", {}) or {}
for d, t in zip(dates[1:], max_temps[1:]):
# 检查 MGM 是否有该日期的预报
mgm_f = mgm_daily.get(d)
@@ -547,6 +555,26 @@ def start_bot():
else:
future_forecasts.append(f"{d[5:]}: {t}{temp_symbol}")
msg_lines.append("📅 " + " | ".join(future_forecasts))
elif mgm_daily:
# Open-Meteo missing: still show next 2 days from MGM daily forecast
from datetime import datetime, timezone, timedelta
local_now = datetime.now(timezone.utc).astimezone(
timezone(timedelta(seconds=int(fallback_utc_offset)))
)
today_local = local_now.strftime("%Y-%m-%d")
future_forecasts = []
for d in sorted(mgm_daily.keys()):
if d <= today_local:
continue
t = mgm_daily.get(d)
if t is None:
continue
future_forecasts.append(f"{d[5:]}: {t}{temp_symbol}")
if len(future_forecasts) >= 2:
break
if future_forecasts:
msg_lines.append("📅 " + " | ".join(future_forecasts))
# --- 3.5 日出日落 + 日照时长 ---
sunrises = daily.get("sunrise", [])
@@ -831,7 +859,9 @@ def start_bot():
# 构建更全的背景数据给 AI
# 补充多模型分歧
mm = weather_data.get("multi_model", {})
mm = weather_data.get("multi_model", {}) or {}
if not isinstance(mm, dict):
mm = {}
if mm.get("forecasts"):
mm_str = " | ".join(
[
+45
View File
@@ -1810,6 +1810,12 @@ class WeatherDataCollector:
# 严格判断是否为美国市场(必须完全匹配列表或缩写)
use_fahrenheit = city_lower in us_cities
# Turkish cities: keep MGM model fallback alive when Open-Meteo is rate-limited.
turkish_provinces = {
"ankara": ("17128", "Ankara"), # settlement reference: Esenboğa airport
"istanbul": ("17060", "Istanbul"),
}
if use_fahrenheit:
logger.info(f"🌡️ {city} 使用华氏度 (°F)")
else:
@@ -1907,6 +1913,35 @@ class WeatherDataCollector:
)
if metar_data:
results["metar"] = metar_data
# Turkish fallback: keep MGM forecasts and nearby stations available
if city_lower in turkish_provinces:
istno, province = turkish_provinces[city_lower]
mgm_data = self.fetch_from_mgm(istno)
if istno == "17128":
mgm_city_center = self.fetch_from_mgm("17130")
if mgm_city_center and mgm_data:
mgm_data["today_high"] = mgm_city_center.get("today_high")
mgm_data["daily_forecasts"] = mgm_city_center.get(
"daily_forecasts"
)
if mgm_data:
results["mgm"] = mgm_data
nearby = self.fetch_mgm_nearby_stations(
province, root_ist_no=istno
)
if nearby:
results["mgm_nearby"] = nearby
# Global nearby fallback from METAR clusters
if city_lower in self.CITY_METAR_CLUSTERS and "mgm_nearby" not in results:
cluster_icaos = self.CITY_METAR_CLUSTERS[city_lower]
cluster_data = self.fetch_metar_nearby_cluster(
cluster_icaos, use_fahrenheit=use_fahrenheit
)
if cluster_data:
results["mgm_nearby"] = cluster_data
if city_lower in self.METEOBLUE_PRIORITY_CITIES:
mb_data = self.fetch_from_meteoblue(
lat,
@@ -1920,6 +1955,16 @@ class WeatherDataCollector:
nws_data = self.fetch_nws(lat, lon)
if nws_data:
results["nws"] = nws_data
# Still try ensemble / multi-model from stale cache while OM is cooling down
ens_data = self.fetch_ensemble(lat, lon, use_fahrenheit=use_fahrenheit)
if ens_data:
results["ensemble"] = ens_data
mm_data = self.fetch_multi_model(
lat, lon, use_fahrenheit=use_fahrenheit
)
if mm_data:
results["multi_model"] = mm_data
else:
# 降级方案(无经纬度)
metar_data = self.fetch_metar(city, use_fahrenheit=use_fahrenheit)
+10
View File
@@ -111,6 +111,16 @@ def _analyze(city: str, force_refresh: bool = False) -> Dict[str, Any]:
mgm = raw.get("mgm") or {}
ens_raw = raw.get("ensemble", {})
mm = raw.get("multi_model", {})
if not isinstance(om, dict):
om = {}
if not isinstance(metar, dict):
metar = {}
if not isinstance(mgm, dict):
mgm = {}
if not isinstance(ens_raw, dict):
ens_raw = {}
if not isinstance(mm, dict):
mm = {}
risk = CITY_RISK_PROFILES.get(city, {})
# ── 2. Current conditions (METAR primary, MGM fallback) ──