Compact scan runway history payload
This commit is contained in:
@@ -6,6 +6,7 @@ from web.scan_terminal_payloads import (
|
|||||||
build_stale_scan_terminal_payload,
|
build_stale_scan_terminal_payload,
|
||||||
)
|
)
|
||||||
from web.scan_terminal_ranker import build_ranked_scan_terminal_result
|
from web.scan_terminal_ranker import build_ranked_scan_terminal_result
|
||||||
|
from web.scan_terminal_city_row import _build_quick_row
|
||||||
from web.routers.scan import router as scan_router
|
from web.routers.scan import router as scan_router
|
||||||
|
|
||||||
|
|
||||||
@@ -137,6 +138,41 @@ def test_scan_terminal_snapshot_id_is_stable_for_same_ranked_inputs():
|
|||||||
assert first.startswith("scan-")
|
assert first.startswith("scan-")
|
||||||
|
|
||||||
|
|
||||||
|
def test_scan_terminal_quick_row_compacts_runway_history_for_list_payload():
|
||||||
|
raw_history = {
|
||||||
|
"35R": [
|
||||||
|
{"time": "2026-05-31T00:00:00+00:00", "temp": 22.11},
|
||||||
|
{"time": "2026-05-31T00:01:00+00:00", "temp": 22.22},
|
||||||
|
{"time": "2026-05-31T00:02:00+00:00", "temp": 22.33},
|
||||||
|
{"time": "2026-05-31T00:10:00+00:00", "temp": 23.44},
|
||||||
|
{"time": "2026-05-31T00:11:00+00:00", "temp": 23.55},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
row = _build_quick_row(
|
||||||
|
city="shanghai",
|
||||||
|
data={
|
||||||
|
"display_name": "Shanghai",
|
||||||
|
"local_date": "2026-05-31",
|
||||||
|
"local_time": "2026-05-31T08:11:00+08:00",
|
||||||
|
"temp_symbol": "°C",
|
||||||
|
"current": {"temp": 22.3, "max_so_far": 23.0},
|
||||||
|
"risk": {"airport": "Shanghai Pudong", "level": "medium"},
|
||||||
|
"deb": {"prediction": 24.0},
|
||||||
|
"probabilities": {"distribution": []},
|
||||||
|
"multi_model": {},
|
||||||
|
"runway_plate_history": raw_history,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
compact_history = row["runway_plate_history"]["35R"]
|
||||||
|
|
||||||
|
assert len(compact_history) == 2
|
||||||
|
assert compact_history[0]["temp"] == 22.3
|
||||||
|
assert compact_history[1]["temp"] == 23.6
|
||||||
|
assert len(str(row["runway_plate_history"])) < len(str(raw_history))
|
||||||
|
|
||||||
|
|
||||||
def test_metar_gate_vetoes_yes_when_observed_breaks_above_bucket():
|
def test_metar_gate_vetoes_yes_when_observed_breaks_above_bucket():
|
||||||
row = {
|
row = {
|
||||||
"id": "yes-row",
|
"id": "yes-row",
|
||||||
@@ -158,4 +194,3 @@ def test_metar_gate_vetoes_yes_when_observed_breaks_above_bucket():
|
|||||||
assert row["v4_metar_decision"] == "veto"
|
assert row["v4_metar_decision"] == "veto"
|
||||||
assert row["ai_decision"] == "veto"
|
assert row["ai_decision"] == "veto"
|
||||||
assert "越过目标桶上沿" in row["ai_reason_zh"]
|
assert "越过目标桶上沿" in row["ai_reason_zh"]
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,22 @@ from web.scan_terminal_filters import (
|
|||||||
market_region_from_tz_offset as _market_region_from_tz_offset,
|
market_region_from_tz_offset as _market_region_from_tz_offset,
|
||||||
safe_int as _safe_int,
|
safe_int as _safe_int,
|
||||||
)
|
)
|
||||||
|
from web.services.city_payloads import aggregate_runway_history
|
||||||
|
|
||||||
|
|
||||||
|
SCAN_ROW_RUNWAY_HISTORY_RESOLUTION = "10m"
|
||||||
|
SCAN_ROW_MAX_RUNWAY_POINTS = 144
|
||||||
|
|
||||||
|
|
||||||
|
def _compact_runway_plate_history_for_scan(raw_history: Any) -> Dict[str, List[Dict[str, Any]]]:
|
||||||
|
if not isinstance(raw_history, dict) or not raw_history:
|
||||||
|
return {}
|
||||||
|
compacted = aggregate_runway_history(raw_history, SCAN_ROW_RUNWAY_HISTORY_RESOLUTION)
|
||||||
|
return {
|
||||||
|
str(runway): points[-SCAN_ROW_MAX_RUNWAY_POINTS:]
|
||||||
|
for runway, points in compacted.items()
|
||||||
|
if isinstance(points, list) and points
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def _resolve_time_range_dates(data: Dict[str, Any], time_range: str) -> List[str]:
|
def _resolve_time_range_dates(data: Dict[str, Any], time_range: str) -> List[str]:
|
||||||
@@ -132,7 +148,9 @@ def _build_terminal_row(
|
|||||||
"amos": data.get("amos") or None,
|
"amos": data.get("amos") or None,
|
||||||
"top_buckets": scan.get("top_buckets") or [],
|
"top_buckets": scan.get("top_buckets") or [],
|
||||||
"all_buckets": scan.get("all_buckets") or [],
|
"all_buckets": scan.get("all_buckets") or [],
|
||||||
"runway_plate_history": data.get("runway_plate_history") or {},
|
"runway_plate_history": _compact_runway_plate_history_for_scan(
|
||||||
|
data.get("runway_plate_history")
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -232,7 +250,9 @@ def _build_quick_row(
|
|||||||
"is_primary_signal": True,
|
"is_primary_signal": True,
|
||||||
"accepting_orders": False,
|
"accepting_orders": False,
|
||||||
"row_id": row_id,
|
"row_id": row_id,
|
||||||
"runway_plate_history": data.get("runway_plate_history") or {},
|
"runway_plate_history": _compact_runway_plate_history_for_scan(
|
||||||
|
data.get("runway_plate_history")
|
||||||
|
),
|
||||||
}
|
}
|
||||||
# Compute a simple edge: model top probability vs neutral
|
# Compute a simple edge: model top probability vs neutral
|
||||||
best_model_prob = max(
|
best_model_prob = max(
|
||||||
|
|||||||
Reference in New Issue
Block a user