From fb4a9d7e09941bd4a49b4b8a35ae3e4c071bb61b Mon Sep 17 00:00:00 2001 From: "2569718930@qq.com" <2569718930@qq.com> Date: Thu, 28 May 2026 09:44:42 +0800 Subject: [PATCH] fix: align gaussian probability with local forecast date --- src/analysis/trend_engine.py | 20 +++++++++++++++++++ tests/test_deployment_runtime_config.py | 6 ++++++ tests/test_trend_engine.py | 26 +++++++++++++++++++++++++ web/analysis_service.py | 1 + 4 files changed, 53 insertions(+) diff --git a/src/analysis/trend_engine.py b/src/analysis/trend_engine.py index c048e8cb..b8d3e29b 100644 --- a/src/analysis/trend_engine.py +++ b/src/analysis/trend_engine.py @@ -312,6 +312,26 @@ def analyze_weather_trend( pass local_hour_frac = local_hour + local_minute / 60 + daily_dates = daily.get("time", []) or [] + daily_highs = daily.get("temperature_2m_max", []) or [] + if local_date_str in daily_dates: + try: + local_day_idx = daily_dates.index(local_date_str) + local_day_high = _sf( + daily_highs[local_day_idx] + if local_day_idx < len(daily_highs) + else None + ) + if local_day_high is not None: + current_forecasts["Open-Meteo"] = local_day_high + except Exception: + pass + forecast_highs = [h for h in current_forecasts.values() if h is not None] + forecast_high = max(forecast_highs) if forecast_highs else None + forecast_median = ( + sorted(forecast_highs)[len(forecast_highs) // 2] if forecast_highs else None + ) + # === DEB === deb_prediction = None deb_raw_prediction = None diff --git a/tests/test_deployment_runtime_config.py b/tests/test_deployment_runtime_config.py index 52d44d05..1948809c 100644 --- a/tests/test_deployment_runtime_config.py +++ b/tests/test_deployment_runtime_config.py @@ -47,3 +47,9 @@ def test_scan_terminal_backend_timeout_returns_before_next_proxy_abort(): assert 'POLYWEATHER_SCAN_TERMINAL_PROXY_TIMEOUT_MS || "40000"' in route_source assert scan_terminal_config.SCAN_TERMINAL_BUILD_TIMEOUT_SEC <= 30 + + +def test_probability_engine_uses_enriched_multi_model_snapshot(): + source = (ROOT / "web" / "analysis_service.py").read_text(encoding="utf-8") + + assert 'raw["multi_model"] = mm' in source diff --git a/tests/test_trend_engine.py b/tests/test_trend_engine.py index 2edfc9ca..4eac7cc5 100644 --- a/tests/test_trend_engine.py +++ b/tests/test_trend_engine.py @@ -137,6 +137,32 @@ class TestMuCalculation: assert mu is not None assert mu >= 33.0, f"μ should be >= actual max (33°C), got {mu}" + @patch("src.analysis.trend_engine.calculate_dynamic_weights", return_value=(None, "")) + @patch("src.analysis.trend_engine.get_deb_accuracy", return_value=None) + @patch("src.analysis.trend_engine.update_daily_record") + def test_mu_uses_city_local_date_daily_high(self, _udr, _deb_acc, _dw): + """Cached daily arrays may include yesterday first; μ must use the city-local target date.""" + data = _make_weather_data( + cur_temp=25.0, + max_so_far=25.0, + om_today_high=24.0, + ens_median=None, + ens_p10=None, + ens_p90=None, + local_time="2026-05-28 09:30", + recent_temps=[("09:00", 25.0), ("08:00", 24.0), ("07:00", 23.0)], + multi_model={}, + ) + data["open-meteo"]["utc_offset"] = 8 * 60 * 60 + data["open-meteo"]["daily"]["time"] = ["2026-05-27", "2026-05-28"] + data["open-meteo"]["daily"]["temperature_2m_max"] = [24.0, 31.0] + data["open-meteo"]["hourly"]["time"] = [f"2026-05-28T{h:02d}:00" for h in range(24)] + + _, _, sd = analyze_weather_trend(data, "°C", "wuhan") + + assert sd["current_forecasts"]["Open-Meteo"] == 31.0 + assert sd["mu"] is not None and sd["mu"] >= 30.0 + # ─── Tests: Dead Market ─── diff --git a/web/analysis_service.py b/web/analysis_service.py index c8997516..3954965c 100644 --- a/web/analysis_service.py +++ b/web/analysis_service.py @@ -737,6 +737,7 @@ def _analyze( mm_hourly = _weather.fetch_multi_model(lat, lon, city=city, use_fahrenheit=is_f) if mm_hourly and mm_hourly.get("hourly_times"): mm = {**mm, **mm_hourly} + raw["multi_model"] = mm risk = CITY_RISK_PROFILES.get(city, {}) network_snapshot = ( build_country_network_snapshot(city, raw)