refactor(phase2): migrate nws + incident/roads to formatter+decider (tier-a) (#30)

Behavior-preserving relocation (byte-identical goldens), behind the staged-
cutover gate.

- formatters/nws.py + gating/nws.py: move _render + all CAP parsing (HTML-strip,
  _parse_nws_description, motion/hail/wind, county scoping, short-expiry) and the
  gating (3h re-alert window, Update/Cancel/Expire tombstone, first-sighting,
  warning->immediate severity). Native env/nws.py stops truncating description +
  carries parameters/eventCode/certainty/msgType/references/geocoder. Registers
  weather_warning + weather_statement.
- formatters/incident.py + gating/incident.py: absorb the incident _render AND
  the work_zone renderer, reconciling the two sub_type vocabularies; traffic_events
  change-detection gating. Native env/{traffic,roads511}.py emit canonical data.
  Registers work_zone/road_incident/road_closure/traffic_congestion.
- formatters/_anchor.py: shared town/distance/bearing resolver (consolidates WFIGS
  _location_anchor + incident nearest_town; WFIGS fire reuses in Phase 3).
- Fixtures captured: nws x37, traffic x46.

Tier-a byte-identical goldens for all fixtures. Tests: +~150; 0 new failures
(34 baseline, 1539 passed).

Co-authored-by: Matt Johnson <mj@k7zvx.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
malice 2026-07-04 18:16:34 -06:00 committed by GitHub
commit bdc42aa341
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
99 changed files with 15848 additions and 28 deletions

View file

@ -240,7 +240,12 @@ def test_to_event_watch_no_inhibit_keys(adapter):
def test_to_event_preserves_raw_in_data(adapter):
"""to_event preserves raw event dict in data field."""
"""Phase-2: to_event emits canonical data schema for formatter+gater.
event.data is now the canonical dict (cap_id, event, area_desc, ) rather
than the verbatim raw dict. Verify the canonical keys are present and that
source fields from raw are correctly mapped.
"""
raw = {
"event_id": "test-123",
"event_type": "Wind Advisory",
@ -249,8 +254,14 @@ def test_to_event_preserves_raw_in_data(adapter):
"custom_field": "custom_value",
}
event = adapter.to_event(raw)
assert event.data == raw
assert event.data["custom_field"] == "custom_value"
# Canonical schema keys required by formatters/nws.py
assert event.data["cap_id"] == "test-123"
assert event.data["event"] == "Wind Advisory"
assert event.data["category"] == "weather_advisory"
assert "geocoder" in event.data
# Raw source field not directly in canonical (stored in raw, not data)
# — the body/title still come from raw via make_event kwargs
assert event.title == "Wind Advisory"
# ============================================================