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:
+33
-3
@@ -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(
|
||||
[
|
||||
|
||||
@@ -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
@@ -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) ──
|
||||
|
||||
Reference in New Issue
Block a user