mirror of
https://github.com/zvx-echo6/meshai.git
synced 2026-06-11 01:14:45 +02:00
feat(v0.5.9): unified incident pipeline + state_511_atis Idaho cutover + two-sided freshness gate
Coordinated change across the consumer dispatch layer + central_normalizer + new incident_handler + new itd_511 work_zone parser. The integrated story: Central PM filed a heads-up that ITD 511 publishes four EventTypes (work_zone, closure, incident, special_event) under us.id Convention A, and meshais v0.5.8 work focused on work_zone shape only -- meaning incidents (the higher-priority operational signal) were silently rendering as the v0.5.7-regression-style fallback. This v0.5.9 closes that gap by treating incidents + closures + special_events from all three traffic adapters (state_511_atis, itd_511, tomtom_incidents) as a single unified pipeline, while also migrating Idaho coverage from state_511_atis to itd_511 (the direct ITD feed) at the consumer level. Components: (1) new meshai/central/incident_handler.py routes incident/closure/special_event events through per-adapter parsers (tomtom, itd_511, state_511_atis-non-ID) to a canonical incident shape, then a single rendering pipeline with sub_type-aware emoji selection (jam/crash/road_closed/disabled_vehicle/parade/special_event/vehicle_fire/road_works). (2) Universal two-sided freshness gate in the consumer dispatch layer: only events with 0 <= age <= 1800s (default-allow on missing start_time) make it past the gate. Rejects both stale events (more than 30 min old) AND future-scheduled events (negative age -- a real itd_511 case for scheduled work projects). The gate sits ABOVE both incident_handler and the v0.5.8 work_zone formatter so all adapters get gated uniformly. (3) state_511_atis Idaho cutover -- both incident_handler and the v0.5.8 work_zone parser skip state_511_atis events where the state token is ID, deferring to itd_511 as the authoritative source. state_511_atis remains fully active for non-Idaho neighbor coverage (WA/OR/MT/UT/WY/NV) -- verified by Phase 2 WA broadcasts in the synthetic probe. (4) new itd_511 work_zone parser (extension to central_normalizer.py) consumes the itd_511 work_zone EventType and produces the same MEDIUM-style wire format as the existing state_511_atis work_zone parser (road + mile range + town + direction + sub_type + ends-at). (5) No Update: broadcasts in the incident pipeline -- per Matts call, real-time traffic Updates (jam getting worse, delay growing) are not actionable for mesh users. State tracking continues via traffic_events UPSERT but only the first sighting of an external_id ever fires a New: broadcast. WFIGS handler unchanged -- fires keep their 8h-rate-limited Update: behavior since acres growth IS operationally meaningful (evacuation decisions). Forecast: 3-10 mesh broadcasts/day in Idaho, all New:. Cross-check: original raw broadcast count was 623 against a fixed-clock 49-min synthetic window; after v0.5.9 REVISED (no Updates) it dropped to 18; after v0.5.9 GAMMA (two-sided gate + Idaho cutover) it dropped to 9. Test count: was 589 baseline, +45 net new -- 634 passing. Synthetic probe verified all four phases: Phase 1 (replay 3032 captured envelopes) = 0 broadcasts (correctly suppressed); Phase 2 (synthesized fresh non-ID + ID) = 7 broadcasts; Phase 3 (synthesized fresh itd_511 work_zone) = 2 broadcasts; Phase 4 (synthesized fresh ID for explicit ID-skip exercise) = caught by ID-skip 1/1. Master stays off in prod; no toggle flips. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
053d67db6e
commit
0099d0fd94
8 changed files with 2088 additions and 12 deletions
137
tests/test_itd_511_work_zone.py
Normal file
137
tests/test_itd_511_work_zone.py
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
"""Tests for the v0.5.9 GAMMA itd_511 work_zone parser in central_normalizer.
|
||||
|
||||
Covers the cutover path: itd_511 supplies all Idaho work_zone broadcasts now
|
||||
(state_511_atis ID is skipped). The parser must produce the same flat dict
|
||||
shape as _parse_state_511_atis so format_work_zone_mesh works unchanged.
|
||||
"""
|
||||
|
||||
from datetime import datetime, timezone
|
||||
|
||||
import pytest
|
||||
|
||||
from meshai import central_normalizer as cn
|
||||
|
||||
|
||||
# ---------- envelope builder ---------------------------------------------
|
||||
|
||||
|
||||
def _itd_work_zone_env(*, roadway="SH-55", direction="North",
|
||||
event_sub_type="roadConstruction",
|
||||
is_full_closure=False,
|
||||
external_id="ITD:469:42",
|
||||
lat=44.103, lon=-116.110,
|
||||
geocoder_city="McCall",
|
||||
start_epoch=1780600000,
|
||||
planned_end_epoch=1781200000,
|
||||
description="Road construction on SH-55 Northbound from MM (140) to MM (145). 6/4/2026 7:00 AM to 6/11/2026 5:00 PM."):
|
||||
return {
|
||||
"id": external_id,
|
||||
"subject": "central.traffic.work_zone.us.id",
|
||||
"data": {
|
||||
"id": external_id, "adapter": "itd_511",
|
||||
"category": "work_zone.itd_511", "severity": 1,
|
||||
"geo": {"centroid": [lon, lat], "primary_region": "US-ID"},
|
||||
"data": {
|
||||
"event_type_short": "work_zone",
|
||||
"event_sub_type": event_sub_type,
|
||||
"roadway_name": roadway, "direction": direction,
|
||||
"description": description,
|
||||
"lanes_affected": "All lanes affected",
|
||||
"is_full_closure": is_full_closure,
|
||||
"itd_severity": "None",
|
||||
"comment": "",
|
||||
"cause": "roadwork",
|
||||
"organization": "ERS",
|
||||
"recurrence_text": "",
|
||||
"recurrence_schedules": [],
|
||||
"restrictions": {},
|
||||
"encoded_polyline": "",
|
||||
"id_internal": 42, "source_id": "999",
|
||||
"reported_epoch": start_epoch,
|
||||
"last_updated_epoch": start_epoch,
|
||||
"start_epoch": start_epoch,
|
||||
"planned_end_epoch": planned_end_epoch,
|
||||
"latitude": lat, "longitude": lon,
|
||||
"_enriched": {"geocoder": {
|
||||
"name": None, "city": geocoder_city,
|
||||
"county": "Valley", "state": "ID",
|
||||
"country": "United States",
|
||||
"landclass": None, "elevation_m": 1530.0,
|
||||
}},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def no_photon(monkeypatch):
|
||||
monkeypatch.setattr(cn, "_photon_reverse_places", lambda lat, lon: [])
|
||||
if hasattr(cn, "_H3_NEAREST_CACHE"):
|
||||
cn._H3_NEAREST_CACHE.clear()
|
||||
|
||||
|
||||
# ---------- test (a): all fields populate -----------------------------------
|
||||
|
||||
|
||||
def test_itd_511_work_zone_parses_with_all_fields(no_photon):
|
||||
env = _itd_work_zone_env()
|
||||
n = cn.normalize(env)
|
||||
assert n is not None
|
||||
assert n["source"] == "itd_511"
|
||||
assert n["road"] == "SH-55"
|
||||
# _norm_direction returns 'northbound' (matches state_511 convention)
|
||||
assert n["direction"] == "northbound"
|
||||
assert n["mile_start"] == 140
|
||||
assert n["mile_end"] == 145
|
||||
assert n["sub_type"] is not None
|
||||
# is_full_closure=False -> impact 'partial' matches state_511 convention
|
||||
assert n["impact"] == "partial"
|
||||
assert n["town"] == "McCall"
|
||||
# ends_at is a datetime
|
||||
assert isinstance(n["ends_at"], datetime)
|
||||
|
||||
|
||||
def test_itd_511_work_zone_full_closure_impact(no_photon):
|
||||
env = _itd_work_zone_env(is_full_closure=True)
|
||||
n = cn.normalize(env)
|
||||
assert n["impact"] == "full_closure"
|
||||
|
||||
|
||||
def test_itd_511_work_zone_renderer_produces_wire(no_photon):
|
||||
from meshai.notifications.renderers.work_zone import format_work_zone_mesh
|
||||
env = _itd_work_zone_env()
|
||||
n = cn.normalize(env)
|
||||
wire = format_work_zone_mesh(n)
|
||||
assert wire is not None
|
||||
# Format matches state_511 convention: 🚧 emoji + road
|
||||
assert "🚧" in wire
|
||||
assert "SH-55" in wire
|
||||
assert "McCall" in wire
|
||||
|
||||
|
||||
def test_itd_511_work_zone_end_date_formatting(no_photon):
|
||||
"""planned_end_epoch should serialize to a datetime that the renderer
|
||||
can format consistently with state_511."""
|
||||
env = _itd_work_zone_env(planned_end_epoch=1781200000)
|
||||
n = cn.normalize(env)
|
||||
assert isinstance(n["ends_at"], datetime)
|
||||
assert n["ends_at"].tzinfo is not None # UTC-aware
|
||||
|
||||
|
||||
def test_itd_511_work_zone_no_end_date(no_photon):
|
||||
"""planned_end_epoch == None or 0 -> ends_at is None."""
|
||||
env = _itd_work_zone_env(planned_end_epoch=None)
|
||||
n = cn.normalize(env)
|
||||
assert n["ends_at"] is None
|
||||
|
||||
|
||||
def test_itd_511_incident_does_not_go_through_work_zone_parser(no_photon):
|
||||
"""The work_zone parser should NOT be invoked for category=incident.itd_511
|
||||
-- those still route through the incident_handler. normalize() returns
|
||||
None (defer) for non-work_zone itd_511 categories."""
|
||||
env = _itd_work_zone_env()
|
||||
env["data"]["category"] = "incident.itd_511"
|
||||
n = cn.normalize(env)
|
||||
# Either None (defer) or not a work_zone dict (no road/direction/etc).
|
||||
if n is not None:
|
||||
assert "_kind" in n # marker, not a parsed work_zone dict
|
||||
Loading…
Add table
Add a link
Reference in a new issue