From a684586cb4ba55194edb646d98e65a45ae920776 Mon Sep 17 00:00:00 2001 From: "2569718930@qq.com" <2569718930@qq.com> Date: Wed, 6 May 2026 18:24:15 +0800 Subject: [PATCH] =?UTF-8?q?=E7=BB=9F=E4=B8=80=20DEB=20=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E6=BA=90=E4=B8=BA=E5=8D=95=E4=B8=80=E8=AE=A1=E7=AE=97=E8=B7=AF?= =?UTF-8?q?=E5=BE=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit getModelView 优先使用根级 detail.deb.prediction 保证与 AI 证据面板一致, _build_city_detail_payload 补上缺失的 deb 和 multi_model_daily 字段, _analyze_summary 补上 LGBM 增强步骤使其与 _analyze 产出相同的 DEB 值。 Constraint: 三个入口 (panel/full/summary) 的 DEB 必须经过同一套 calculate_dynamic_weights + LGBM 重算流程 Tested: ruff + tsc 全过 --- frontend/lib/model-utils.ts | 5 +++-- web/analysis_service.py | 45 +++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 2 deletions(-) diff --git a/frontend/lib/model-utils.ts b/frontend/lib/model-utils.ts index acf10bd1..ae679d76 100644 --- a/frontend/lib/model-utils.ts +++ b/frontend/lib/model-utils.ts @@ -37,15 +37,16 @@ export function getProbabilityView(detail: CityDetail, targetDate?: string | nul export function getModelView(detail: CityDetail, targetDate?: string | null) { const date = targetDate || detail.local_date; const daily = detail.multi_model_daily?.[date]; + const deb = detail.deb?.prediction ?? daily?.deb?.prediction ?? null; if (daily) { return { - deb: daily.deb?.prediction ?? null, + deb, models: daily.models || {}, }; } return { - deb: detail.deb?.prediction ?? null, + deb, models: detail.multi_model || {}, }; } diff --git a/web/analysis_service.py b/web/analysis_service.py index ae015891..445f192a 100644 --- a/web/analysis_service.py +++ b/web/analysis_service.py @@ -2792,6 +2792,49 @@ def _analyze_summary(city: str, force_refresh: bool = False) -> Dict[str, Any]: if deb_val is None: deb_val = om_today + # LGBM enhancement (same as _analyze, so DEB is a single source of truth) + if current_forecasts and deb_val is not None: + h_times = om_hourly.get("time", []) + h_temps = om_hourly.get("temperature_2m", []) + h_rad = om_hourly.get("shortwave_radiation", []) + peak_hours: list[str] = [] + if h_temps and h_times and len(h_temps) == len(h_times) and h_rad and len(h_rad) == len(h_times): + for i, ts in enumerate(h_times): + if isinstance(ts, str) and "T" in ts: + try: + dt = datetime.fromisoformat(ts) + local_h = dt.hour + dt.minute / 60.0 + except Exception: + continue + if 11 <= local_h <= 17 and (h_rad[i] and h_rad[i] > 200): + peak_hours.append(ts.split("T")[1][:5]) + first_peak_h = int(peak_hours[0].split(":")[0]) if peak_hours else 13 + last_peak_h = int(peak_hours[-1].split(":")[0]) if peak_hours else 15 + if local_hour_frac > last_peak_h: + peak_status = "past" + elif first_peak_h <= local_hour_frac <= last_peak_h: + peak_status = "in_window" + else: + peak_status = "before" + lgbm_val, _ = predict_lgbm_daily_high( + city_name=city, + current_forecasts=current_forecasts, + deb_prediction=deb_val, + current_temp=cur_temp, + max_so_far=max_so_far, + humidity=_sf(primary_current.get("humidity")), + wind_speed_kt=_sf(primary_current.get("wind_speed_kt")), + visibility_mi=_sf(primary_current.get("visibility_mi")), + local_hour=local_hour, + local_date=local_date_str, + peak_status=peak_status, + ) + if lgbm_val is not None: + current_forecasts["LGBM"] = lgbm_val + blended, _weights_info = calculate_dynamic_weights(city, current_forecasts) + if blended is not None: + deb_val = blended + settlement_today_obs = [] if use_settlement_current: explicit_obs = settlement_current.get("today_obs") or [] @@ -3101,6 +3144,8 @@ def _build_city_detail_payload( for k, v in (data.get("multi_model") or {}).items() if not _is_excluded_model_name(k) }, + "deb": data.get("deb") or {}, + "multi_model_daily": data.get("multi_model_daily") or {}, "probabilities": data.get("probabilities") or {"mu": None, "distribution": []}, "dynamic_commentary": data.get("dynamic_commentary") or {"summary": "", "notes": []}, "intraday_meteorology": data.get("intraday_meteorology")