feat(tomtom_incidents): TomTom real-time traffic incidents adapter (v0.9.5)

Fourth CENTRAL_TRAFFIC event adapter. Complements wzdx (federal work zones) and
state_511_atis (state-DOT reports) with TomTom commercial vehicle-telematics
coverage. Polls the Orbis incidentDetails endpoint per metro bbox, emits one
event per incident to central.traffic.incident.<state>. Ships disabled.

central-supervisor + central-gui restart only -- adapter row on the EXISTING
CENTRAL_TRAFFIC stream, so NO archive restart and no new stream/dependency.
Reuses the existing "tomtom" api key.

- Bbox limit refutation: incidentDetails rejects bbox > 10,000 km^2, so coverage
  is per-metro bboxes (Treasure Valley / Boise, 8,601 km^2), NOT statewide. One
  bbox @ 1800s = 1,440 calls/mo = 58% of the 2,500/mo free-tier cap. Expansion
  rows must respect N*(43200/cadence_min) <= 2500.
- category="incident.tomtom_incidents" -> GUI event_type "incident" (shared with
  state_511_atis; cross-source overlap is by design = additive coverage, distinct
  dedup ids + categories, no Central-side cross-source dedup).
- Severity from magnitudeOfDelay (0->1,1->1,2->2,3->3,4->4; 4=closure). Never None.
- geo.geometry carries TomTom's Point/LineString directly (already lon/lat GeoJSON;
  the v0.9.3 framework renders the affected road as a polyline). No decode needed.
- Dedup id <state_code>:tomtom:<tomtom_id> (upstream id stable across polls,
  verified 154/154 over 60s). Inherits the v0.9.1 dedup mixin.
- aiohttp params= URL-encodes the fields{} GraphQL braces (no curl-glob issue);
  key redacted from logs; poll skips cleanly without a key.

Full suite: 809 passed, 1 skipped (central and unprivileged zvx, 3x each).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Matt Johnson 2026-05-26 00:25:27 +00:00
commit 42d5faa80c
9 changed files with 479 additions and 1 deletions

View file

@ -1518,6 +1518,47 @@ road name, description, county, severity). Verified for Idaho only.
- **Removal semantics:** none in v1. Events age out of the upstream feed; the
14-day dedup sweep expires stale ids.
### tomtom_incidents — TomTom real-time traffic incidents (commercial coverage)
Real-time incidents (closures, jams, hazards, road work, accidents) from TomTom
Orbis `incidentDetails`, polled per metro bbox. Complements wzdx (federal work
zones) and state_511_atis (state-DOT reports) with commercial vehicle-telematics
coverage. One event per incident.
- **Stream:** `CENTRAL_TRAFFIC` (event class). **event_type:** `incident` (from
`category = "incident.tomtom_incidents"`); shares the type with state_511_atis.
- **Subject pattern:** `central.traffic.incident.<state>` (e.g.
`central.traffic.incident.id`); `<state>` is the per-bbox `state_code`.
- **Coverage:** configured metro bboxes, **each <= 10,000 km^2** (TomTom rejects
larger). Ships with Treasure Valley (Boise). **Cadence 1800s (30 min)** ->
1 bbox = 1,440 calls/mo, 58% of the 2,500/mo free-tier cap. Adding bboxes must
respect `N * (43200/cadence_min) <= 2500`.
- **Dedup key shape:** `<state_code>:tomtom:<tomtom_id>` (e.g.
`ID:tomtom:TTI-5df75143-...`); the upstream id is stable across polls.
- **Severity:** from `magnitudeOfDelay` (0->1, 1->1, 2->2, 3->3, 4->4; 4 ==
closure/blocking). Never None.
- **Event.data fields:**
| key | type | nullable | description |
|---|---|---|---|
| `description` | str | yes | Event text, e.g. `Roadworks`, `Closed` |
| `from` / `to` | str | yes | Affected segment endpoints |
| `magnitude_of_delay` | int | yes | 0-4 (drives severity) |
| `icon_category` | int | yes | TomTom icon enum (8=closed, 9=roadworks, 6=jam, ...) |
| `length` / `delay` | float | yes | Affected length (m) / delay (s) |
| `road_numbers` | list[str] | yes | Route numbers if known |
| `start_time` / `end_time` | str (ISO 8601) | yes | Incident window; `end_time` also sets `Event.expires` |
| `time_validity` | str | yes | e.g. `present` |
| `state_code` / `bbox_name` | str | no | Routing + source bbox |
| `latitude` / `longitude` | float | yes | First geometry vertex (enrichment input) |
The affected-road geometry (Point or LineString) rides on `geo.geometry` and
renders as a polyline on the map (v0.9.3 framework).
- **Decipherable as-is:** yes -- description + from/to + magnitude are user-ready;
geocoder fills city/county.
- **Removal semantics:** none in v1; incidents drop out of the feed when cleared,
swept by the 14-day dedup window.
### tomtom_flow — TomTom Orbis vector flow tiles (per-segment speed, telemetry)
Per-road-segment traffic speed from TomTom Orbis **vector** flow tiles, polled for