From ee535e9efac47af0c7de19eac8343a1905895233 Mon Sep 17 00:00:00 2001
From: "2569718930@qq.com" <2569718930@qq.com>
Date: Sun, 1 Mar 2026 15:53:29 +0800
Subject: [PATCH] feat: DEB accuracy tracking - save predictions, compute WU
hit rate and MAE
---
bot_listener.py | 12 ++++++--
src/analysis/deb_algorithm.py | 58 +++++++++++++++++++++++++++++++++--
2 files changed, 65 insertions(+), 5 deletions(-)
diff --git a/bot_listener.py b/bot_listener.py
index 4b176e4b..e8dbe8bc 100644
--- a/bot_listener.py
+++ b/bot_listener.py
@@ -78,12 +78,20 @@ def analyze_weather_trend(weather_data, temp_symbol, city_name=None):
if city_name and current_forecasts:
blended_high, weight_info = calculate_dynamic_weights(city_name, current_forecasts)
if blended_high is not None:
- insights.insert(0, f"🧬 DEB 融合预测:{blended_high}{temp_symbol} ({weight_info})")
+ # 展示准确率(如果有历史数据)
+ from src.analysis.deb_algorithm import get_deb_accuracy
+ accuracy = get_deb_accuracy(city_name)
+ acc_tag = ""
+ if accuracy:
+ hit_rate, mae, total_days, details_str = accuracy
+ acc_tag = f"\n 📈 DEB 历史战绩:{details_str}"
+
+ insights.insert(0, f"🧬 DEB 融合预测:{blended_high}{temp_symbol} ({weight_info}){acc_tag}")
ai_features.append(f"🧬 DEB系统已通过历史偏差矫正算出期待点是: {blended_high}{temp_symbol}。")
# 顺便把今天的预测记录下来供之后回测用
try:
- update_daily_record(city_name, local_date_str, current_forecasts, max_so_far)
+ update_daily_record(city_name, local_date_str, current_forecasts, max_so_far, deb_prediction=blended_high)
except:
pass
diff --git a/src/analysis/deb_algorithm.py b/src/analysis/deb_algorithm.py
index db544569..315866e6 100644
--- a/src/analysis/deb_algorithm.py
+++ b/src/analysis/deb_algorithm.py
@@ -45,11 +45,12 @@ def save_history(filepath, data):
except Exception as e:
print(f"Error saving history: {e}")
-def update_daily_record(city_name, date_str, forecasts, actual_high):
+def update_daily_record(city_name, date_str, forecasts, actual_high, deb_prediction=None):
"""
保存/更新某城市某天的各个模型预报与最终实测值
forecasts: dict, 例如 {"ECMWF": 28.5, "GFS": 30.0, ...}
actual_high: float, 最终实测最高温
+ deb_prediction: float, DEB 融合预测值(用于准确率追踪)
"""
project_root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
history_file = os.path.join(project_root, 'data', 'daily_records.json')
@@ -61,14 +62,15 @@ def update_daily_record(city_name, date_str, forecasts, actual_high):
if date_str not in data[city_name]:
data[city_name][date_str] = {}
- # 避免无意义的频繁磁盘写入:如果数据没有变化,直接返回
+ # 避免无意义的频繁磁盘写入
old_actual = data[city_name][date_str].get('actual_high')
if old_actual == actual_high and data[city_name][date_str].get('forecasts') == forecasts:
return
data[city_name][date_str]['forecasts'] = forecasts
- # 只要仍在更新或者已经结束,都记录最新高点
data[city_name][date_str]['actual_high'] = actual_high
+ if deb_prediction is not None:
+ data[city_name][date_str]['deb_prediction'] = deb_prediction
# 自动清理:只保留最近 14 天的记录(DEB 只用 7 天,14 天留足余量)
cutoff = (datetime.now() - timedelta(days=14)).strftime("%Y-%m-%d")
@@ -162,3 +164,53 @@ def calculate_dynamic_weights(city_name, current_forecasts, lookback_days=7):
return round(blended_high, 1), " | ".join(weight_str_parts)
+
+def get_deb_accuracy(city_name):
+ """
+ 计算 DEB 融合预测的历史准确率
+ 返回: (hit_rate, mae, total_days, details_str) 或 None
+ - hit_rate: WU 结算命中率 (DEB 四舍五入 == 实测四舍五入)
+ - mae: 平均绝对误差
+ - total_days: 有效天数
+ - details_str: 格式化的展示字符串
+ """
+ project_root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
+ history_file = os.path.join(project_root, 'data', 'daily_records.json')
+ data = load_history(history_file)
+
+ if city_name not in data:
+ return None
+
+ city_data = data[city_name]
+ today_str = datetime.now().strftime("%Y-%m-%d")
+
+ hits = 0
+ total = 0
+ errors = []
+
+ for date_str in sorted(city_data.keys()):
+ if date_str == today_str:
+ continue # 跳过今天,还没结算
+ record = city_data[date_str]
+ deb_pred = record.get('deb_prediction')
+ actual = record.get('actual_high')
+
+ if deb_pred is None or actual is None:
+ continue
+
+ total += 1
+ deb_wu = round(deb_pred)
+ actual_wu = round(actual)
+ if deb_wu == actual_wu:
+ hits += 1
+ errors.append(abs(deb_pred - actual))
+
+ if total == 0:
+ return None
+
+ hit_rate = hits / total * 100
+ mae = sum(errors) / len(errors)
+
+ details_str = f"过去{total}天 WU命中 {hits}/{total} ({hit_rate:.0f}%) | MAE: {mae:.1f}°"
+
+ return hit_rate, mae, total, details_str