Tighten fallback reads around peak-window evidence

Fallback city-card reads now distinguish three cases that previously collapsed into the generic fast-evidence copy: observed highs above the model path, observed highs still lagging after the peak window, and low observations before the peak window that should wait for confirmation rather than down-revise immediately. The same pass removes three unused private helpers from the scan terminal service.

Constraint: Fallback output must be useful before the full AI airport-bulletin read returns

Rejected: Treat any low latest METAR as a down-revision | early-day observations can be below the forecast before the peak window

Rejected: Keep unused helper wrappers | they were unreferenced and added noise to an already large module

Confidence: high

Scope-risk: moderate

Tested: pytest tests/test_web_observability.py -q

Tested: npm run build
This commit is contained in:
2569718930@qq.com
2026-04-28 06:22:07 +08:00
parent d1d9f80f0f
commit 0a3242c6ec
3 changed files with 239 additions and 63 deletions
+112
View File
@@ -173,6 +173,118 @@ def test_city_ai_fallback_revises_up_when_latest_metar_breaks_above_models():
assert "继续上修最高温中枢" in payload["risks_zh"][0]
def test_city_ai_fallback_revises_down_after_peak_when_observed_high_lags():
payload = scan_terminal_service._build_city_ai_fallback(
{
"city_display_name": "London",
"temp_symbol": "°C",
"deb": {"prediction": 30.0},
"model_cluster": {
"sources": [
{"value": 29.2},
{"value": 30.0},
{"value": 31.1},
]
},
"window_phase": "post_peak",
"peak_window_label": "14:00-16:59",
"observation_anchor": {
"is_airport_metar": True,
"station_code": "EGLL",
},
"airport_current": {
"station_code": "EGLL",
"temp": 27.0,
"max_so_far": 27.5,
"report_time": "16:30Z",
"raw_metar": "EGLL 271630Z 22008KT 9999 SCT030 27/15 Q1012",
},
},
locale="zh-CN",
reason="stream preview",
)
assert payload["predicted_max"] == 27.5
assert "峰值窗口(14:00-16:59)已过或接近结束" in payload["final_judgment_zh"]
assert "最高温中枢需先下修到 27.5°C" in payload["final_judgment_zh"]
assert "共同支撑本轮最高温中枢" not in payload["reasoning_zh"]
assert "下修压力" in payload["reasoning_zh"]
assert "继续下修最高温中枢" in payload["risks_zh"][0]
def test_city_ai_fallback_does_not_downrevise_before_peak_window():
payload = scan_terminal_service._build_city_ai_fallback(
{
"city_display_name": "Dubai",
"temp_symbol": "°C",
"deb": {"prediction": 41.0},
"model_cluster": {
"sources": [
{"value": 40.5},
{"value": 41.0},
{"value": 41.6},
]
},
"window_phase": "early_today",
"minutes_until_peak_start": 240,
"peak_window_label": "14:00-16:59",
"observation_anchor": {
"is_airport_metar": True,
"station_code": "OMDB",
},
"airport_current": {
"station_code": "OMDB",
"temp": 35.0,
"report_time": "08:00Z",
"raw_metar": "OMDB 270800Z 29007KT CAVOK 35/20 Q1008",
},
},
locale="zh-CN",
reason="stream preview",
)
assert payload["predicted_max"] == 41.0
assert "暂不直接下修" in payload["reasoning_zh"]
assert "峰值窗口尚未到来" in payload["reasoning_zh"]
assert "若峰值窗口前继续偏低,需要下修最高温中枢" in payload["risks_zh"][0]
def test_city_ai_fallback_marks_peak_window_passed_without_waiting_for_warming():
payload = scan_terminal_service._build_city_ai_fallback(
{
"city_display_name": "Paris",
"temp_symbol": "°C",
"deb": {"prediction": 28.0},
"model_cluster": {
"sources": [
{"value": 27.6},
{"value": 28.0},
{"value": 28.5},
]
},
"window_phase": "post_peak",
"peak_window_label": "13:00-15:59",
"observation_anchor": {
"is_airport_metar": True,
"station_code": "LFPG",
},
"airport_current": {
"station_code": "LFPG",
"temp": 27.0,
"max_so_far": 27.2,
"report_time": "17:00Z",
"raw_metar": "LFPG 271700Z 25006KT 9999 FEW035 27/13 Q1014",
},
},
locale="zh-CN",
reason="stream preview",
)
assert "峰值窗口(13:00-15:59)已过" in payload["final_judgment_zh"]
assert "不是继续按待升温路径解读" in payload["reasoning_zh"]
assert "避免继续上调最高温中枢" in payload["risks_zh"][0]
def test_city_ai_stream_request_only_asks_provider_for_observation_read():
request_payload = scan_terminal_service._build_city_ai_stream_request(
{