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