From 4f3cd3290ff7310d967f4477287ef136a0029d6e Mon Sep 17 00:00:00 2001 From: "2569718930@qq.com" <2569718930@qq.com> Date: Mon, 23 Feb 2026 22:19:20 +0800 Subject: [PATCH] feat: introduce WeatherDataCollector for fetching multi-source weather data from OpenWeatherMap, Visual Crossing, and NOAA METAR. --- bot_listener.py | 57 ++++++++++++++++++++++++++ src/data_collection/weather_sources.py | 40 ++++++++++-------- 2 files changed, 81 insertions(+), 16 deletions(-) diff --git a/bot_listener.py b/bot_listener.py index 060e7afd..5ea7c9f2 100644 --- a/bot_listener.py +++ b/bot_listener.py @@ -517,6 +517,63 @@ def analyze_weather_trend(weather_data, temp_symbol): else: insights.append(f"⏰ 入场时机:不建议 — {factors_str}。不确定性大,等更多数据。") + # === 明日预览:当今日峰值已过,自动显示明天的模型共识 === + if is_peak_passed: + tomorrow_forecasts = {} + tomorrow_date = None + + # 从 multi_model 的 daily_forecasts 中取明天数据 + mm_daily = multi_model.get("daily_forecasts", {}) + mm_dates = multi_model.get("dates", []) + + if len(mm_dates) >= 2: + tomorrow_date = mm_dates[1] + tomorrow_forecasts = mm_daily.get(tomorrow_date, {}) + + # 明天的 Open-Meteo 预报 + tomorrow_om = None + om_max_list = daily.get("temperature_2m_max", []) + om_dates = daily.get("time", []) + if len(om_max_list) >= 2: + tomorrow_om = om_max_list[1] + if tomorrow_date is None and len(om_dates) >= 2: + tomorrow_date = om_dates[1] + + if tomorrow_date and (tomorrow_forecasts or tomorrow_om is not None): + # 格式化日期 (02-24) + date_short = tomorrow_date[5:] if tomorrow_date else "明天" + + preview_parts = [f"\n📋 明日预览 ({date_short})"] + + if tomorrow_om is not None: + preview_parts.append(f"📊 Open-Meteo 预报: {tomorrow_om}{temp_symbol}") + + if tomorrow_forecasts: + t_values = list(tomorrow_forecasts.values()) + t_max = max(t_values) + t_min = min(t_values) + t_spread = t_max - t_min + + is_f = (temp_symbol == "°F") + tight = 1.5 if is_f else 0.8 + mid = 3.0 if is_f else 1.5 + + parts = " | ".join([f"{name} {val}{temp_symbol}" for name, val in tomorrow_forecasts.items()]) + + if t_spread <= tight: + preview_parts.append(f"🎯 模型共识:高 — {parts},极差 {t_spread:.1f}°") + elif t_spread <= mid: + preview_parts.append(f"⚖️ 模型共识:中 — {parts},极差 {t_spread:.1f}°") + else: + highest = max(tomorrow_forecasts.items(), key=lambda x: x[1]) + lowest = min(tomorrow_forecasts.items(), key=lambda x: x[1]) + preview_parts.append( + f"⚠️ 模型共识:低 — {parts},极差 {t_spread:.1f}°!" + f"{highest[0]} 最高 vs {lowest[0]} 最低" + ) + + insights.extend(preview_parts) + if not insights: return "" diff --git a/src/data_collection/weather_sources.py b/src/data_collection/weather_sources.py index 4aa5e73b..9d272e78 100644 --- a/src/data_collection/weather_sources.py +++ b/src/data_collection/weather_sources.py @@ -653,6 +653,8 @@ class WeatherDataCollector: - ICON (德国气象局 DWD) - GEM (加拿大气象局) - JMA (日本气象厅) + + 返回 3 天的预报数据,支持今日+明日共识分析 """ try: url = "https://api.open-meteo.com/v1/forecast" @@ -663,7 +665,7 @@ class WeatherDataCollector: "daily": "temperature_2m_max", "models": models, "timezone": "auto", - "forecast_days": 1, + "forecast_days": 3, "_t": int(time.time()), } if use_fahrenheit: @@ -678,13 +680,8 @@ class WeatherDataCollector: response.raise_for_status() data = response.json() - # Open-Meteo 多模型返回格式: - # "daily": { - # "temperature_2m_max_ecmwf_ifs025": [12.3], - # "temperature_2m_max_gfs_seamless": [11.8], - # ... - # } daily = data.get("daily", {}) + dates = daily.get("time", []) model_labels = { "ecmwf_ifs025": "ECMWF", @@ -694,23 +691,34 @@ class WeatherDataCollector: "jma_seamless": "JMA", } - forecasts = {} - for model_key, label in model_labels.items(): - key = f"temperature_2m_max_{model_key}" - values = daily.get(key, []) - if values and values[0] is not None: - forecasts[label] = round(values[0], 1) + # 按天提取每个模型的预报 + daily_forecasts = {} # {"2026-02-23": {"ECMWF": 7.9, "GFS": 6.5, ...}, ...} + for day_idx, date_str in enumerate(dates): + day_data = {} + for model_key, label in model_labels.items(): + key = f"temperature_2m_max_{model_key}" + values = daily.get(key, []) + if day_idx < len(values) and values[day_idx] is not None: + day_data[label] = round(values[day_idx], 1) + if day_data: + daily_forecasts[date_str] = day_data - if not forecasts: + if not daily_forecasts: logger.warning("Multi-model: 无有效模型数据") return None + # 今天的预报 (向后兼容) + today_date = dates[0] if dates else None + forecasts = daily_forecasts.get(today_date, {}) + labels_str = ", ".join([f"{k}={v}" for k, v in forecasts.items()]) - logger.info(f"🔬 Multi-model ({len(forecasts)}个): {labels_str}") + logger.info(f"🔬 Multi-model ({len(forecasts)}个, {len(daily_forecasts)}天): {labels_str}") return { "source": "multi_model", - "forecasts": forecasts, # {"ECMWF": 12.3, "GFS": 11.8, ...} + "forecasts": forecasts, # 今天 {"ECMWF": 12.3, "GFS": 11.8, ...} (向后兼容) + "daily_forecasts": daily_forecasts, # 按天 {"2026-02-23": {...}, "2026-02-24": {...}} + "dates": dates, "unit": "fahrenheit" if use_fahrenheit else "celsius", } except Exception as e: