diff --git a/work/meshai/central/incident_handler.py b/work/meshai/central/incident_handler.py index 2ab1d4d..13486d2 100644 --- a/work/meshai/central/incident_handler.py +++ b/work/meshai/central/incident_handler.py @@ -602,6 +602,87 @@ def handle_incident(envelope: dict, subject: str, source = n["source"] pk_combined = f"{source}|{external_id}" + # ── Phase-2 cutover branch ──────────────────────────────────────────── + # When the relevant category has been explicitly cut over, delegate + # traffic_events management to gating.incident.decide() and write + # canonical data into the shared `data` dict so the formatter can + # render from it at dispatch time. NOT-cutover path is unchanged. + _kind_to_cat = { + "incident": "road_incident", + "closure": "road_closure", + "special_event": "road_incident", + "work_zone": "work_zone", + } + _event_cat = _kind_to_cat.get(n.get("category_kind", "incident"), "road_incident") + + from meshai.notifications.cutover import is_cutover as _is_cutover + if _is_cutover(_event_cat) and isinstance(data, dict): + # Build canonical data from parsed dict n. + _canonical = { + "external_id": n["external_id"], + "source": n["source"], + "sub_type": n.get("sub_type"), + "road": n.get("road"), + "direction": n.get("direction"), + "from_loc": n.get("from_loc"), + "to_loc": n.get("to_loc"), + "mile_start": n.get("mile_start"), + "mile_end": n.get("mile_end"), + "mile_marker": n.get("mile_marker"), + "lanes_affected": n.get("lanes_affected"), + "cause": n.get("cause"), + "comment": n.get("comment"), + "impact": n.get("impact"), + "county": n.get("county"), + "state": n.get("state"), + "lat": n.get("lat"), + "lon": n.get("lon"), + "geocoder_city": n.get("geocoder_city"), + "landclass": n.get("landclass"), + "start_at": n.get("start_at"), + "end_at": n.get("end_at"), + "magnitude": n.get("magnitude"), + "delay_seconds": n.get("delay_seconds"), + "icon_category": n.get("icon_category"), + } + + _log_id_co = _log_event_returning_id( + conn, now=now, source=source, category=category_raw, + severity_word=severity_word, + event_id_external=external_id, + subject=subject, handled=0, + table_name="traffic_events", table_pk=pk_combined) + + from meshai.notifications.gating.incident import decide as _gate_decide + _gate = _gate_decide(_canonical, source=source, now=float(now)) + + if not _gate.broadcast: + return None + + data.update(_canonical) + data.update(_gate.data_patch) + data["_broadcast_audit"] = {"table": "traffic_events", "pk": pk_combined} + + _raw_commit = _gate.commit + _log_row_id_co = _log_id_co + + def _on_commit_cutover(committed_at: float) -> None: + if _raw_commit is not None: + _raw_commit(committed_at) + if _log_row_id_co is not None: + try: + _c = get_db() + _c.execute("UPDATE event_log SET handled=1 WHERE id=?", + (int(_log_row_id_co),)) + except Exception: + logger.exception( + "incident cutover commit: event_log update failed") + + data["_on_broadcast_committed"] = _on_commit_cutover + return _render(n) + + # ── Legacy path (not cutover): exact original logic below ───────────── + log_id = _log_event_returning_id( conn, now=now, source=source, category=category_raw, severity_word=severity_word, diff --git a/work/meshai/central/nws_handler.py b/work/meshai/central/nws_handler.py index ad19112..a05db44 100644 --- a/work/meshai/central/nws_handler.py +++ b/work/meshai/central/nws_handler.py @@ -215,6 +215,15 @@ def _location_anchor(area_desc: Optional[str], geocoder_city: Optional[str], def handle_nws(envelope: dict, subject: str, data: Optional[dict] = None, now: Optional[int] = None) -> Optional[str]: + """Central path handler for NWS weather-alert envelopes. + + Phase-2 refactor: when the category is cut over (MESHAI_CUTOVER_CATEGORIES + contains weather_warning or weather_statement), gating is delegated to + meshai.notifications.gating.nws.decide() and canonical data is written + into the shared `data` dict for the formatter. + + On NOT-cutover: exact legacy code path, byte-for-byte unchanged. + """ if not isinstance(envelope, dict): return None inner = envelope.get("data") or {} if (inner.get("adapter") or "") != "nws": return None @@ -226,18 +235,125 @@ def handle_nws(envelope: dict, subject: str, category_raw = inner.get("category") or "" severity_word = _coerce_severity(inner.get("severity")) + cap_id = d.get("id") or inner.get("id") + if not cap_id: + return None + + # ── Field extraction (shared by both paths) ─────────────────────────────── + msg_type = d.get("msgType") + event_type = d.get("event") or _category_to_event_type(category_raw) + area_desc = d.get("areaDesc") + headline = d.get("headline") + description = d.get("description") + cap_severity = d.get("severity") + county = d.get("areaDesc") or ge.get("county") + state = ge.get("state") or d.get("state") + expires_epoch = _parse_iso(d.get("expires")) + same_code = ((d.get("eventCode") or {}).get("SAME") or [""])[0] + certainty = d.get("certainty") or "" + references = d.get("references") or [] + parameters = d.get("parameters") or {} + + lat = lon = None + cent = geo.get("centroid") or [] + if isinstance(cent, list) and len(cent) >= 2: + lon, lat = cent[0], cent[1] + + # ── Cutover gate ────────────────────────────────────────────────────────── + from meshai.notifications.cutover import is_cutover + _cutover = is_cutover("weather_warning") or is_cutover("weather_statement") + + if _cutover: + # ── NEW PATH: delegate to gating/nws.py decide() ───────────────────── + try: + conn = get_db() + except Exception: + logger.exception("nws_handler: persistence unavailable") + return None + + # Tombstone: log handled=0, return None (before canonical/decide). + if msg_type in set(adapter_config.nws.tombstone_msgtypes): + _log_event(conn, now=now, source="nws", category=category_raw, + severity_word=severity_word, event_id_external=cap_id, + subject=subject, handled=0, + table_name="nws_alerts", table_pk=cap_id) + return None + + # Always log the event (both broadcast and suppress paths). + log_id = _log_event_returning_id( + conn, now=now, source="nws", category=category_raw, + severity_word=severity_word, event_id_external=cap_id, + subject=subject, handled=0, + table_name="nws_alerts", table_pk=cap_id) + + # Build canonical data dict for decide() and the formatter. + canonical: dict = { + "cap_id": cap_id, + "event": event_type, + "same_code": same_code, + "cap_severity": cap_severity, + "certainty": certainty, + "expires_at": expires_epoch, + "area_desc": area_desc, + "geocoder": { + "city": ge.get("city"), + "county": county, + "state": state, + }, + "description": description, + "parameters": parameters, + "msgType": msg_type, + "references": references, + "category": category_raw, + "headline": headline, + } + + from meshai.notifications.gating.nws import decide as _gate_decide + gate = _gate_decide(canonical, source="nws", now=float(now)) + + if not gate.broadcast: + return None + + # Write canonical fields + gate data_patch into shared data dict. + if isinstance(data, dict): + data.update(canonical) + data.update(gate.data_patch) + data["_broadcast_audit"] = {"table": "nws_alerts", "pk": cap_id} + + _raw_commit = gate.commit + _log_row_id = log_id + + def _on_commit(committed_at: float) -> None: + if _raw_commit is not None: + _raw_commit(committed_at) + if _log_row_id is not None: + try: + c = get_db() + c.execute("UPDATE event_log SET handled=1 WHERE id=?", + (int(_log_row_id),)) + except Exception: + logger.exception("nws commit: event_log update failed") + + data["_on_broadcast_committed"] = _on_commit + + # Return _render() wire for backward compat with existing call-sites. + # At dispatch time compose_mesh_message() will use the registered + # formatter (formatters/nws.py) which re-renders from event.data + # producing byte-identical output (tier-a). + _prefix = gate.data_patch.get("_nws_prefix", "") + return _render(event_type=event_type, area_desc=area_desc, + geocoder_city=ge.get("city"), county=county, state=state, + expires_epoch=expires_epoch, lat=lat, lon=lon, now=now, + prefix=_prefix, d=d) + + # ── NOT cutover: exact legacy path (byte-for-byte unchanged) ───────────── try: conn = get_db() except Exception: logger.exception("nws_handler: persistence unavailable") return None - cap_id = d.get("id") or inner.get("id") - if not cap_id: - return None - # Tombstone: msgType in {Cancel, Expire} -> log handled=0, no broadcast. - msg_type = d.get("msgType") if msg_type in set(adapter_config.nws.tombstone_msgtypes): _log_event(conn, now=now, source="nws", category=category_raw, severity_word=severity_word, event_id_external=cap_id, @@ -268,20 +384,6 @@ def handle_nws(envelope: dict, subject: str, "SELECT last_broadcast_at FROM nws_alerts WHERE event_id=?", (cap_id,)).fetchone() - event_type = d.get("event") or _category_to_event_type(category_raw) - area_desc = d.get("areaDesc") - headline = d.get("headline") - description = d.get("description") - cap_severity = d.get("severity") - county = d.get("areaDesc") or ge.get("county") - state = ge.get("state") or d.get("state") - expires_epoch = _parse_iso(d.get("expires")) - - lat = lon = None - cent = geo.get("centroid") or [] - if isinstance(cent, list) and len(cent) >= 2: - lon, lat = cent[0], cent[1] - if row is None: conn.execute( "INSERT INTO nws_alerts(event_id, alert_type, severity, county, " diff --git a/work/meshai/env/nws.py b/work/meshai/env/nws.py index 041dc47..5c443b0 100644 --- a/work/meshai/env/nws.py +++ b/work/meshai/env/nws.py @@ -68,6 +68,10 @@ class NWSAlertsAdapter: def to_event(self, raw: dict) -> Event: """Convert internal event dict to pipeline Event. + Phase-2: emits canonical event.data schema so the formatter+gater + architecture (formatters/nws.py, gating/nws.py) can operate on + native events identically to Central-sourced events. + Args: raw: Internal event dict from get_events() @@ -89,6 +93,34 @@ class NWSAlertsAdapter: base = event_type.rsplit(" ", 1)[0] if " " in event_type else event_type inhibit_keys = [f"nws:{base} Watch", f"nws:{base} Advisory"] + area_desc = raw.get("area_desc", "") + + # Build canonical data dict for the formatter+gater. + # Keys match the Central-bridge canonical schema so both paths render + # identically. Native has no geocoder enrichment (city/state=None); + # county falls back to areaDesc, mirroring the handler's: + # county = d.get("areaDesc") or ge.get("county") + canonical = { + "cap_id": raw.get("cap_id") or raw.get("event_id", ""), + "event": event_type, + "same_code": raw.get("same_code", ""), + "cap_severity": raw.get("cap_severity") or raw.get("severity", "Unknown"), + "certainty": raw.get("certainty", ""), + "expires_at": raw.get("expires_at") or raw.get("expires") or None, + "area_desc": area_desc, + "geocoder": { + "city": None, + "county": area_desc, # areaDesc as county fallback (no enrichment) + "state": None, + }, + "description": raw.get("description", ""), # FULL — not truncated + "parameters": raw.get("parameters") or {}, + "msgType": raw.get("msgType") or raw.get("messageType", "Alert"), + "references": raw.get("references") or [], + "category": category, + "headline": raw.get("headline", ""), + } + return make_event( source="nws", category=category, @@ -103,7 +135,7 @@ class NWSAlertsAdapter: nws_zones=raw.get("areas", []), group_key=group_key, inhibit_keys=inhibit_keys, - data=raw, + data=canonical, ) def tick(self) -> bool: @@ -200,12 +232,23 @@ class NWSAlertsAdapter: "event_type": props.get("event", "Unknown"), "severity": severity, "headline": props.get("headline", ""), - "description": (props.get("description") or "")[:500], + "description": props.get("description") or "", # FULL — not truncated "onset": onset, "expires": expires, "areas": props.get("geocode", {}).get("UGC", []), "area_desc": props.get("areaDesc", ""), "fetched_at": time.time(), + # ── Canonical schema fields (Phase-2) ──────────────────────── + # These are read by to_event() to build the canonical data dict + # for the formatter+gater architecture. + "cap_id": props.get("id", ""), + "same_code": ((props.get("eventCode") or {}).get("SAME") or [""])[0], + "cap_severity": props.get("severity", "Unknown"), + "certainty": props.get("certainty", "Unknown"), + "expires_at": expires, + "parameters": props.get("parameters") or {}, + "msgType": props.get("messageType", "Alert"), + "references": props.get("references") or [], } # Try to get centroid from geometry diff --git a/work/meshai/env/roads511.py b/work/meshai/env/roads511.py index e9b2c80..90052ac 100644 --- a/work/meshai/env/roads511.py +++ b/work/meshai/env/roads511.py @@ -391,6 +391,41 @@ class Roads511Adapter: # sole inhibit_key lets the pipeline Inhibitor suppress # lower-severity re-emissions while a higher-severity one is active # for the same incident (severity tiering delegated to Inhibitor). + + # Canonical data for the Phase-2 formatter (incident path). + # Native 511 adapter has a description-based format; road/direction + # are parsed best-effort from the headline/description. + _roadway = props.get("roadway") + _desc = evt.get("description", "") or "" + _is_closure = bool(props.get("is_closure")) + canonical_data = { + "external_id": None, # native adapter, no Central dedup + "source": "511", + "sub_type": "road_closed" if _is_closure else "incident", + "road": _roadway or None, + "direction": None, # not structured in native 511 feed + "from_loc": None, + "to_loc": None, + "mile_start": None, + "mile_end": None, + "mile_marker": None, + "lanes_affected": None, + "cause": None, + "comment": _desc[:200] if _desc else title, + "impact": "all lanes closed" if _is_closure else None, + "county": None, + "state": None, + "lat": lat, + "lon": lon, + "geocoder_city": None, + "landclass": None, + "start_at": None, + "end_at": None, + "magnitude": None, + "delay_seconds": None, + "icon_category": "road_closed" if _is_closure else "incident", + } + return make_event( source="511", category="road_closure", @@ -403,6 +438,7 @@ class Roads511Adapter: lon=lon, group_key=event_id, inhibit_keys=[event_id], + data=canonical_data, ) except Exception: logger.exception(f"511 to_event failed for evt: {evt.get('event_id')}") diff --git a/work/meshai/env/traffic.py b/work/meshai/env/traffic.py index 3b73930..b6b6947 100644 --- a/work/meshai/env/traffic.py +++ b/work/meshai/env/traffic.py @@ -282,6 +282,37 @@ class TomTomTrafficAdapter: # higher-severity one is active for the same corridor. corridor_key = f"traffic_{str(corridor).replace(' ', '_').lower()}" + # Canonical data for the Phase-2 formatter (incident path). + # TomTom Flow lacks structured road/direction/county; degrade + # gracefully (None fields) — formatter uses headline as comment. + canonical_data = { + "external_id": None, # native adapter, no Central dedup + "source": "traffic", + "sub_type": "road_closed" if props.get("roadClosure") else "jam", + "road": str(corridor).replace("_", " ") if corridor else None, + "direction": None, + "from_loc": None, + "to_loc": None, + "mile_start": None, + "mile_end": None, + "mile_marker": None, + "lanes_affected": None, + "cause": None, + "comment": title, + "impact": "all lanes closed" if props.get("roadClosure") else None, + "county": None, + "state": None, + "lat": lat, + "lon": lon, + "geocoder_city": None, + "landclass": None, + "start_at": None, + "end_at": None, + "magnitude": None, + "delay_seconds": None, + "icon_category": "road_closed" if props.get("roadClosure") else "jam", + } + return make_event( source="traffic", category="traffic_congestion", @@ -294,6 +325,7 @@ class TomTomTrafficAdapter: lon=lon, group_key=corridor_key, inhibit_keys=[corridor_key], + data=canonical_data, ) except Exception: logger.exception(f"Traffic to_event failed for evt: {evt.get('event_id')}") diff --git a/work/meshai/notifications/formatters/__init__.py b/work/meshai/notifications/formatters/__init__.py index e8573e1..051ef66 100644 --- a/work/meshai/notifications/formatters/__init__.py +++ b/work/meshai/notifications/formatters/__init__.py @@ -72,3 +72,17 @@ register("avalanche_watch", _avy_fmt_mod.format) from meshai.notifications.formatters import swpc as _swpc_fmt_mod # noqa: E402,F401 register("geomagnetic_storm", _swpc_fmt_mod.format) register("rf_propagation_alert", _swpc_fmt_mod.format) + +# Phase-2: NWS weather alerts (weather_warning + weather_statement). +# weather_watch and weather_advisory are not yet migrated (Phase-2 scope). +from meshai.notifications.formatters import nws as _nws_fmt_mod # noqa: E402,F401 +register("weather_warning", _nws_fmt_mod.format) +register("weather_statement", _nws_fmt_mod.format) + +# Phase-2: incident / roads categories. +# One formatter handles all four; event.category drives the render path. +from meshai.notifications.formatters import incident as _incident_fmt_mod # noqa: E402,F401 +register("work_zone", _incident_fmt_mod.format) +register("road_incident", _incident_fmt_mod.format) +register("road_closure", _incident_fmt_mod.format) +register("traffic_congestion", _incident_fmt_mod.format) diff --git a/work/meshai/notifications/formatters/_anchor.py b/work/meshai/notifications/formatters/_anchor.py new file mode 100644 index 0000000..3a367ed --- /dev/null +++ b/work/meshai/notifications/formatters/_anchor.py @@ -0,0 +1,131 @@ +"""Shared location anchor resolver — Phase-2+. + +resolve_anchor(lat, lon, *, max_mi) -> Optional[dict] + +Returns {town: str, distance_mi: int, bearing: str} or None. + +Priority: + 1. Curated ``town_anchors`` SQLite table (GUI-managed, haversine nearest). + 2. Photon reverse geocoder via central_normalizer.nearest_town() fallback. + +The function is PURE (no side-effects beyond the LRU cache inside +nearest_town) and safe to call from any formatter. Both the incident +formatter (Phase 2) and the WFIGS fire formatter (Phase 3) use it. + +Haversine and bearing implementations are local copies so the module +has no runtime dependency on central_normalizer (avoids the circular +import chain formatters → central_normalizer → persistence → formatters). +""" +from __future__ import annotations + +import logging +import math +from typing import Optional + +logger = logging.getLogger(__name__) + + +# ── Geometry helpers (mirrors central_normalizer exactly) ───────────────── + + +def _haversine_miles(lat1: float, lon1: float, lat2: float, lon2: float) -> float: + R = 3958.8 + phi1, phi2 = math.radians(lat1), math.radians(lat2) + dphi = math.radians(lat2 - lat1) + dl = math.radians(lon2 - lon1) + a = math.sin(dphi / 2) ** 2 + math.cos(phi1) * math.cos(phi2) * math.sin(dl / 2) ** 2 + return 2 * R * math.asin(math.sqrt(a)) + + +def _bearing_compass( + lat1: float, lon1: float, lat2: float, lon2: float +) -> str: + """Compass bearing from town (lat2, lon2) to event (lat1, lon1). + + Result is the direction the event lies relative to the town, so + '8 mi N of Plummer' means the event is north of the town. + Mirrors central_normalizer._bearing_compass and wfigs_handler._location_anchor. + """ + phi1, phi2 = math.radians(lat2), math.radians(lat1) + dl = math.radians(lon1 - lon2) + x = math.sin(dl) * math.cos(phi2) + y = (math.cos(phi1) * math.sin(phi2) + - math.sin(phi1) * math.cos(phi2) * math.cos(dl)) + brng = (math.degrees(math.atan2(x, y)) + 360) % 360 + points = ["N", "NE", "E", "SE", "S", "SW", "W", "NW"] + return points[int((brng + 22.5) // 45) % 8] + + +# ── Public API ──────────────────────────────────────────────────────────── + + +def resolve_anchor( + lat: float, lon: float, *, max_mi: float = 100.0 +) -> Optional[dict]: + """Find the nearest town to (lat, lon) within *max_mi* miles. + + Priority + -------- + 1. Curated ``town_anchors`` SQLite table — haversine nearest row with + ``lat IS NOT NULL AND lon IS NOT NULL``. GUI-managed and always + tried first so curated entries beat the Photon fallback. + 2. ``central_normalizer.nearest_town()`` Photon reverse-geocoder — + same H3-cached Photon call used by the normalizer's town selection. + + Parameters + ---------- + lat, lon : float + Event coordinates. + max_mi : float + Maximum anchor distance in miles. Default 100. + + Returns + ------- + dict with keys ``town`` (str), ``distance_mi`` (int), ``bearing`` (str) + — or ``None`` when no town is within *max_mi*. + """ + if lat is None or lon is None: + return None + try: + lat, lon = float(lat), float(lon) + except (TypeError, ValueError): + return None + + # ── 1. Curated town_anchors table ──────────────────────────────────── + try: + from meshai.persistence import get_db + rows = get_db().execute( + "SELECT name, lat, lon FROM town_anchors " + "WHERE lat IS NOT NULL AND lon IS NOT NULL" + ).fetchall() + best = None + best_d = float("inf") + for row in rows: + d = _haversine_miles(lat, lon, float(row["lat"]), float(row["lon"])) + if d < best_d: + best_d = d + best = row + if best is not None and best_d <= max_mi: + bearing = _bearing_compass(lat, lon, float(best["lat"]), float(best["lon"])) + return { + "town": best["name"].title(), + "distance_mi": int(round(best_d)), + "bearing": bearing, + } + except Exception: + logger.debug("resolve_anchor: town_anchors lookup failed, falling back to Photon") + + # ── 2. Photon nearest_town fallback ────────────────────────────────── + try: + from meshai.central_normalizer import nearest_town + nt = nearest_town(lat, lon, max_distance_mi=max_mi) + if nt and nt.get("name"): + return { + "town": str(nt["name"]), + "distance_mi": nt.get("distance_mi"), + "bearing": nt.get("bearing"), + } + except Exception: + logger.debug("resolve_anchor: nearest_town fallback also failed") + + return None diff --git a/work/meshai/notifications/formatters/incident.py b/work/meshai/notifications/formatters/incident.py new file mode 100644 index 0000000..3a43e61 --- /dev/null +++ b/work/meshai/notifications/formatters/incident.py @@ -0,0 +1,444 @@ +"""Incident/Roads event formatter — Phase-2 reference implementation. + +Absorbs two legacy renderers into one entry-point dispatched by event.category: + + category == "work_zone" + → _render_work_zone(): replicates renderers.work_zone.format_work_zone_mesh() + byte-for-byte. 80-byte UTF-8 cap (internal constant); the outer + ``budget`` parameter is unused on this path (the 80-byte cap is + inherent to the mesh format for work-zone events). + + category in ("road_incident", "road_closure", "traffic_congestion", other) + → _render_incident(): replicates incident_handler._render() byte-for-byte. + Fitted to the injected ``budget`` character limit. + +Canonical event.data schemas +---------------------------- +Work-zone events (category == "work_zone"): + road str | None — normalised road name + direction str | None — full form: "northbound"/"southbound"/…/"both"/"unknown" + mile_start int | None + mile_end int | None + sub_type str | None — human-readable: "paving", "road construction", … + impact str | None — "full_closure" | "partial" + ends_at_epoch float | None — ends_at.timestamp() (reconstructed to datetime in formatter) + town str | None — pre-computed anchor (populated by bridge/normalizer) + distance_mi int | None + bearing str | None + lat float | None — raw coords for resolve_anchor fallback + lon float | None + +Incident events (other categories): + external_id str + source str + sub_type str — snake_case: "accident", "road_works", "closure", … + road str | None + direction str | None — short: "N"/"S"/"E"/"W"/"both" + from_loc str | None + to_loc str | None + mile_marker float | None + lanes_affected str | None + comment str | None + impact str | None — "all lanes closed" | None + county str | None + state str | None + lat float | None + lon float | None + geocoder_city str | None + landclass str | None + magnitude int | None + delay_seconds int | None + icon_category str | None + +Reconciled sub_type taxonomy +---------------------------- +Two distinct vocabularies coexist, disambiguated by event.category: + + Incident snake_case (all non-work_zone categories): + accident, jam, road_closed, closure, road_works, lane_closed, + ramp_closed, debris, vehicle_on_fire, disabled_vehicle, ice, fog, + flooding, wind, broken_down, danger, rain, incident, special_event, parade + + Work-zone human-readable (category == "work_zone"): + paving, road construction, construction work, bridge construction, + bridge maintenance, bridge inspection, pavement marking, + emergency repairs, utility work, guardrail repairs, shoulder work, + brush control, flagging, alternating one-way, maintenance, + minor repair, roadside work, overhead work, subsurface work, + barrier work, surface work, painting, roadway relocation, + new construction, road work, construction, emergency repairs, + detour, lanes reduced, one-way alternating + (+ wzdx impact fold-ins: "lanes reduced, paving" etc.) + +The formatter selects the rendering path from event.category; no cross- +vocabulary lookup is needed. + +Time contract: ``now`` (float epoch) is accepted by format() and passed +through to the work-zone ends-at renderer as a naive datetime. Incident +rendering is time-independent (no relative-time strings). +All clock reads MUST go through meshai.notifications.clock — never stdlib. +""" +from __future__ import annotations + +from datetime import datetime +from typing import TYPE_CHECKING, Optional + +from meshai.notifications.formatters._budget import fit_to_budget + +if TYPE_CHECKING: + from meshai.notifications.events import Event + + +# ── Work-zone byte budget (mirrors renderers.work_zone._BYTE_BUDGET) ────── +_WZ_BYTE_BUDGET = 80 + + +def _bytelen(s: str) -> int: + return len(s.encode("utf-8")) + + +# ── Incident sub_type lookup tables (mirrors incident_handler exactly) ───── + +_SUB_TYPE_EMOJI = { + "accident": "🚨", + "jam": "🚗", + "road_closed": "🚫", + "closure": "🚫", + "road_works": "🚧", + "lane_closed": "🟠", + "ramp_closed": "🟠", + "debris": "⚠️", + "vehicle_on_fire": "🔥", + "disabled_vehicle": "🛑", + "ice": "⚠️", + "fog": "⚠️", + "flooding": "🌊", + "wind": "🌬️", + "broken_down": "🛞", + "danger": "⚠️", + "rain": "⚠️", + "incident": "⚠️", + "special_event": "🎪", + "parade": "🎪", +} + +_SUB_TYPE_DISPLAY = { + "accident": "Crash", + "jam": "Stationary Traffic", + "road_closed": "Road Closed", + "closure": "Closure", + "road_works": "Road Works", + "lane_closed": "Lane Reduction", + "ramp_closed": "Ramp Closed", + "debris": "Debris on Roadway", + "vehicle_on_fire": "Vehicle Fire", + "disabled_vehicle": "Disabled Vehicle", + "ice": "Icy Conditions", + "fog": "Fog", + "flooding": "Flooding", + "wind": "High Winds", + "broken_down": "Broken-Down Vehicle", + "danger": "Dangerous Conditions", + "rain": "Heavy Rain", + "incident": "Road Incident", + "special_event": "Special Event", + "parade": "Parade", +} + +_DIRECTION_LONG = { + "North": "Northbound", "N": "Northbound", "NB": "Northbound", + "north": "Northbound", "nb": "Northbound", + "South": "Southbound", "S": "Southbound", "SB": "Southbound", + "south": "Southbound", "sb": "Southbound", + "East": "Eastbound", "E": "Eastbound", "EB": "Eastbound", + "east": "Eastbound", "eb": "Eastbound", + "West": "Westbound", "W": "Westbound", "WB": "Westbound", + "west": "Westbound", "wb": "Westbound", + "Both": "Both Directions", "both": "Both Directions", +} + + +# ── Work-zone rendering helpers (mirrors renderers.work_zone exactly) ────── + +def _wz_format_end_short( + ends_at: Optional[datetime], now: datetime +) -> Optional[str]: + """Mirror of renderers.work_zone._format_end_short — byte-identical. + + ``now`` is required (never reads the live clock; caller must pass the + pinned clock value). Matches the contract enforced by + test_formatters_no_clock. + """ + from datetime import timedelta + if ends_at is None: + return None + if ends_at.tzinfo is not None: + ends_at = ends_at.replace(tzinfo=None) + if now.tzinfo is not None: + now = now.replace(tzinfo=None) + delta = ends_at - now + if delta.total_seconds() < 0: + return None + hour = ends_at.hour + minute = ends_at.minute + if hour == 0: + time_part = "12am" if minute == 0 else f"12:{minute:02d}am" + elif hour < 12: + time_part = f"{hour}am" if minute == 0 else f"{hour}:{minute:02d}am" + elif hour == 12: + time_part = "12pm" if minute == 0 else f"12:{minute:02d}pm" + else: + time_part = f"{hour-12}pm" if minute == 0 else f"{hour-12}:{minute:02d}pm" + + if delta < timedelta(hours=24) and ends_at.date() == now.date(): + return f"today {time_part}" + if (delta < timedelta(hours=48) + and ends_at.date() == (now + timedelta(days=1)).date()): + return f"tomorrow {time_part}" + if delta < timedelta(days=7): + wd = ends_at.strftime("%a") + return f"{wd} {time_part}" + if delta < timedelta(days=365): + return (ends_at.strftime("%b %-d") + if hasattr(datetime, "now") else ends_at.strftime("%b ") + str(ends_at.day)) + return None + + +def _wz_format_direction_phrase(direction: Optional[str]) -> Optional[str]: + if not direction or direction == "unknown": + return None + if direction == "both": + return "both directions" + return direction + + +def _wz_format_mile_segment( + mile_start: Optional[int], mile_end: Optional[int] +) -> Optional[str]: + if mile_start is None: + return None + if mile_end is not None and mile_end != mile_start: + return f"@ mile {mile_start}–{mile_end}" + return f"@ mile {mile_start}" + + +def _wz_format_distance_segment( + distance_mi: Optional[int], bearing: Optional[str], town: Optional[str] +) -> Optional[str]: + if not town: + return None + if distance_mi is not None and bearing and distance_mi >= 1: + return f"{distance_mi} mi {bearing} of {town}" + return f"near {town}" + + +def _wz_truncate_road(road: str, budget: int) -> str: + if _bytelen(road) <= budget: + return road + cut = road + while cut and _bytelen(cut + "…") > budget: + cut = cut[:-1] + return cut + "…" if cut else "…" + + +def _render_work_zone(d: dict, now_dt: Optional[datetime]) -> str: + """Byte-identical replica of renderers.work_zone.format_work_zone_mesh().""" + emoji = "🚧" + raw_road = d.get("road") + town = d.get("town") + + if raw_road: + road = raw_road + head = f"{emoji} {road}" + suppress_distance_seg = False + elif town: + road = town + head = f"{emoji} {_wz_format_distance_segment(d.get('distance_mi'), d.get('bearing'), town)}" + suppress_distance_seg = True + else: + road = "Road event" + head = f"{emoji} {road}" + suppress_distance_seg = False + + mile_seg = ( + _wz_format_mile_segment(d.get("mile_start"), d.get("mile_end")) + if raw_road else None + ) + dist_seg = ( + None if suppress_distance_seg + else _wz_format_distance_segment(d.get("distance_mi"), d.get("bearing"), town) + ) + dir_phrase = _wz_format_direction_phrase(d.get("direction")) + sub = d.get("sub_type") + impact = d.get("impact") + if impact == "full_closure": + sub = f"all lanes closed{' (' + sub + ')' if sub else ''}" + + # Reconstruct ends_at from epoch for the time formatter. + # ends_at_epoch is stored as calendar.timegm(naive_dt.timetuple()) — + # i.e., the wall-clock value of the naive datetime treated as UTC. + # datetime.utcfromtimestamp() reconstructs the same naive datetime + # regardless of the process TZ. This mirrors how the old renderers + # strip tzinfo and compare naive datetimes directly. + ends_at: Optional[datetime] = None + ends_epoch = d.get("ends_at_epoch") + if ends_epoch is not None: + try: + ends_at = datetime.utcfromtimestamp(float(ends_epoch)) + except (TypeError, ValueError, OSError): + ends_at = None + + ends_seg = _wz_format_end_short(ends_at, now=now_dt) + + segs: list[tuple[int, str, str]] = [] + if mile_seg: + segs.append((10, " ", mile_seg)) + if dist_seg: + segs.append((20, ", ", dist_seg)) + if dir_phrase or sub: + segs.append((30, ": ", "")) + if dir_phrase: + segs.append((30, "", dir_phrase)) + if sub: + segs.append((40, ", " if dir_phrase else "", sub)) + if ends_seg: + segs.append((50, ", ends ", ends_seg)) + + kept = list(range(len(segs))) + while True: + out = head + last_was_colon = False + for i in kept: + prio, joiner, text = segs[i] + if text == "" and joiner == ": ": + out += ": " + last_was_colon = True + continue + if last_was_colon: + out += text + last_was_colon = False + else: + out += joiner + text + if _bytelen(out) <= _WZ_BYTE_BUDGET: + return out + droppable = [i for i in kept if segs[i][2] != ""] + if not droppable: + budget_for_road = _WZ_BYTE_BUDGET - _bytelen(emoji + " ") + return f"{emoji} {_wz_truncate_road(road, budget_for_road)}" + worst = max(droppable, key=lambda i: segs[i][0]) + kept.remove(worst) + remaining_after_colon = any( + segs[i][2] != "" for i in kept + if any( + segs[j][0] == 30 and segs[j][2] == "" for j in range(len(segs)) if j < i + ) + ) + if not remaining_after_colon: + kept = [i for i in kept + if not (segs[i][2] == "" and segs[i][1] == ": ")] + + +# ── Incident rendering (mirrors incident_handler._render exactly) ────────── + +def _render_incident(d: dict, budget: int) -> str: + """Byte-identical replica of incident_handler._render().""" + sub_type = d.get("sub_type") or "incident" + emoji = _SUB_TYPE_EMOJI.get(sub_type, "⚠️") + display = _SUB_TYPE_DISPLAY.get(sub_type, "Road Incident") + + # Line 1: emoji + display + city/county + anchor = d.get("geocoder_city") or d.get("county") + state = d.get("state") or "" + if anchor: + anchor_part = f"Near {anchor}, {state}".rstrip(", ") + if not d.get("geocoder_city") and d.get("county"): + anchor_part = f"Near {anchor} Co, {state}".rstrip(", ") + else: + anchor_part = state or "" + line1 = f"{emoji} {display} — {anchor_part}".rstrip(" —") + + # Line 2: road + direction (+ MP) + lane-status joined by " · ". + road = d.get("road") + direction = d.get("direction") + dir_long = _DIRECTION_LONG.get(direction, direction) if direction else None + mile = d.get("mile_marker") + from_loc = d.get("from_loc") + to_loc = d.get("to_loc") + seg: list[str] = [] + if road and dir_long: + seg.append(f"{road} {dir_long}") + elif road: + seg.append(road) + elif from_loc and to_loc: + seg.append(f"{from_loc} → {to_loc}") + elif from_loc: + seg.append(from_loc) + if mile is not None: + seg.append(f"MP {mile}") + lanes = d.get("lanes_affected") + if lanes and lanes.strip().lower() not in ("no data", ""): + seg.append(lanes.strip()) + line2 = " · ".join(seg) + + msg = "\n".join(l for l in (line1, line2) if l) + + comment = d.get("comment") + if comment and comment.strip(): + comment_normalized = comment.strip().lower() + lanes_normalized = (lanes or "").strip().lower() + if comment_normalized != lanes_normalized: + msg = f"{msg}\n{comment.strip()}" if msg else comment.strip() + + return fit_to_budget(msg, budget) + + +# ── Resolve anchor for incidents that lack geocoder_city ────────────────── + +def _resolve_incident_anchor(d: dict) -> tuple[Optional[str], Optional[str]]: + """Return (anchor_city, state) for line-1 of the incident render. + + Priority mirrors incident_handler._render() exactly: + 1. geocoder_city (direct) + 2. county (with "Co" suffix) + Falls through to (None, state) when neither is available. + """ + city = d.get("geocoder_city") + county = d.get("county") + state = d.get("state") or "" + if city: + return city, state + if county: + return county, state + return None, state + + +# ── Public API ──────────────────────────────────────────────────────────── + + +def format(event: "Event", *, now: float, budget: int) -> str: + """Render an incident/roads event to a mesh wire string. + + Dispatches on event.category: + "work_zone" → work-zone renderer (80-byte UTF-8 cap, ignores budget) + everything else → incident renderer (budget character cap) + + Parameters + ---------- + event : Event — reads from event.data (canonical schema above). + now : float — frozen-clock epoch. Used only for work-zone ends-at + rendering; incident rendering is time-independent. + budget : int — mesh packet character budget (incident path only). + + Returns + ------- + str — UTF-8 wire string fitting within the appropriate budget. + """ + d = event.data or {} + category = event.category or "" + + if category == "work_zone": + # Reconstruct naive local datetime for the ends-at renderer. + now_dt = datetime.fromtimestamp(now) + return _render_work_zone(d, now_dt=now_dt) + + return _render_incident(d, budget=budget) diff --git a/work/meshai/notifications/formatters/nws.py b/work/meshai/notifications/formatters/nws.py new file mode 100644 index 0000000..2c94ead --- /dev/null +++ b/work/meshai/notifications/formatters/nws.py @@ -0,0 +1,377 @@ +"""NWS weather-alert formatter — Phase-2 implementation. + +Reads canonical event.data schema: + event, same_code, cap_severity, certainty, expires_at, + area_desc, geocoder{city,county,state}, + description (FULL), parameters (raw CAP dict), + msgType, references, cap_id + (+ decider-injected: _nws_prefix, _severity_override) + +Tier-A wire contract: + Byte-identical to nws_handler._render() for the same input values. + The relative expiry uses the injected `now` (stdlib clock calls forbidden); + fit to injected `budget`. + +Time contract: `now` is accepted but not used for rendering (structural seam +for future relative-time annotations). All time reads MUST go through +meshai.notifications.clock (clock.now / clock.now_dt) — never stdlib +equivalents — so golden-file tests can freeze the clock via monkeypatch. +""" +from __future__ import annotations + +import re +import zoneinfo +from datetime import datetime +from typing import TYPE_CHECKING, Optional + +from meshai.notifications.formatters._budget import fit_to_budget + +if TYPE_CHECKING: + from meshai.notifications.events import Event + +# ── Event-emoji tables (verbatim from nws_handler) ─────────────────────────── + +_EVENT_EMOJI = [ + ("tornado", "🌪️"), + ("severe thunderstorm", "🌩️"), + ("thunderstorm", "🌩️"), + ("flash flood", "🌊"), + ("flood", "🌊"), + ("winter storm", "❄️"), + ("blizzard", "❄️"), + ("ice storm", "❄️"), + ("ice", "❄️"), + ("excessive heat", "🌡️"), + ("heat", "🌡️"), + ("high wind", "🌬️"), + ("wind", "🌬️"), + ("fire weather", "🔥"), + ("red flag", "🔥"), + ("air quality", "😷"), + ("freeze", "🥶"), + ("frost", "🥶"), +] + +_SAME_EMOJI = { + "TOR": "🌪️", "SVR": "⛈️", "FFW": "🌊", "FLW": "🌊", + "WSW": "❄️", "BZW": "❄️", "WCY": "❄️", "EWW": "💨", + "HWW": "💨", "FRW": "🔥", "SPS": "🌬️", "SMW": "⛈️", + "MAW": "🌊", "ADR": "⚠️", +} + +# ── Hail descriptor table (verbatim from nws_handler) ──────────────────────── + +_HAIL_DESCRIPTORS = { + "pea": 0.25, "half inch": 0.50, "penny": 0.75, "nickel": 0.88, + "quarter": 1.00, "half dollar": 1.25, "ping pong": 1.50, "ping-pong": 1.50, + "golf ball": 1.75, "golf": 1.75, "hen egg": 2.00, "tennis ball": 2.50, + "baseball": 2.75, "softball": 4.00, +} + + +# ── Parsing helpers (verbatim from nws_handler) ─────────────────────────────── + +def _parse_nws_description(description: str) -> dict: + result = {} + patterns = { + "hazard": r"HAZARD\.\.\.(.*?)(?=\n\n|\nSOURCE|\nIMPACT|\nLocations|$)", + "impact": r"IMPACT\.\.\.(.*?)(?=\n\n|\nLocations|$)", + "tornado": r"TORNADO\.\.\.(.*?)(?=\n\n|\n[A-Z]+\.\.\.|$)", + "tornado_threat": r"TORNADO DAMAGE THREAT\.\.\.(.*?)(?=\n\n|\n[A-Z]+\.\.\.|$)", + "locations": r"Locations impacted include[.…]*\s*(.*?)(?=\n\n|$)", + } + for key, pattern in patterns.items(): + m = re.search(pattern, description or "", re.DOTALL | re.IGNORECASE) + if m: + text = m.group(1).replace("\n", " ").strip() + if text: + # Preserve the FULL town list for path-sampling; + # all other fields keep the 80-char cap. + result[key] = text[:400] if key == "locations" else text[:80] + return result + + +def _parse_motion(params: dict) -> tuple: + """Parse eventMotionDescription into (compass, speed_mph). + Format: '...DEG...KT' e.g. '254DEG...35KT' + Returns (compass_str, speed_mph_int) or (None, None).""" + raw = (params.get("eventMotionDescription") or [""])[0] + if not raw: + return None, None + m = re.search(r"(\d+)DEG\.+(\d+)KT", raw) + if not m: + return None, None + deg = float(m.group(1)) + knots = int(m.group(2)) + mph = round(knots * 1.15) + dirs = ["N", "NE", "E", "SE", "S", "SW", "W", "NW"] + compass = dirs[int((deg + 22.5) / 45) % 8] + return compass, mph + + +def _emoji_for_event(event_type: Optional[str]) -> str: + if not event_type: + return "⚠️" + s = event_type.lower() + for substr, emoji in _EVENT_EMOJI: + if substr in s: + return emoji + return "⚠️" + + +def _tighten_wind(wind: str) -> str: + """'60 MPH' -> '60mph winds' (no space before mph, no 'wind gusts' filler).""" + w = (wind or "").strip().lower().replace(" mph", "mph") + if not w: + return "" + if not w.endswith("mph"): + w = f"{w}mph" + return f"{w} winds" + + +def _fmt_hail(hail: str) -> str: + """Numeric or descriptor hail size -> '1\" hail'. Descriptor maps per NWS.""" + s = (hail or "").strip() + if not s: + return "" + val = None + low = s.lower() + for k, v in _HAIL_DESCRIPTORS.items(): + if k in low: + val = v + break + if val is None: + try: + val = float(re.sub(r"[^0-9.]", "", s)) + except (ValueError, TypeError): + return "" + txt = f"{val:.2f}".rstrip("0").rstrip(".") + return f'{txt}" hail' + + +def _collapse_certainty(text: str) -> str: + """'Radar confirmed'/'Radar indicated' -> 'radar'; 'Observed' -> 'observed'.""" + low = (text or "").strip().lower() + if low in ("radar confirmed", "radar indicated"): + return "radar" + if low == "observed": + return "observed" + if low == "likely": + return "likely" + if low == "on ground": + return "on ground" + return (text or "").strip() + + +def _tighten_hazard(text: str) -> str: + """Compact a free-form NWS hazard sentence into the terse mesh idiom.""" + if not text: + return "" + t = text.strip().rstrip(".") + low = t.lower() + for k in sorted(_HAIL_DESCRIPTORS, key=len, reverse=True): + for variant in (f"{k} size hail", f"{k}-size hail", f"{k} sized hail"): + idx = low.find(variant) + if idx != -1: + repl = _fmt_hail(k) + if repl: + t = t[:idx] + repl + t[idx + len(variant):] + low = t.lower() + break + t = re.sub(r"(\d+(?:\.\d+)?)[\- ]inch(?:es)?\s+hail", + lambda m: _fmt_hail(m.group(1)) or m.group(0), t, + flags=re.IGNORECASE) + t = re.sub(r"wind gusts?\s+(?:in excess of|up to|to|of|reaching|near|around)" + r"\s+(\d+)\s*mph", r"\1mph gusts", t, flags=re.IGNORECASE) + t = re.sub(r"winds?\s+gusting\s+(?:up\s+)?to\s+(\d+)\s*mph", + r"\1mph gusts", t, flags=re.IGNORECASE) + t = re.sub(r"(?:damaging\s+)?winds?\s+(?:in excess of|up to|to|of)\s+" + r"(\d+)\s*mph", r"\1mph winds", t, flags=re.IGNORECASE) + t = re.sub(r"\bin excess of\b", ">", t, flags=re.IGNORECASE) + t = re.sub(r"(\d+)\s*mph", r"\1mph", t, flags=re.IGNORECASE) + t = re.sub(r"\s+", " ", t).strip() + return t + + +# ── Public API ──────────────────────────────────────────────────────────────── + +def format(event: "Event", *, now: float, budget: int) -> str: + """Render the NWS weather-alert wire string from canonical event.data. + + Args: + event: Pipeline Event — reads from event.data (canonical schema). + now: Frozen-clock epoch (structural seam; not used in current + rendering — expiry is absolute, not relative). + budget: Mesh-packet character budget (from budget_for("nws")). + + Returns: + UTF-8 string fitting within *budget* characters. + Byte-identical to nws_handler._render() for equivalent inputs (Tier-A). + + Canonical fields read from event.data: + event str — NWS event type ("Severe Thunderstorm Warning") + same_code str — SAME/eventCode value ("SVR", "SPS", etc.) + area_desc str — areaDesc from CAP + expires_at int — unix epoch of expiry (or None) + description str — FULL CAP description (not truncated) + parameters dict — raw CAP parameters dict + certainty str — CAP certainty ("Observed", "Likely", ...) + _nws_prefix str — decider-injected prefix ("", "Update", "Active") + """ + d = event.data or {} + + # ── Extract canonical fields ────────────────────────────────────────────── + event_type = d.get("event") or "Weather Alert" + area_desc = d.get("area_desc") or "" + expires_epoch = d.get("expires_at") + description_full = d.get("description") or "" + parameters = d.get("parameters") or {} + same_code = d.get("same_code") or "" + certainty = (d.get("certainty") or "").strip() + prefix = d.get("_nws_prefix") or "" + + # ── Adapter config ──────────────────────────────────────────────────────── + from meshai.adapter_config import adapter_config + + # ── Parse description ───────────────────────────────────────────────────── + desc = _parse_nws_description(description_full) + + # ── Emoji (SAME code → explicit map, else event-type substring) ─────────── + emoji = _SAME_EMOJI.get(same_code) or _emoji_for_event(event_type) + prefix_seg = f"{prefix}: " if prefix else "" + + # Line 1: emoji + event type + line1 = f"{emoji} {prefix_seg}{event_type or 'Weather Alert'}" + + # Line 2: "Until {time} {tz} — {area}" + tz = zoneinfo.ZoneInfo("America/Boise") + if expires_epoch: + exp_local = datetime.fromtimestamp(expires_epoch, tz=tz) + exp_str = exp_local.strftime("%-I:%M %p %Z") + time_seg = f"Until {exp_str}" + else: + time_seg = "" + area = (area_desc or "").split(";")[0].strip() + _area_limit = int(adapter_config.nws.area_max_chars) + if len(area) > _area_limit: + cut = area[:_area_limit].rsplit(" ", 1)[0] + if not cut: + cut = area[:_area_limit] + area = cut + "…" + if time_seg and area: + line2 = f"{time_seg} — {area}" + elif time_seg: + line2 = time_seg + elif area: + line2 = area + else: + line2 = "" + + # Line 3: hazard + certainty/threat (SAME-code branched) + line3 = "" + if same_code == "TOR": + detection = (parameters.get("tornadoDetection") or [""])[0] + status = "on ground" if detection == "OBSERVED" else "radar" + threat = (parameters.get("tornadoDamageThreat") or [""])[0] + threat_seg = f" · {threat.lower()} damage" if threat else "" + line3 = f"tornado {status}{threat_seg}" + elif same_code == "SVR": + wind = (parameters.get("maxWindGust") or [""])[0] + hail = (parameters.get("maxHailSize") or [""])[0] + bits = [] + if wind and wind not in ("0 MPH", ""): + w = _tighten_wind(wind) + if w: + bits.append(w) + if hail and hail not in ("0.00", "0", ""): + h = _fmt_hail(hail) + if h: + bits.append(h) + hazard = ", ".join(bits) + # SVR is radar-based: "Observed" certainty => "Radar confirmed" => "radar". + confirm = _collapse_certainty( + "Radar confirmed" if certainty == "Observed" else "Radar indicated") + line3 = f"{hazard} · {confirm}" if hazard else confirm + elif same_code in ("FFW", "FLW"): + hazard_text = desc.get("hazard") or "" + if ". " in hazard_text: + hazard_text = hazard_text.split(". ")[0] + hazard_text = _tighten_hazard(hazard_text) + desc_lower = description_full.lower() + flood_cause = "" + for keyword, label in [("thunderstorm", "thunderstorms"), + ("dam", "dam failure"), + ("snowmelt", "snowmelt"), + ("ice jam", "ice jam")]: + if keyword in desc_lower: + flood_cause = label + break + cause_seg = f" · {flood_cause}" if flood_cause else "" + line3 = f"{hazard_text}{cause_seg}" if hazard_text else flood_cause + else: + # SPS, WSW, etc.: first hazard sentence (tightened) + certainty if + # Observed/Likely. + hazard_text = desc.get("hazard") or "" + if ". " in hazard_text: + hazard_text = hazard_text.split(". ")[0] + hazard_text = _tighten_hazard(hazard_text) + cert_seg = "" + if certainty in ("Observed", "Likely"): + cert_seg = f" · {_collapse_certainty(certainty)}" + line3 = f"{hazard_text}{cert_seg}" if hazard_text else "" + + # Line 4: motion + locations (path-sampled if the full town list won't fit). + compass, speed_mph = _parse_motion(parameters) + motion = f"Moving {compass} {speed_mph} mph" if compass and speed_mph else "" + + # Parse the (now-full) locations string into an ordered town list. + raw_locs = (desc.get("locations") or "").rstrip("., ") + towns = [t.strip() for t in raw_locs.split(",") if t.strip()] + if towns: + towns[-1] = re.sub(r"^and\s+", "", towns[-1], flags=re.IGNORECASE).strip() + towns = [t for t in towns if t] + + def _dedup(seq): + """Drop consecutive repeats.""" + out = [] + for t in seq: + if not out or out[-1] != t: + out.append(t) + return out + + def _line4(locs: str) -> str: + if motion and locs: + return f"{motion} — {locs}" + if motion: + return motion + return locs or "" + + PACKET_LIMIT = budget + + # Location representations from richest to poorest; first form that fits wins. + loc_options = [", ".join(towns)] + if len(towns) >= 3: + loc_options.append(" → ".join( + _dedup([towns[0], towns[len(towns) // 2], towns[-1]]))) + if len(towns) >= 2: + loc_options.append(" → ".join(_dedup([towns[0], towns[-1]]))) + if towns: + loc_options.append(towns[0]) + loc_options.append("") # motion only / empty + + base_lines = [l for l in (line1, line2, line3) if l] + msg = None + for locs in loc_options: + cand4 = _line4(locs) + lines = base_lines + ([cand4] if cand4 else []) + candidate = "\n".join(lines) + if len(candidate) <= PACKET_LIMIT: + msg = candidate + break + if msg is None: + # Even motion-only line 4 overflows: drop line 4 entirely. + msg = "\n".join(base_lines) + + # Final hard-cap safety net for the pathological case where lines 1-3 alone + # overflow. + return fit_to_budget(msg, PACKET_LIMIT) diff --git a/work/meshai/notifications/gating/__init__.py b/work/meshai/notifications/gating/__init__.py index fc97b12..502c689 100644 --- a/work/meshai/notifications/gating/__init__.py +++ b/work/meshai/notifications/gating/__init__.py @@ -63,3 +63,17 @@ register("avalanche_watch", _avy_gate_mod.decide) from meshai.notifications.gating import swpc as _swpc_gate_mod # noqa: E402,F401 register("geomagnetic_storm", _swpc_gate_mod.decide) register("rf_propagation_alert", _swpc_gate_mod.decide) + +# Phase-2: NWS weather alerts (weather_warning + weather_statement). +# weather_watch and weather_advisory are not yet migrated (Phase-2 scope). +from meshai.notifications.gating import nws as _nws_gate_mod # noqa: E402,F401 +register("weather_warning", _nws_gate_mod.decide) +register("weather_statement", _nws_gate_mod.decide) + +# Phase-2: incident / roads categories. +# One decider handles all four; it reads external_id to decide dedup path. +from meshai.notifications.gating import incident as _incident_gate_mod # noqa: E402,F401 +register("work_zone", _incident_gate_mod.decide) +register("road_incident", _incident_gate_mod.decide) +register("road_closure", _incident_gate_mod.decide) +register("traffic_congestion", _incident_gate_mod.decide) diff --git a/work/meshai/notifications/gating/incident.py b/work/meshai/notifications/gating/incident.py new file mode 100644 index 0000000..7babed8 --- /dev/null +++ b/work/meshai/notifications/gating/incident.py @@ -0,0 +1,286 @@ +"""Incident/Roads gating decider — Phase-2 reference implementation. + +Mirrors incident_handler change-detection EXACTLY: + * NEW external_id → INSERT into traffic_events, broadcast lifecycle="new" + * Existing row, last_broadcast_at IS NULL → broadcast lifecycle="new" + (cold-start: dispatcher dropped the prior broadcast) + * adapter_config.incident.broadcast_on_update is False → suppress + * magnitude stepped up OR delay doubled OR icon changed → Update + * otherwise → suppress + +decide(data, *, source, now) -> GateResult: + + Canonical data schema consumed (same keys as the central bridge + produces; subset used by gating): + external_id str | None — None for native adapters (always broadcast) + source str — "tomtom_incidents" | "state_511_atis" | "itd_511" | … + sub_type str | None + road str | None + direction str | None + mile_start int | None + mile_end int | None + county str | None + state str | None + lat float | None + lon float | None + impact str | None + start_at int | None + end_at int | None + magnitude int | None — magnitude_of_delay + delay_seconds int | None + icon_category str | None + + Emitted data_patch keys: + is_update bool — True on Update: broadcasts, False on New: + _dedup_suffix str — empty (external_id is the dedup key) + + commit(now: float) -> None (deferred idempotent): + UPSERT last_broadcast_at / first_broadcast_at / + last_broadcast_magnitude / last_broadcast_delay_seconds / + last_broadcast_icon_category on the traffic_events row. + Safe to call N times (all writes are conditional UPSERTs). + +Native adapters (external_id is None or empty): + No traffic_events lookup; always broadcast with lifecycle="native". + The event bus inhibitor + group_key handle dedup for native events. +""" +from __future__ import annotations + +import logging +from typing import Optional + +from meshai.adapter_config import adapter_config +from meshai.notifications.gating.base import GateResult +from meshai.persistence import get_db + +logger = logging.getLogger(__name__) + + +def decide(data: dict, *, source: str, now: float) -> GateResult: + """Gate + dedup decision for incident/roads events. + + Parameters + ---------- + data : Canonical event.data (see module docstring for schema). + source : Adapter source name, e.g. "tomtom_incidents". + now : Current epoch from clock.now() — determinism seam. + + Returns + ------- + GateResult with: + broadcast=True lifecycle="new" | "update" data_patch + commit set + broadcast=False lifecycle="suppress" data_patch={} commit=None + """ + external_id = data.get("external_id") or None + source_val = data.get("source") or source + + # ── Native adapters: no external_id, no traffic_events dedup ───────── + if not external_id: + return GateResult( + broadcast=True, + lifecycle="native", + reason=f"native adapter {source_val!r} — no external_id, no dedup", + data_patch={"is_update": False, "_dedup_suffix": ""}, + ) + + # ── Persistence lookup ──────────────────────────────────────────────── + try: + conn = get_db() + except Exception: + logger.exception("incident decide: persistence unavailable") + return GateResult( + broadcast=False, + lifecycle="suppress", + reason="persistence unavailable", + ) + + now_int = int(now) + magnitude = data.get("magnitude") + delay_seconds = data.get("delay_seconds") + icon_category = data.get("icon_category") + + row = conn.execute( + "SELECT first_seen_at, last_broadcast_at, " + "last_broadcast_magnitude, last_broadcast_delay_seconds, " + "last_broadcast_icon_category " + "FROM traffic_events WHERE source=? AND external_id=?", + (source_val, external_id), + ).fetchone() + + # ── NEW external_id ─────────────────────────────────────────────────── + if row is None: + # INSERT with last_broadcast_at = NULL (armed by commit on delivery). + try: + conn.execute( + "INSERT INTO traffic_events(" + "source, external_id, road, direction, " + "mile_start, mile_end, county, state, lat, lon, " + "sub_type, impact, start_at, end_at, " + "first_seen_at, last_seen_at, last_broadcast_at, " + "magnitude_of_delay, delay_seconds, icon_category, " + "last_broadcast_magnitude, last_broadcast_delay_seconds, " + "last_broadcast_icon_category" + ") VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)", + ( + source_val, external_id, + data.get("road"), data.get("direction"), + data.get("mile_start"), data.get("mile_end"), + data.get("county"), data.get("state"), + data.get("lat"), data.get("lon"), + data.get("sub_type"), data.get("impact"), + data.get("start_at"), data.get("end_at"), + now_int, now_int, None, + magnitude, delay_seconds, icon_category, + None, None, None, + ), + ) + except Exception: + logger.exception("incident decide: INSERT failed for %s|%s", + source_val, external_id) + + patch = {"is_update": False, "_dedup_suffix": ""} + + def _commit_new(committed_at: float) -> None: + _do_commit(committed_at, source_val, external_id, + magnitude, delay_seconds, icon_category) + + return GateResult( + broadcast=True, + lifecycle="new", + reason=f"new {source_val}|{external_id}", + data_patch=patch, + commit=_commit_new, + ) + + # ── EXISTING row: always refresh last_seen_at + current fields ──────── + try: + conn.execute( + "UPDATE traffic_events " + "SET sub_type=?, impact=?, magnitude_of_delay=?, " + "delay_seconds=?, icon_category=?, last_seen_at=?, " + "lat=COALESCE(?, lat), lon=COALESCE(?, lon), " + "direction=COALESCE(?, direction), road=COALESCE(?, road) " + "WHERE source=? AND external_id=?", + ( + data.get("sub_type"), data.get("impact"), + magnitude, delay_seconds, icon_category, + now_int, + data.get("lat"), data.get("lon"), + data.get("direction"), data.get("road"), + source_val, external_id, + ), + ) + except Exception: + logger.exception("incident decide: UPDATE last_seen_at failed") + + last_bcast_at = row["last_broadcast_at"] + + # ── Cold-start: row exists but was never delivered ──────────────────── + if last_bcast_at is None: + patch = {"is_update": False, "_dedup_suffix": ""} + + def _commit_cold(committed_at: float) -> None: + _do_commit(committed_at, source_val, external_id, + magnitude, delay_seconds, icon_category) + + return GateResult( + broadcast=True, + lifecycle="new", + reason=f"cold-start {source_val}|{external_id}", + data_patch=patch, + commit=_commit_cold, + ) + + # ── Post-first-broadcast: check broadcast_on_update ─────────────────── + if not bool(adapter_config.incident.broadcast_on_update): + return GateResult( + broadcast=False, + lifecycle="suppress", + reason="broadcast_on_update=False", + ) + + last_bcast_mag = row["last_broadcast_magnitude"] + last_bcast_delay = row["last_broadcast_delay_seconds"] + last_bcast_icon = row["last_broadcast_icon_category"] + + mag_stepped_up = ( + magnitude is not None + and (last_bcast_mag is None or magnitude > last_bcast_mag) + ) + delay_doubled = ( + delay_seconds is not None + and last_bcast_delay is not None + and last_bcast_delay > 0 + and delay_seconds >= 2 * last_bcast_delay + ) + icon_changed = ( + icon_category is not None + and last_bcast_icon is not None + and icon_category != last_bcast_icon + ) + + if not (mag_stepped_up or delay_doubled or icon_changed): + return GateResult( + broadcast=False, + lifecycle="suppress", + reason=( + f"no update condition met " + f"(mag={magnitude} last={last_bcast_mag}, " + f"delay={delay_seconds} last={last_bcast_delay}, " + f"icon={icon_category!r} last={last_bcast_icon!r})" + ), + ) + + patch = {"is_update": True, "_dedup_suffix": ""} + + def _commit_update(committed_at: float) -> None: + _do_commit(committed_at, source_val, external_id, + magnitude, delay_seconds, icon_category) + + reason_parts = [] + if mag_stepped_up: + reason_parts.append(f"mag {last_bcast_mag}→{magnitude}") + if delay_doubled: + reason_parts.append(f"delay {last_bcast_delay}→{delay_seconds}s") + if icon_changed: + reason_parts.append(f"icon {last_bcast_icon!r}→{icon_category!r}") + + return GateResult( + broadcast=True, + lifecycle="update", + reason=f"update {source_val}|{external_id}: {', '.join(reason_parts)}", + data_patch=patch, + commit=_commit_update, + ) + + +def _do_commit( + committed_at: float, + source_val: str, + external_id: str, + magnitude: Optional[int], + delay_seconds: Optional[int], + icon_category: Optional[str], +) -> None: + """Idempotent UPSERT: arm last_broadcast_at on confirmed delivery.""" + try: + conn = get_db() + conn.execute( + "UPDATE traffic_events " + "SET last_broadcast_at=?, " + "first_broadcast_at=COALESCE(first_broadcast_at, ?), " + "last_broadcast_magnitude=?, " + "last_broadcast_delay_seconds=?, " + "last_broadcast_icon_category=? " + "WHERE source=? AND external_id=?", + ( + int(committed_at), int(committed_at), + magnitude, delay_seconds, icon_category, + source_val, external_id, + ), + ) + except Exception: + logger.exception( + "incident commit: persistence update failed for %s|%s", + source_val, external_id, + ) diff --git a/work/meshai/notifications/gating/nws.py b/work/meshai/notifications/gating/nws.py new file mode 100644 index 0000000..2b861c0 --- /dev/null +++ b/work/meshai/notifications/gating/nws.py @@ -0,0 +1,219 @@ +"""NWS weather-alert gating decider — Phase-2 implementation. + +Mirrors nws_handler gating logic EXACTLY: + - Tombstone: msgType in {Cancel, Expire} → suppress + - First-sighting (nws_alerts row is None): broadcast, prefix=""/"Update" + - Cold-start race (row exists, last_broadcast_at IS NULL): broadcast + - Dedup-window re-broadcast (>= duplicate_allowed_after_seconds): + broadcast, prefix="Active" + - Within dedup window: suppress + +decide(data, *, source, now) -> GateResult: + + Canonical data schema consumed: + cap_id, msgType, references, event, area_desc, geocoder, + cap_severity, expires_at, description, parameters, same_code, + certainty, category, headline + + Emitted data_patch keys: + _nws_prefix str — "", "Update", or "Active" + _severity_override str|None — "immediate" for warning categories + + commit(now: float) -> None: + Idempotent UPDATE: sets last_broadcast_at + first_broadcast_at on + the nws_alerts row. Safe to call N times (COALESCE preserves + first_broadcast_at on repeated calls). +""" +from __future__ import annotations + +import logging +from typing import Optional + +from meshai.adapter_config import adapter_config +from meshai.notifications.gating.base import GateResult +from meshai.persistence import get_db + +logger = logging.getLogger(__name__) + + +# ── Helpers ─────────────────────────────────────────────────────────────────── + +def _is_update(conn, references: list) -> bool: + """Return True if any CAP id in `references` was previously broadcast. + + Mirrors nws_handler._is_update exactly, operating on the pre-extracted + references list rather than the full `d` dict. + """ + if not references: + return False + ref_ids = [r["identifier"] for r in references + if isinstance(r, dict) and r.get("identifier")] + if not ref_ids: + return False + placeholders = ",".join("?" * len(ref_ids)) + row = conn.execute( + f"SELECT 1 FROM nws_alerts WHERE event_id IN ({placeholders}) " + "AND last_broadcast_at IS NOT NULL LIMIT 1", + ref_ids, + ).fetchone() + return row is not None + + +def _severity_override_for(category: str) -> Optional[str]: + """Map warning category to immediate severity override. + + Mirrors nws_handler's: + if category_raw.endswith("_warning") or category_raw.endswith(".warning"): + data["_severity_override"] = "immediate" + + Works for both Central category strings (e.g. "wx.alert.tornado_warning") + and native meshai category strings (e.g. "weather_warning"). + """ + cat = category or "" + if cat.endswith("_warning") or cat.endswith(".warning"): + return "immediate" + return None + + +def _make_commit(cap_id: str): + """Return an idempotent commit closure that arms last_broadcast_at.""" + def _commit(committed_at: float) -> None: + try: + c = get_db() + c.execute( + "UPDATE nws_alerts SET last_broadcast_at=?, " + "first_broadcast_at=COALESCE(first_broadcast_at, ?) " + "WHERE event_id=?", + (int(committed_at), int(committed_at), cap_id), + ) + except Exception: + logger.exception("nws commit: persistence update failed for %s", cap_id) + return _commit + + +# ── Public API ──────────────────────────────────────────────────────────────── + +def decide(data: dict, *, source: str, now: float) -> GateResult: + """Gate + first-sighting decision for NWS weather alerts. + + Parameters + ---------- + data: + Canonical Event.data dict (see module docstring for schema). + source: + Adapter source name, e.g. "nws". + now: + Current epoch — determinism seam, no time.time(). + + Returns + ------- + GateResult with broadcast=True/False and appropriate data_patch. + """ + cap_id = data.get("cap_id") + if not cap_id: + return GateResult( + broadcast=False, lifecycle="suppress", + reason="no cap_id in canonical data", + ) + + msg_type = data.get("msgType") or "" + references = data.get("references") or [] + expires_at = data.get("expires_at") + area_desc = data.get("area_desc") or "" + cap_severity = data.get("cap_severity") or "" + event_type = data.get("event") or "" + geocoder = data.get("geocoder") or {} + county = geocoder.get("county") or area_desc + state = geocoder.get("state") or "" + description = data.get("description") or "" + headline = data.get("headline") or "" + category = data.get("category") or "" + + # ── Tombstone: Cancel/Expire → suppress ─────────────────────────────────── + try: + tombstone_types = set(adapter_config.nws.tombstone_msgtypes) + except Exception: + tombstone_types = {"Cancel", "Expire"} + if msg_type in tombstone_types: + return GateResult( + broadcast=False, lifecycle="tombstone", + reason=f"msgType={msg_type!r} is a tombstone", + ) + + # ── Severity override for warning categories ─────────────────────────────── + sev_override = _severity_override_for(category) + + # ── Persistence ─────────────────────────────────────────────────────────── + try: + conn = get_db() + except Exception: + logger.exception("nws decide: persistence unavailable") + return GateResult( + broadcast=False, lifecycle="suppress", + reason="persistence unavailable", + ) + + row = conn.execute( + "SELECT last_broadcast_at FROM nws_alerts WHERE event_id=?", + (cap_id,), + ).fetchone() + + # ── First sighting ──────────────────────────────────────────────────────── + if row is None: + _prefix = "Update" if _is_update(conn, references) else "" + conn.execute( + "INSERT INTO nws_alerts(event_id, alert_type, severity, county, " + "state, headline, description, expires_at, first_seen_at, " + "last_broadcast_at) VALUES (?,?,?,?,?,?,?,?,?,?)", + (cap_id, event_type, cap_severity, county, state, + headline, description, + int(expires_at) if expires_at is not None else None, + int(now), None), + ) + return GateResult( + broadcast=True, + lifecycle="new", + reason=f"first sighting cap_id={cap_id}", + data_patch={ + "_nws_prefix": _prefix, + "_severity_override": sev_override, + }, + commit=_make_commit(cap_id), + ) + + # ── Cold-start race: row exists but broadcast was previously dropped ─────── + if row["last_broadcast_at"] is None: + _prefix = "Update" if _is_update(conn, references) else "" + return GateResult( + broadcast=True, + lifecycle="cold_start", + reason=f"cold-start race cap_id={cap_id}", + data_patch={ + "_nws_prefix": _prefix, + "_severity_override": sev_override, + }, + commit=_make_commit(cap_id), + ) + + # ── Dedup-window check ──────────────────────────────────────────────────── + last_bcast = float(row["last_broadcast_at"]) + try: + window_s = int(adapter_config.nws.duplicate_allowed_after_seconds) + except Exception: + window_s = 10800 # 3 hours default + if window_s > 0 and (now - last_bcast) >= window_s: + return GateResult( + broadcast=True, + lifecycle="rebroadcast", + reason=f"dedup window expired ({window_s}s) for cap_id={cap_id}", + data_patch={ + "_nws_prefix": "Active", + "_severity_override": sev_override, + }, + commit=_make_commit(cap_id), + ) + + return GateResult( + broadcast=False, lifecycle="suppress", + reason=f"within {window_s}s dedup window for cap_id={cap_id}", + ) diff --git a/work/tests/fixtures/nws/0000.json b/work/tests/fixtures/nws/0000.json new file mode 100644 index 0000000..f197617 --- /dev/null +++ b/work/tests/fixtures/nws/0000.json @@ -0,0 +1,118 @@ +{ + "envelope": { + "id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.7e35e313802db63f4b33f772a2597b4078600350.001.1", + "source": "central.echo6.co", + "type": "central.wx.alert.special_weather_statement.v1", + "time": "2026-06-27T23:15:00+00:00", + "datacontenttype": "application/json", + "centralschemaversion": "1.0", + "centralcategory": "wx.alert.special_weather_statement", + "centralseverity": 2, + "specversion": "1.0", + "data": { + "id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.7e35e313802db63f4b33f772a2597b4078600350.001.1", + "adapter": "nws", + "category": "wx.alert.special_weather_statement", + "time": "2026-06-27T23:15:00Z", + "expires": "2026-06-27T23:45:00Z", + "severity": 2, + "geo": { + "centroid": [ + -116.15599999999999, + 41.730000000000004 + ], + "bbox": [ + -116.32, + 41.66, + -115.93, + 41.86 + ], + "regions": [ + "US-NV-FIPS32007", + "US-NV-Z031" + ], + "primary_region": "US-NV-FIPS32007", + "geometry": null + }, + "data": { + "@id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.7e35e313802db63f4b33f772a2597b4078600350.001.1", + "@type": "wx:Alert", + "id": "urn:oid:2.49.0.1.840.0.7e35e313802db63f4b33f772a2597b4078600350.001.1", + "areaDesc": "Northern Elko County", + "geocode": { + "SAME": [ + "032007" + ], + "UGC": [ + "NVZ031" + ] + }, + "affectedZones": [ + "https://api.weather.gov/zones/forecast/NVZ031" + ], + "references": [], + "sent": "2026-06-27T16:15:00-07:00", + "effective": "2026-06-27T16:15:00-07:00", + "onset": "2026-06-27T16:15:00-07:00", + "expires": "2026-06-27T16:45:00-07:00", + "ends": null, + "status": "Actual", + "messageType": "Alert", + "category": "Met", + "severity": "Moderate", + "certainty": "Observed", + "urgency": "Expected", + "event": "Special Weather Statement", + "sender": "w-nws.webmaster@noaa.gov", + "senderName": "NWS Elko NV", + "headline": "Special Weather Statement issued June 27 at 4:15PM PDT by NWS Elko NV", + "description": "At 415 PM PDT, Doppler radar was tracking a strong thunderstorm\ncapable of producing a landspout 16 miles southwest of Owyhee, moving\neast at 25 mph.\n\nHAZARD...Landspouts, wind gusts up to 40 mph, and half inch hail.\n\nSOURCE...Radar indicated.\n\nIMPACT...Minor damage to outdoor objects is possible. Gusty winds\ncould knock down tree limbs and blow around unsecured\nobjects.\n\nLocations impacted include...\nMountain City.", + "instruction": "If outdoors, consider seeking shelter inside a building.", + "response": "Execute", + "note": null, + "parameters": { + "AWIPSidentifier": [ + "SPSLKN" + ], + "WMOidentifier": [ + "WWUS85 KLKN 272315 RRA" + ], + "NWSheadline": [ + "A STRONG THUNDERSTORM WILL IMPACT NORTHWESTERN ELKO COUNTY THROUGH 445 PM PDT" + ], + "eventMotionDescription": [ + "2026-06-27T23:15:00-00:00...storm...258DEG...20KT...41.73,-116.26" + ], + "maxWindGust": [ + "40 MPH" + ], + "maxHailSize": [ + "0.50" + ], + "BLOCKCHANNEL": [ + "EAS", + "NWEM", + "CMAS" + ], + "EAS-ORG": [ + "WXR" + ] + }, + "scope": "Public", + "code": "IPAWSv1.0", + "language": "en-US", + "web": "http://www.weather.gov", + "eventCode": { + "SAME": [ + "SPS" + ], + "NationalWeatherService": [ + "SPS" + ] + } + } + } + }, + "subject": "central.wx.alert.us.nv.county.fips32007", + "captured_epoch": 1783206513 +} \ No newline at end of file diff --git a/work/tests/fixtures/nws/0001.json b/work/tests/fixtures/nws/0001.json new file mode 100644 index 0000000..1c36f2c --- /dev/null +++ b/work/tests/fixtures/nws/0001.json @@ -0,0 +1,118 @@ +{ + "envelope": { + "id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.77e566f81234fa136014003ec08b0d965ee7b4dc.001.1", + "source": "central.echo6.co", + "type": "central.wx.alert.special_weather_statement.v1", + "time": "2026-06-27T23:16:00+00:00", + "datacontenttype": "application/json", + "centralschemaversion": "1.0", + "centralcategory": "wx.alert.special_weather_statement", + "centralseverity": 2, + "specversion": "1.0", + "data": { + "id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.77e566f81234fa136014003ec08b0d965ee7b4dc.001.1", + "adapter": "nws", + "category": "wx.alert.special_weather_statement", + "time": "2026-06-27T23:16:00Z", + "expires": "2026-06-27T23:45:00Z", + "severity": 2, + "geo": { + "centroid": [ + -116.13, + 41.762 + ], + "bbox": [ + -116.29, + 41.68, + -115.89, + 41.92 + ], + "regions": [ + "US-NV-FIPS32007", + "US-NV-Z031" + ], + "primary_region": "US-NV-FIPS32007", + "geometry": null + }, + "data": { + "@id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.77e566f81234fa136014003ec08b0d965ee7b4dc.001.1", + "@type": "wx:Alert", + "id": "urn:oid:2.49.0.1.840.0.77e566f81234fa136014003ec08b0d965ee7b4dc.001.1", + "areaDesc": "Northern Elko County", + "geocode": { + "SAME": [ + "032007" + ], + "UGC": [ + "NVZ031" + ] + }, + "affectedZones": [ + "https://api.weather.gov/zones/forecast/NVZ031" + ], + "references": [], + "sent": "2026-06-27T16:16:00-07:00", + "effective": "2026-06-27T16:16:00-07:00", + "onset": "2026-06-27T16:16:00-07:00", + "expires": "2026-06-27T16:45:00-07:00", + "ends": null, + "status": "Actual", + "messageType": "Alert", + "category": "Met", + "severity": "Moderate", + "certainty": "Observed", + "urgency": "Expected", + "event": "Special Weather Statement", + "sender": "w-nws.webmaster@noaa.gov", + "senderName": "NWS Elko NV", + "headline": "Special Weather Statement issued June 27 at 4:16PM PDT by NWS Elko NV", + "description": "At 416 PM PDT, Doppler radar was tracking a strong thunderstorm\ncapable of producing a landspout 13 miles southwest of Owyhee, moving\nnortheast at 25 mph.\n\nHAZARD...Landspouts, wind gusts up to 40 mph, and half inch hail.\n\nSOURCE...Radar indicated.\n\nIMPACT...Minor damage to outdoor objects is possible. Gusty winds\ncould knock down tree limbs and blow around unsecured\nobjects.\n\nLocations impacted include...\nMountain City.", + "instruction": "If outdoors, consider seeking shelter inside a building.", + "response": "Execute", + "note": null, + "parameters": { + "AWIPSidentifier": [ + "SPSLKN" + ], + "WMOidentifier": [ + "WWUS85 KLKN 272316" + ], + "NWSheadline": [ + "A STRONG THUNDERSTORM WILL IMPACT NORTH CENTRAL ELKO COUNTY THROUGH 445 PM PDT" + ], + "eventMotionDescription": [ + "2026-06-27T23:16:00-00:00...storm...241DEG...22KT...41.76,-116.21" + ], + "maxWindGust": [ + "40 MPH" + ], + "maxHailSize": [ + "0.50" + ], + "BLOCKCHANNEL": [ + "EAS", + "NWEM", + "CMAS" + ], + "EAS-ORG": [ + "WXR" + ] + }, + "scope": "Public", + "code": "IPAWSv1.0", + "language": "en-US", + "web": "http://www.weather.gov", + "eventCode": { + "SAME": [ + "SPS" + ], + "NationalWeatherService": [ + "SPS" + ] + } + } + } + }, + "subject": "central.wx.alert.us.nv.county.fips32007", + "captured_epoch": 1783206513 +} \ No newline at end of file diff --git a/work/tests/fixtures/nws/0002.json b/work/tests/fixtures/nws/0002.json new file mode 100644 index 0000000..a083c1b --- /dev/null +++ b/work/tests/fixtures/nws/0002.json @@ -0,0 +1,128 @@ +{ + "envelope": { + "id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.dfad3eef33e0c76c3d80f75f404dcd3e2e8b00a8.001.1", + "source": "central.echo6.co", + "type": "central.wx.alert.special_weather_statement.v1", + "time": "2026-06-30T20:09:00+00:00", + "datacontenttype": "application/json", + "centralschemaversion": "1.0", + "centralcategory": "wx.alert.special_weather_statement", + "centralseverity": 2, + "specversion": "1.0", + "data": { + "id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.dfad3eef33e0c76c3d80f75f404dcd3e2e8b00a8.001.1", + "adapter": "nws", + "category": "wx.alert.special_weather_statement", + "time": "2026-06-30T20:09:00Z", + "expires": "2026-06-30T21:00:00Z", + "severity": 2, + "geo": { + "centroid": [ + -113.244, + 43.95 + ], + "bbox": [ + -113.38, + 43.81, + -113.03, + 44.09 + ], + "regions": [ + "US-ID-FIPS16023", + "US-ID-FIPS16033", + "US-ID-FIPS16037", + "US-ID-Z067", + "US-ID-Z068", + "US-ID-Z069" + ], + "primary_region": "US-ID-FIPS16023", + "geometry": null + }, + "data": { + "@id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.dfad3eef33e0c76c3d80f75f404dcd3e2e8b00a8.001.1", + "@type": "wx:Alert", + "id": "urn:oid:2.49.0.1.840.0.dfad3eef33e0c76c3d80f75f404dcd3e2e8b00a8.001.1", + "areaDesc": "Beaverhead/Lemhi Highlands; Lost River Valleys; Lost River Range", + "geocode": { + "SAME": [ + "016023", + "016033", + "016037" + ], + "UGC": [ + "IDZ067", + "IDZ068", + "IDZ069" + ] + }, + "affectedZones": [ + "https://api.weather.gov/zones/forecast/IDZ067", + "https://api.weather.gov/zones/forecast/IDZ068", + "https://api.weather.gov/zones/forecast/IDZ069" + ], + "references": [], + "sent": "2026-06-30T14:09:00-06:00", + "effective": "2026-06-30T14:09:00-06:00", + "onset": "2026-06-30T14:09:00-06:00", + "expires": "2026-06-30T15:00:00-06:00", + "ends": null, + "status": "Actual", + "messageType": "Alert", + "category": "Met", + "severity": "Moderate", + "certainty": "Observed", + "urgency": "Expected", + "event": "Special Weather Statement", + "sender": "w-nws.webmaster@noaa.gov", + "senderName": "NWS Pocatello ID", + "headline": "Special Weather Statement issued June 30 at 2:09PM MDT by NWS Pocatello ID", + "description": "At 209 PM MDT, Doppler radar was tracking a strong thunderstorm 13\nmiles northeast of Darlington, or 17 miles east of Mackay, moving\neast at 5 mph.\n\nHAZARD...Wind gusts in excess of 45 mph and half inch hail.\n\nSOURCE...Radar indicated.\n\nIMPACT...Gusty winds could knock down tree limbs and blow around\nunsecured objects. Minor hail damage to outdoor objects is\npossible.\n\nThis storm will remain over mainly rural areas of north central Butte\nCounty.", + "instruction": "If outdoors, consider seeking shelter inside a building.", + "response": "Execute", + "note": null, + "parameters": { + "AWIPSidentifier": [ + "SPSPIH" + ], + "WMOidentifier": [ + "WWUS85 KPIH 302009" + ], + "NWSheadline": [ + "A STRONG THUNDERSTORM WILL IMPACT NORTH CENTRAL BUTTE COUNTY THROUGH 300 PM MDT" + ], + "eventMotionDescription": [ + "2026-06-30T20:09:00-00:00...storm...275DEG...5KT...43.98,-113.28" + ], + "maxWindGust": [ + "45 MPH" + ], + "maxHailSize": [ + "0.50" + ], + "BLOCKCHANNEL": [ + "EAS", + "NWEM", + "CMAS" + ], + "EAS-ORG": [ + "WXR" + ] + }, + "scope": "Public", + "code": "IPAWSv1.0", + "language": "en-US", + "web": "http://www.weather.gov", + "eventCode": { + "SAME": [ + "SPS" + ], + "NationalWeatherService": [ + "SPS" + ] + } + } + } + }, + "subject": "central.wx.alert.us.id.county.fips16023", + "captured_epoch": 1783206513 +} \ No newline at end of file diff --git a/work/tests/fixtures/nws/0003.json b/work/tests/fixtures/nws/0003.json new file mode 100644 index 0000000..42b3e6d --- /dev/null +++ b/work/tests/fixtures/nws/0003.json @@ -0,0 +1,122 @@ +{ + "envelope": { + "id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.c72d73e7d5f08fab03d9bc5e6fd22abd3158545a.001.1", + "source": "central.echo6.co", + "type": "central.wx.alert.special_weather_statement.v1", + "time": "2026-06-30T21:22:00+00:00", + "datacontenttype": "application/json", + "centralschemaversion": "1.0", + "centralcategory": "wx.alert.special_weather_statement", + "centralseverity": 2, + "specversion": "1.0", + "data": { + "id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.c72d73e7d5f08fab03d9bc5e6fd22abd3158545a.001.1", + "adapter": "nws", + "category": "wx.alert.special_weather_statement", + "time": "2026-06-30T21:22:00Z", + "expires": "2026-06-30T21:45:00Z", + "severity": 2, + "geo": { + "centroid": [ + -113.05, + 42.284 + ], + "bbox": [ + -113.26, + 42.18, + -112.76, + 42.48 + ], + "regions": [ + "US-ID-FIPS16031", + "US-ID-FIPS16071", + "US-ID-FIPS16077", + "US-ID-Z057" + ], + "primary_region": "US-ID-FIPS16031", + "geometry": null + }, + "data": { + "@id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.c72d73e7d5f08fab03d9bc5e6fd22abd3158545a.001.1", + "@type": "wx:Alert", + "id": "urn:oid:2.49.0.1.840.0.c72d73e7d5f08fab03d9bc5e6fd22abd3158545a.001.1", + "areaDesc": "Raft River Region", + "geocode": { + "SAME": [ + "016031", + "016071", + "016077" + ], + "UGC": [ + "IDZ057" + ] + }, + "affectedZones": [ + "https://api.weather.gov/zones/forecast/IDZ057" + ], + "references": [], + "sent": "2026-06-30T15:22:00-06:00", + "effective": "2026-06-30T15:22:00-06:00", + "onset": "2026-06-30T15:22:00-06:00", + "expires": "2026-06-30T15:45:00-06:00", + "ends": null, + "status": "Actual", + "messageType": "Alert", + "category": "Met", + "severity": "Moderate", + "certainty": "Observed", + "urgency": "Expected", + "event": "Special Weather Statement", + "sender": "w-nws.webmaster@noaa.gov", + "senderName": "NWS Pocatello ID", + "headline": "Special Weather Statement issued June 30 at 3:22PM MDT by NWS Pocatello ID", + "description": "At 321 PM MDT, Doppler radar was tracking a strong thunderstorm 10\nmiles northwest of Juniper, or 14 miles east of Malta, moving\nnortheast at 20 mph.\n\nHAZARD...Wind gusts in excess of 45 mph and pea size hail.\n\nSOURCE...Radar indicated.\n\nIMPACT...Gusty winds could knock down tree limbs and blow around\nunsecured objects. Minor hail damage to outdoor objects is\npossible.\n\nLocations impacted include...\nSweetzer Summit and Sublett Reservoir.", + "instruction": "If outdoors, consider seeking shelter inside a building.", + "response": "Execute", + "note": null, + "parameters": { + "AWIPSidentifier": [ + "SPSPIH" + ], + "WMOidentifier": [ + "WWUS85 KPIH 302122" + ], + "NWSheadline": [ + "A STRONG THUNDERSTORM WILL IMPACT WEST CENTRAL ONEIDA...SOUTH CENTRAL POWER AND EAST CENTRAL CASSIA COUNTIES THROUGH 345 PM MDT" + ], + "eventMotionDescription": [ + "2026-06-30T21:21:00-00:00...storm...242DEG...16KT...42.3,-113.09" + ], + "maxWindGust": [ + "45 MPH" + ], + "maxHailSize": [ + "0.25" + ], + "BLOCKCHANNEL": [ + "EAS", + "NWEM", + "CMAS" + ], + "EAS-ORG": [ + "WXR" + ] + }, + "scope": "Public", + "code": "IPAWSv1.0", + "language": "en-US", + "web": "http://www.weather.gov", + "eventCode": { + "SAME": [ + "SPS" + ], + "NationalWeatherService": [ + "SPS" + ] + } + } + } + }, + "subject": "central.wx.alert.us.id.county.fips16031", + "captured_epoch": 1783206513 +} \ No newline at end of file diff --git a/work/tests/fixtures/nws/0004.json b/work/tests/fixtures/nws/0004.json new file mode 100644 index 0000000..a9a27ee --- /dev/null +++ b/work/tests/fixtures/nws/0004.json @@ -0,0 +1,141 @@ +{ + "envelope": { + "id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.26924936ea78ca9810c5e08d8dc7b25977e593dd.001.1", + "source": "central.echo6.co", + "type": "central.wx.alert.special_weather_statement.v1", + "time": "2026-06-30T22:36:00+00:00", + "datacontenttype": "application/json", + "centralschemaversion": "1.0", + "centralcategory": "wx.alert.special_weather_statement", + "centralseverity": 2, + "specversion": "1.0", + "data": { + "id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.26924936ea78ca9810c5e08d8dc7b25977e593dd.001.1", + "adapter": "nws", + "category": "wx.alert.special_weather_statement", + "time": "2026-06-30T22:36:00Z", + "expires": "2026-06-30T23:00:00Z", + "severity": 2, + "geo": { + "centroid": [ + -112.67999999999999, + 42.708 + ], + "bbox": [ + -113.02, + 42.55, + -112.46, + 42.94 + ], + "regions": [ + "US-ID-FIPS16005", + "US-ID-FIPS16011", + "US-ID-FIPS16013", + "US-ID-FIPS16029", + "US-ID-FIPS16031", + "US-ID-FIPS16067", + "US-ID-FIPS16071", + "US-ID-FIPS16077", + "US-ID-Z054", + "US-ID-Z055", + "US-ID-Z057", + "US-ID-Z058" + ], + "primary_region": "US-ID-FIPS16005", + "geometry": null + }, + "data": { + "@id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.26924936ea78ca9810c5e08d8dc7b25977e593dd.001.1", + "@type": "wx:Alert", + "id": "urn:oid:2.49.0.1.840.0.26924936ea78ca9810c5e08d8dc7b25977e593dd.001.1", + "areaDesc": "Lower Snake River Plain; Eastern Magic Valley; Raft River Region; Marsh and Arbon Highlands", + "geocode": { + "SAME": [ + "016005", + "016011", + "016077", + "016013", + "016031", + "016067", + "016071", + "016029" + ], + "UGC": [ + "IDZ054", + "IDZ055", + "IDZ057", + "IDZ058" + ] + }, + "affectedZones": [ + "https://api.weather.gov/zones/forecast/IDZ054", + "https://api.weather.gov/zones/forecast/IDZ055", + "https://api.weather.gov/zones/forecast/IDZ057", + "https://api.weather.gov/zones/forecast/IDZ058" + ], + "references": [], + "sent": "2026-06-30T16:36:00-06:00", + "effective": "2026-06-30T16:36:00-06:00", + "onset": "2026-06-30T16:36:00-06:00", + "expires": "2026-06-30T17:00:00-06:00", + "ends": null, + "status": "Actual", + "messageType": "Alert", + "category": "Met", + "severity": "Moderate", + "certainty": "Observed", + "urgency": "Expected", + "event": "Special Weather Statement", + "sender": "w-nws.webmaster@noaa.gov", + "senderName": "NWS Pocatello ID", + "headline": "Special Weather Statement issued June 30 at 4:36PM MDT by NWS Pocatello ID", + "description": "At 436 PM MDT, Doppler radar was tracking a cluster of strong\nthunderstorms near Fort Hall Bannock Peak, or 13 miles southeast of\nNeeley, moving north at 30 mph.\n\nHAZARD...Wind gusts in excess of 35 mph.\n\nSOURCE...Radar indicated.\n\nIMPACT...Gusty winds could knock down tree limbs and blow around\nunsecured objects.\n\nLocations impacted include...\nWestern Pocatello, American Falls, Neeley, American Falls Reservoir,\nChubbuck, Rockland, Fort Hall Bannock Peak, Fort Hall Bannock Creek\nLodge, Pocatello Airport, and Pauline.", + "instruction": "If outdoors, consider seeking shelter inside a building.\n\nIf on or near American Falls Reservoir, get out of the water and\nmove indoors or inside a vehicle. Strong winds will create rough\nchop.\nRemember, lightning can strike out to 10 miles from the parent\nthunderstorm. If you can hear thunder, you are close enough to be\nstruck by lightning. Move to safe shelter now! Do not be caught on\nthe water in a thunderstorm.", + "response": "Execute", + "note": null, + "parameters": { + "AWIPSidentifier": [ + "SPSPIH" + ], + "WMOidentifier": [ + "WWUS85 KPIH 302236" + ], + "NWSheadline": [ + "STRONG THUNDERSTORMS WILL IMPACT NORTHWESTERN BANNOCK...CENTRAL POWER AND SOUTHWESTERN BINGHAM COUNTIES THROUGH 500 PM MDT" + ], + "eventMotionDescription": [ + "2026-06-30T22:36:00-00:00...storm...184DEG...24KT...42.66,-112.65" + ], + "maxWindGust": [ + "35 MPH" + ], + "maxHailSize": [ + "0.00" + ], + "BLOCKCHANNEL": [ + "EAS", + "NWEM", + "CMAS" + ], + "EAS-ORG": [ + "WXR" + ] + }, + "scope": "Public", + "code": "IPAWSv1.0", + "language": "en-US", + "web": "http://www.weather.gov", + "eventCode": { + "SAME": [ + "SPS" + ], + "NationalWeatherService": [ + "SPS" + ] + } + } + } + }, + "subject": "central.wx.alert.us.id.county.fips16005", + "captured_epoch": 1783206513 +} \ No newline at end of file diff --git a/work/tests/fixtures/nws/0005.json b/work/tests/fixtures/nws/0005.json new file mode 100644 index 0000000..0643a84 --- /dev/null +++ b/work/tests/fixtures/nws/0005.json @@ -0,0 +1,127 @@ +{ + "envelope": { + "id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.25f30e5e74ca0381402bc7a82d5fc80b1b45acfd.001.1", + "source": "central.echo6.co", + "type": "central.wx.alert.special_weather_statement.v1", + "time": "2026-06-30T23:07:00+00:00", + "datacontenttype": "application/json", + "centralschemaversion": "1.0", + "centralcategory": "wx.alert.special_weather_statement", + "centralseverity": 2, + "specversion": "1.0", + "data": { + "id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.25f30e5e74ca0381402bc7a82d5fc80b1b45acfd.001.1", + "adapter": "nws", + "category": "wx.alert.special_weather_statement", + "time": "2026-06-30T23:07:00Z", + "expires": "2026-06-30T23:45:00Z", + "severity": 2, + "geo": { + "centroid": [ + -112.52000000000001, + 42.730000000000004 + ], + "bbox": [ + -112.73, + 42.57, + -112.2, + 42.97 + ], + "regions": [ + "US-ID-FIPS16005", + "US-ID-FIPS16011", + "US-ID-FIPS16029", + "US-ID-FIPS16077", + "US-ID-Z054", + "US-ID-Z058" + ], + "primary_region": "US-ID-FIPS16005", + "geometry": null + }, + "data": { + "@id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.25f30e5e74ca0381402bc7a82d5fc80b1b45acfd.001.1", + "@type": "wx:Alert", + "id": "urn:oid:2.49.0.1.840.0.25f30e5e74ca0381402bc7a82d5fc80b1b45acfd.001.1", + "areaDesc": "Lower Snake River Plain; Marsh and Arbon Highlands", + "geocode": { + "SAME": [ + "016005", + "016011", + "016077", + "016029" + ], + "UGC": [ + "IDZ054", + "IDZ058" + ] + }, + "affectedZones": [ + "https://api.weather.gov/zones/forecast/IDZ054", + "https://api.weather.gov/zones/forecast/IDZ058" + ], + "references": [], + "sent": "2026-06-30T17:07:00-06:00", + "effective": "2026-06-30T17:07:00-06:00", + "onset": "2026-06-30T17:07:00-06:00", + "expires": "2026-06-30T17:45:00-06:00", + "ends": null, + "status": "Actual", + "messageType": "Alert", + "category": "Met", + "severity": "Moderate", + "certainty": "Observed", + "urgency": "Expected", + "event": "Special Weather Statement", + "sender": "w-nws.webmaster@noaa.gov", + "senderName": "NWS Pocatello ID", + "headline": "Special Weather Statement issued June 30 at 5:07PM MDT by NWS Pocatello ID", + "description": "At 507 PM MDT, Doppler radar was tracking a strong thunderstorm near\nFort Hall Bannock Creek Lodge, or 12 miles southwest of Pocatello,\nmoving northeast at 20 mph.\n\nHAZARD...Wind gusts in excess of 35 mph and pea size hail.\n\nSOURCE...Radar indicated.\n\nIMPACT...Gusty winds could knock down tree limbs and blow around\nunsecured objects. Minor hail damage to outdoor objects is\npossible.\n\nLocations impacted include...\nPocatello, eastern American Falls Reservoir, Chubbuck, Inkom,\nPortneuf Gap, Fort Hall Bannock Creek Lodge, Pocatello Airport, and\nMink Creek Pass.", + "instruction": "If outdoors, consider seeking shelter inside a building.\n\nIf on or near American Falls Reservoir, get out of the water and\nmove indoors or inside a vehicle. Strong winds will create rough\nchop.\nRemember, lightning can strike out to 10 miles from the parent\nthunderstorm. If you can hear thunder, you are close enough to be\nstruck by lightning. Move to safe shelter now! Do not be caught on\nthe water in a thunderstorm.", + "response": "Execute", + "note": null, + "parameters": { + "AWIPSidentifier": [ + "SPSPIH" + ], + "WMOidentifier": [ + "WWUS85 KPIH 302307" + ], + "NWSheadline": [ + "A STRONG THUNDERSTORM WILL IMPACT NORTHWESTERN BANNOCK AND NORTHEASTERN POWER COUNTIES THROUGH 545 PM MDT" + ], + "eventMotionDescription": [ + "2026-06-30T23:07:00-00:00...storm...212DEG...17KT...42.71,-112.58" + ], + "maxWindGust": [ + "35 MPH" + ], + "maxHailSize": [ + "0.25" + ], + "BLOCKCHANNEL": [ + "EAS", + "NWEM", + "CMAS" + ], + "EAS-ORG": [ + "WXR" + ] + }, + "scope": "Public", + "code": "IPAWSv1.0", + "language": "en-US", + "web": "http://www.weather.gov", + "eventCode": { + "SAME": [ + "SPS" + ], + "NationalWeatherService": [ + "SPS" + ] + } + } + } + }, + "subject": "central.wx.alert.us.id.county.fips16005", + "captured_epoch": 1783206513 +} \ No newline at end of file diff --git a/work/tests/fixtures/nws/0006.json b/work/tests/fixtures/nws/0006.json new file mode 100644 index 0000000..df2ab16 --- /dev/null +++ b/work/tests/fixtures/nws/0006.json @@ -0,0 +1,129 @@ +{ + "envelope": { + "id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.4a8bcccd7e28d4b55d4612e168328b9ed78fdf84.001.1", + "source": "central.echo6.co", + "type": "central.wx.alert.special_weather_statement.v1", + "time": "2026-06-30T23:39:00+00:00", + "datacontenttype": "application/json", + "centralschemaversion": "1.0", + "centralcategory": "wx.alert.special_weather_statement", + "centralseverity": 2, + "specversion": "1.0", + "data": { + "id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.4a8bcccd7e28d4b55d4612e168328b9ed78fdf84.001.1", + "adapter": "nws", + "category": "wx.alert.special_weather_statement", + "time": "2026-06-30T23:39:00Z", + "expires": "2026-07-01T00:30:00Z", + "severity": 2, + "geo": { + "centroid": [ + -111.854, + 42.99400000000001 + ], + "bbox": [ + -112.08, + 42.84, + -111.52, + 43.27 + ], + "regions": [ + "US-ID-FIPS16005", + "US-ID-FIPS16011", + "US-ID-FIPS16019", + "US-ID-FIPS16029", + "US-ID-FIPS16077", + "US-ID-Z058", + "US-ID-Z062" + ], + "primary_region": "US-ID-FIPS16005", + "geometry": null + }, + "data": { + "@id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.4a8bcccd7e28d4b55d4612e168328b9ed78fdf84.001.1", + "@type": "wx:Alert", + "id": "urn:oid:2.49.0.1.840.0.4a8bcccd7e28d4b55d4612e168328b9ed78fdf84.001.1", + "areaDesc": "Marsh and Arbon Highlands; Blackfoot Mountains", + "geocode": { + "SAME": [ + "016005", + "016011", + "016029", + "016077", + "016019" + ], + "UGC": [ + "IDZ058", + "IDZ062" + ] + }, + "affectedZones": [ + "https://api.weather.gov/zones/forecast/IDZ058", + "https://api.weather.gov/zones/forecast/IDZ062" + ], + "references": [], + "sent": "2026-06-30T17:39:00-06:00", + "effective": "2026-06-30T17:39:00-06:00", + "onset": "2026-06-30T17:39:00-06:00", + "expires": "2026-06-30T18:30:00-06:00", + "ends": null, + "status": "Actual", + "messageType": "Alert", + "category": "Met", + "severity": "Moderate", + "certainty": "Observed", + "urgency": "Expected", + "event": "Special Weather Statement", + "sender": "w-nws.webmaster@noaa.gov", + "senderName": "NWS Pocatello ID", + "headline": "Special Weather Statement issued June 30 at 5:39PM MDT by NWS Pocatello ID", + "description": "At 538 PM MDT, Doppler radar was tracking a strong thunderstorm near\nChesterfield Reservoir, moving northeast at 15 mph.\n\nHAZARD...Wind gusts in excess of 35 mph and pea size hail.\n\nSOURCE...Radar indicated.\n\nIMPACT...Gusty winds could knock down tree limbs and blow around\nunsecured objects. Minor hail damage to outdoor objects is\npossible.\n\nLocations impacted include...\nChesterfield Reservoir, northern Blackfoot Reservoir, Chesterfield,\nCutthroat Trout Campground, and Trail Creek Campground.", + "instruction": "If outdoors, consider seeking shelter inside a building.\n\nIf on or near Blackfoot Reservoir, get out of the water and move\nindoors or inside a vehicle. Strong winds will create rough chop.\nRemember, lightning can strike out to 10 miles from the parent\nthunderstorm. If you can hear thunder, you are close enough to be\nstruck by lightning. Move to safe shelter now! Do not be caught on\nthe water in a thunderstorm.", + "response": "Execute", + "note": null, + "parameters": { + "AWIPSidentifier": [ + "SPSPIH" + ], + "WMOidentifier": [ + "WWUS85 KPIH 302339" + ], + "NWSheadline": [ + "A STRONG THUNDERSTORM WILL IMPACT NORTHWESTERN CARIBOU...NORTHERN BANNOCK...SOUTHERN BONNEVILLE AND SOUTHEASTERN BINGHAM COUNTIES THROUGH 630 PM MDT" + ], + "eventMotionDescription": [ + "2026-06-30T23:38:00-00:00...storm...236DEG...15KT...42.99,-111.93" + ], + "maxWindGust": [ + "35 MPH" + ], + "maxHailSize": [ + "0.25" + ], + "BLOCKCHANNEL": [ + "EAS", + "NWEM", + "CMAS" + ], + "EAS-ORG": [ + "WXR" + ] + }, + "scope": "Public", + "code": "IPAWSv1.0", + "language": "en-US", + "web": "http://www.weather.gov", + "eventCode": { + "SAME": [ + "SPS" + ], + "NationalWeatherService": [ + "SPS" + ] + } + } + } + }, + "subject": "central.wx.alert.us.id.county.fips16005", + "captured_epoch": 1783206513 +} \ No newline at end of file diff --git a/work/tests/fixtures/nws/0007.json b/work/tests/fixtures/nws/0007.json new file mode 100644 index 0000000..3890dcf --- /dev/null +++ b/work/tests/fixtures/nws/0007.json @@ -0,0 +1,127 @@ +{ + "envelope": { + "id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.2dede2c223ae88415b686fbbe38a0834933ae5a6.001.1", + "source": "central.echo6.co", + "type": "central.wx.alert.special_weather_statement.v1", + "time": "2026-07-01T21:26:00+00:00", + "datacontenttype": "application/json", + "centralschemaversion": "1.0", + "centralcategory": "wx.alert.special_weather_statement", + "centralseverity": 2, + "specversion": "1.0", + "data": { + "id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.2dede2c223ae88415b686fbbe38a0834933ae5a6.001.1", + "adapter": "nws", + "category": "wx.alert.special_weather_statement", + "time": "2026-07-01T21:26:00Z", + "expires": "2026-07-01T22:00:00Z", + "severity": 2, + "geo": { + "centroid": [ + -114.1211111111111, + 42.31777777777778 + ], + "bbox": [ + -114.29, + 42.0, + -113.71, + 42.52 + ], + "regions": [ + "US-ID-FIPS16013", + "US-ID-FIPS16031", + "US-ID-FIPS16067", + "US-ID-FIPS16077", + "US-ID-Z055", + "US-ID-Z056" + ], + "primary_region": "US-ID-FIPS16013", + "geometry": null + }, + "data": { + "@id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.2dede2c223ae88415b686fbbe38a0834933ae5a6.001.1", + "@type": "wx:Alert", + "id": "urn:oid:2.49.0.1.840.0.2dede2c223ae88415b686fbbe38a0834933ae5a6.001.1", + "areaDesc": "Eastern Magic Valley; Southern Hills/Albion Mountains", + "geocode": { + "SAME": [ + "016013", + "016031", + "016067", + "016077" + ], + "UGC": [ + "IDZ055", + "IDZ056" + ] + }, + "affectedZones": [ + "https://api.weather.gov/zones/forecast/IDZ055", + "https://api.weather.gov/zones/forecast/IDZ056" + ], + "references": [], + "sent": "2026-07-01T15:26:00-06:00", + "effective": "2026-07-01T15:26:00-06:00", + "onset": "2026-07-01T15:26:00-06:00", + "expires": "2026-07-01T16:00:00-06:00", + "ends": null, + "status": "Actual", + "messageType": "Alert", + "category": "Met", + "severity": "Moderate", + "certainty": "Observed", + "urgency": "Expected", + "event": "Special Weather Statement", + "sender": "w-nws.webmaster@noaa.gov", + "senderName": "NWS Pocatello ID", + "headline": "Special Weather Statement issued July 1 at 3:26PM MDT by NWS Pocatello ID", + "description": "At 326 PM MDT, Doppler radar was tracking a strong thunderstorm 11\nmiles west of Oakley Reservoir, moving northeast at 30 mph.\n\nHAZARD...Wind gusts in excess of 45 mph and nickel size hail.\n\nSOURCE...Radar indicated.\n\nIMPACT...Gusty winds could knock down tree limbs and blow around\nunsecured objects. Minor hail damage to outdoor objects is\npossible.\n\nLocations impacted include...\nOakley Reservoir, Oakley, and Bostetter Ranger Station.", + "instruction": "If outdoors, consider seeking shelter inside a building.\n\nFrequent cloud to ground lightning is occurring with this storm.\nLightning can strike 10 miles away from a thunderstorm. Seek a safe\nshelter inside a building or vehicle.", + "response": "Execute", + "note": null, + "parameters": { + "AWIPSidentifier": [ + "SPSPIH" + ], + "WMOidentifier": [ + "WWUS85 KPIH 012126" + ], + "NWSheadline": [ + "A STRONG THUNDERSTORM WILL IMPACT SOUTHWESTERN CASSIA COUNTY THROUGH 400 PM MDT" + ], + "eventMotionDescription": [ + "2026-07-01T21:26:00-00:00...storm...221DEG...24KT...42.19,-114.15" + ], + "maxWindGust": [ + "45 MPH" + ], + "maxHailSize": [ + "0.88" + ], + "BLOCKCHANNEL": [ + "EAS", + "NWEM", + "CMAS" + ], + "EAS-ORG": [ + "WXR" + ] + }, + "scope": "Public", + "code": "IPAWSv1.0", + "language": "en-US", + "web": "http://www.weather.gov", + "eventCode": { + "SAME": [ + "SPS" + ], + "NationalWeatherService": [ + "SPS" + ] + } + } + } + }, + "subject": "central.wx.alert.us.id.county.fips16013", + "captured_epoch": 1783206513 +} \ No newline at end of file diff --git a/work/tests/fixtures/nws/0008.json b/work/tests/fixtures/nws/0008.json new file mode 100644 index 0000000..1c7e53a --- /dev/null +++ b/work/tests/fixtures/nws/0008.json @@ -0,0 +1,127 @@ +{ + "envelope": { + "id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.c6add6b54c87dec0372f5cac3b3e3da2c46c6320.001.1", + "source": "central.echo6.co", + "type": "central.wx.alert.severe_thunderstorm_warning.v1", + "time": "2026-07-01T21:50:00+00:00", + "datacontenttype": "application/json", + "centralschemaversion": "1.0", + "centralcategory": "wx.alert.severe_thunderstorm_warning", + "centralseverity": 3, + "specversion": "1.0", + "data": { + "id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.c6add6b54c87dec0372f5cac3b3e3da2c46c6320.001.1", + "adapter": "nws", + "category": "wx.alert.severe_thunderstorm_warning", + "time": "2026-07-01T21:50:00Z", + "expires": "2026-07-01T22:30:00Z", + "severity": 3, + "geo": { + "centroid": [ + -113.93199999999999, + 42.224000000000004 + ], + "bbox": [ + -114.16, + 42.09, + -113.58, + 42.47 + ], + "regions": [ + "US-ID-C031", + "US-ID-FIPS16031" + ], + "primary_region": "US-ID-C031", + "geometry": null + }, + "data": { + "@id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.c6add6b54c87dec0372f5cac3b3e3da2c46c6320.001.1", + "@type": "wx:Alert", + "id": "urn:oid:2.49.0.1.840.0.c6add6b54c87dec0372f5cac3b3e3da2c46c6320.001.1", + "areaDesc": "Cassia, ID", + "geocode": { + "SAME": [ + "016031" + ], + "UGC": [ + "IDC031" + ] + }, + "affectedZones": [ + "https://api.weather.gov/zones/county/IDC031" + ], + "references": [], + "sent": "2026-07-01T15:50:00-06:00", + "effective": "2026-07-01T15:50:00-06:00", + "onset": "2026-07-01T15:50:00-06:00", + "expires": "2026-07-01T16:30:00-06:00", + "ends": "2026-07-01T16:30:00-06:00", + "status": "Actual", + "messageType": "Alert", + "category": "Met", + "severity": "Severe", + "certainty": "Observed", + "urgency": "Immediate", + "event": "Severe Thunderstorm Warning", + "sender": "w-nws.webmaster@noaa.gov", + "senderName": "NWS Pocatello ID", + "headline": "Severe Thunderstorm Warning issued July 1 at 3:50PM MDT until July 1 at 4:30PM MDT by NWS Pocatello ID", + "description": "SVRPIH\n\nThe National Weather Service in Pocatello has issued a\n\n* Severe Thunderstorm Warning for...\nSouthwestern Cassia County in southeastern Idaho...\n\n* Until 430 PM MDT.\n\n* At 350 PM MDT, a severe thunderstorm was located near Oakley\nReservoir, moving northeast at 20 mph.\n\nHAZARD...Quarter size hail.\n\nSOURCE...Radar indicated.\n\nIMPACT...Damage to vehicles is expected.\n\n* Locations impacted include...\nOakley Reservoir and Oakley.", + "instruction": "For your protection move to an interior room on the lowest floor of a\nbuilding.\n\nTorrential rainfall is occurring with this storm, and may lead to\nflash flooding. Do not drive your vehicle through flooded roadways.", + "response": "Shelter", + "note": null, + "parameters": { + "AWIPSidentifier": [ + "SVRPIH" + ], + "WMOidentifier": [ + "WUUS55 KPIH 012150" + ], + "eventMotionDescription": [ + "2026-07-01T21:50:00-00:00...storm...246DEG...17KT...42.22,-114.01" + ], + "windThreat": [ + "RADAR INDICATED" + ], + "maxWindGust": [ + "Up to 50 MPH" + ], + "hailThreat": [ + "RADAR INDICATED" + ], + "maxHailSize": [ + "1.00" + ], + "BLOCKCHANNEL": [ + "EAS", + "NWEM", + "CMAS" + ], + "EAS-ORG": [ + "WXR" + ], + "VTEC": [ + "/O.NEW.KPIH.SV.W.0027.260701T2150Z-260701T2230Z/" + ], + "eventEndingTime": [ + "2026-07-01T16:30:00-06:00" + ] + }, + "scope": "Public", + "code": "IPAWSv1.0", + "language": "en-US", + "web": "http://www.weather.gov", + "eventCode": { + "SAME": [ + "SVR" + ], + "NationalWeatherService": [ + "SVW" + ] + } + } + } + }, + "subject": "central.wx.alert.us.id.county.c031", + "captured_epoch": 1783206513 +} \ No newline at end of file diff --git a/work/tests/fixtures/nws/0009.json b/work/tests/fixtures/nws/0009.json new file mode 100644 index 0000000..2067fef --- /dev/null +++ b/work/tests/fixtures/nws/0009.json @@ -0,0 +1,137 @@ +{ + "envelope": { + "id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.d854d25f06637c7ac4dc7614f2d6254a7f227873.001.1", + "source": "central.echo6.co", + "type": "central.wx.alert.severe_thunderstorm_warning.v1", + "time": "2026-07-01T22:04:00+00:00", + "datacontenttype": "application/json", + "centralschemaversion": "1.0", + "centralcategory": "wx.alert.severe_thunderstorm_warning", + "centralseverity": 3, + "specversion": "1.0", + "data": { + "id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.d854d25f06637c7ac4dc7614f2d6254a7f227873.001.1", + "adapter": "nws", + "category": "wx.alert.severe_thunderstorm_warning", + "time": "2026-07-01T22:04:00Z", + "expires": "2026-07-01T22:30:00Z", + "severity": 3, + "geo": { + "centroid": [ + -113.902, + 42.257999999999996 + ], + "bbox": [ + -114.11, + 42.16, + -113.58, + 42.47 + ], + "regions": [ + "US-ID-C031", + "US-ID-FIPS16031" + ], + "primary_region": "US-ID-C031", + "geometry": null + }, + "data": { + "@id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.d854d25f06637c7ac4dc7614f2d6254a7f227873.001.1", + "@type": "wx:Alert", + "id": "urn:oid:2.49.0.1.840.0.d854d25f06637c7ac4dc7614f2d6254a7f227873.001.1", + "areaDesc": "Cassia, ID", + "geocode": { + "SAME": [ + "016031" + ], + "UGC": [ + "IDC031" + ] + }, + "affectedZones": [ + "https://api.weather.gov/zones/county/IDC031" + ], + "references": [ + { + "@id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.c6add6b54c87dec0372f5cac3b3e3da2c46c6320.001.1", + "identifier": "urn:oid:2.49.0.1.840.0.c6add6b54c87dec0372f5cac3b3e3da2c46c6320.001.1", + "sender": "w-nws.webmaster@noaa.gov", + "sent": "2026-07-01T15:50:00-06:00" + } + ], + "sent": "2026-07-01T16:04:00-06:00", + "effective": "2026-07-01T16:04:00-06:00", + "onset": "2026-07-01T16:04:00-06:00", + "expires": "2026-07-01T16:30:00-06:00", + "ends": "2026-07-01T16:30:00-06:00", + "status": "Actual", + "messageType": "Update", + "category": "Met", + "severity": "Severe", + "certainty": "Observed", + "urgency": "Immediate", + "event": "Severe Thunderstorm Warning", + "sender": "w-nws.webmaster@noaa.gov", + "senderName": "NWS Pocatello ID", + "headline": "Severe Thunderstorm Warning issued July 1 at 4:04PM MDT until July 1 at 4:30PM MDT by NWS Pocatello ID", + "description": "At 403 PM MDT, a severe thunderstorm was located over Oakley, or near\nOakley Reservoir, moving northeast at 20 mph.\n\nHAZARD...Quarter size hail.\n\nSOURCE...Radar indicated.\n\nIMPACT...Damage to vehicles is expected.\n\nLocations impacted include...\nOakley Reservoir and Oakley.", + "instruction": "For your protection move to an interior room on the lowest floor of a\nbuilding.\n\nTorrential rainfall is occurring with this storm, and may lead to\nflash flooding. Do not drive your vehicle through flooded roadways.", + "response": "Shelter", + "note": null, + "parameters": { + "AWIPSidentifier": [ + "SVSPIH" + ], + "WMOidentifier": [ + "WWUS55 KPIH 012204" + ], + "NWSheadline": [ + "A SEVERE THUNDERSTORM WARNING REMAINS IN EFFECT UNTIL 430 PM MDT FOR WEST CENTRAL CASSIA COUNTY" + ], + "eventMotionDescription": [ + "2026-07-01T22:03:00-00:00...storm...246DEG...17KT...42.25,-113.93" + ], + "windThreat": [ + "RADAR INDICATED" + ], + "maxWindGust": [ + "Up to 50 MPH" + ], + "hailThreat": [ + "RADAR INDICATED" + ], + "maxHailSize": [ + "1.00" + ], + "BLOCKCHANNEL": [ + "EAS", + "NWEM", + "CMAS" + ], + "EAS-ORG": [ + "WXR" + ], + "VTEC": [ + "/O.CON.KPIH.SV.W.0027.000000T0000Z-260701T2230Z/" + ], + "eventEndingTime": [ + "2026-07-01T16:30:00-06:00" + ] + }, + "scope": "Public", + "code": "IPAWSv1.0", + "language": "en-US", + "web": "http://www.weather.gov", + "eventCode": { + "SAME": [ + "SVS" + ], + "NationalWeatherService": [ + "SVW" + ] + } + } + } + }, + "subject": "central.wx.alert.us.id.county.c031", + "captured_epoch": 1783206513 +} \ No newline at end of file diff --git a/work/tests/fixtures/nws/0010.json b/work/tests/fixtures/nws/0010.json new file mode 100644 index 0000000..a0eb408 --- /dev/null +++ b/work/tests/fixtures/nws/0010.json @@ -0,0 +1,125 @@ +{ + "envelope": { + "id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.5c7f541457ac9d46793ef42667626fce15a685a7.001.1", + "source": "central.echo6.co", + "type": "central.wx.alert.special_weather_statement.v1", + "time": "2026-07-01T22:47:00+00:00", + "datacontenttype": "application/json", + "centralschemaversion": "1.0", + "centralcategory": "wx.alert.special_weather_statement", + "centralseverity": 2, + "specversion": "1.0", + "data": { + "id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.5c7f541457ac9d46793ef42667626fce15a685a7.001.1", + "adapter": "nws", + "category": "wx.alert.special_weather_statement", + "time": "2026-07-01T22:47:00Z", + "expires": "2026-07-01T23:30:00Z", + "severity": 2, + "geo": { + "centroid": [ + -114.81199999999998, + 42.492000000000004 + ], + "bbox": [ + -115.05, + 42.27, + -114.36, + 42.8 + ], + "regions": [ + "US-ID-FIPS16047", + "US-ID-FIPS16053", + "US-ID-FIPS16083", + "US-ID-Z016", + "US-ID-Z030" + ], + "primary_region": "US-ID-FIPS16047", + "geometry": null + }, + "data": { + "@id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.5c7f541457ac9d46793ef42667626fce15a685a7.001.1", + "@type": "wx:Alert", + "id": "urn:oid:2.49.0.1.840.0.5c7f541457ac9d46793ef42667626fce15a685a7.001.1", + "areaDesc": "Western Magic Valley; Southern Twin Falls County", + "geocode": { + "SAME": [ + "016047", + "016053", + "016083" + ], + "UGC": [ + "IDZ016", + "IDZ030" + ] + }, + "affectedZones": [ + "https://api.weather.gov/zones/forecast/IDZ016", + "https://api.weather.gov/zones/forecast/IDZ030" + ], + "references": [], + "sent": "2026-07-01T16:47:00-06:00", + "effective": "2026-07-01T16:47:00-06:00", + "onset": "2026-07-01T16:47:00-06:00", + "expires": "2026-07-01T17:30:00-06:00", + "ends": null, + "status": "Actual", + "messageType": "Alert", + "category": "Met", + "severity": "Moderate", + "certainty": "Observed", + "urgency": "Expected", + "event": "Special Weather Statement", + "sender": "w-nws.webmaster@noaa.gov", + "senderName": "NWS Boise ID", + "headline": "Special Weather Statement issued July 1 at 4:47PM MDT by NWS Boise ID", + "description": "At 447 PM MDT, Doppler radar was tracking a strong thunderstorm near\nRoseworth, or 20 miles southwest of Twin Falls, moving northeast at\n20 mph.\n\nHAZARD...Wind gusts up to 40 mph and pea size hail.\n\nSOURCE...Radar indicated.\n\nIMPACT...Gusty winds could knock down tree limbs and blow around\nunsecured objects. Areas of blowing dust possible. Minor\ndamage to outdoor objects is possible.\n\nLocations impacted include...\nTwin Falls, Buhl, Kimberly, Filer, Castleford, Magic Valley Regional\nAirport, and Roseworth.", + "instruction": "If outdoors, consider seeking shelter inside a building.\n\nFrequent cloud to ground lightning is occurring with this storm.\nLightning can strike 10 miles away from a thunderstorm. Seek a safe\nshelter inside a building or vehicle.", + "response": "Execute", + "note": null, + "parameters": { + "AWIPSidentifier": [ + "SPSBOI" + ], + "WMOidentifier": [ + "WWUS85 KBOI 012247" + ], + "NWSheadline": [ + "A STRONG THUNDERSTORM WILL IMPACT WEST CENTRAL TWIN FALLS... WESTERN JEROME AND SOUTHEASTERN GOODING COUNTIES THROUGH 530 PM MDT" + ], + "eventMotionDescription": [ + "2026-07-01T22:47:00-00:00...storm...214DEG...18KT...42.43,-114.82" + ], + "maxWindGust": [ + "40 MPH" + ], + "maxHailSize": [ + "0.25" + ], + "BLOCKCHANNEL": [ + "EAS", + "NWEM", + "CMAS" + ], + "EAS-ORG": [ + "WXR" + ] + }, + "scope": "Public", + "code": "IPAWSv1.0", + "language": "en-US", + "web": "http://www.weather.gov", + "eventCode": { + "SAME": [ + "SPS" + ], + "NationalWeatherService": [ + "SPS" + ] + } + } + } + }, + "subject": "central.wx.alert.us.id.county.fips16047", + "captured_epoch": 1783206513 +} \ No newline at end of file diff --git a/work/tests/fixtures/nws/0011.json b/work/tests/fixtures/nws/0011.json new file mode 100644 index 0000000..28d46b4 --- /dev/null +++ b/work/tests/fixtures/nws/0011.json @@ -0,0 +1,122 @@ +{ + "envelope": { + "id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.24dbf178f22821530104c9956cb76aa7971b6359.001.1", + "source": "central.echo6.co", + "type": "central.wx.alert.special_weather_statement.v1", + "time": "2026-07-01T23:26:00+00:00", + "datacontenttype": "application/json", + "centralschemaversion": "1.0", + "centralcategory": "wx.alert.special_weather_statement", + "centralseverity": 2, + "specversion": "1.0", + "data": { + "id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.24dbf178f22821530104c9956cb76aa7971b6359.001.1", + "adapter": "nws", + "category": "wx.alert.special_weather_statement", + "time": "2026-07-01T23:26:00Z", + "expires": "2026-07-02T00:15:00Z", + "severity": 2, + "geo": { + "centroid": [ + -114.67999999999999, + 42.642 + ], + "bbox": [ + -114.87, + 42.5, + -114.39, + 42.89 + ], + "regions": [ + "US-ID-FIPS16047", + "US-ID-FIPS16053", + "US-ID-FIPS16083", + "US-ID-Z016" + ], + "primary_region": "US-ID-FIPS16047", + "geometry": null + }, + "data": { + "@id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.24dbf178f22821530104c9956cb76aa7971b6359.001.1", + "@type": "wx:Alert", + "id": "urn:oid:2.49.0.1.840.0.24dbf178f22821530104c9956cb76aa7971b6359.001.1", + "areaDesc": "Western Magic Valley", + "geocode": { + "SAME": [ + "016047", + "016053", + "016083" + ], + "UGC": [ + "IDZ016" + ] + }, + "affectedZones": [ + "https://api.weather.gov/zones/forecast/IDZ016" + ], + "references": [], + "sent": "2026-07-01T17:26:00-06:00", + "effective": "2026-07-01T17:26:00-06:00", + "onset": "2026-07-01T17:26:00-06:00", + "expires": "2026-07-01T18:15:00-06:00", + "ends": null, + "status": "Actual", + "messageType": "Alert", + "category": "Met", + "severity": "Moderate", + "certainty": "Observed", + "urgency": "Expected", + "event": "Special Weather Statement", + "sender": "w-nws.webmaster@noaa.gov", + "senderName": "NWS Boise ID", + "headline": "Special Weather Statement issued July 1 at 5:26PM MDT by NWS Boise ID", + "description": "At 526 PM MDT, Doppler radar was tracking a strong thunderstorm over\nBuhl, or 14 miles southwest of Jerome, moving northeast at 20 mph.\n\nHAZARD...Wind gusts up to 40 mph and half inch hail.\n\nSOURCE...Radar indicated.\n\nIMPACT...Gusty winds could knock down tree limbs and blow around\nunsecured objects. Areas of blowing dust possible. Minor\ndamage to outdoor objects is possible.\n\nLocations impacted include...\nJerome, Buhl, Wendell, and Filer.", + "instruction": "If outdoors, consider seeking shelter inside a building.\n\nFrequent cloud to ground lightning is occurring with this storm.\nLightning can strike 10 miles away from a thunderstorm. Seek a safe\nshelter inside a building or vehicle.", + "response": "Execute", + "note": null, + "parameters": { + "AWIPSidentifier": [ + "SPSBOI" + ], + "WMOidentifier": [ + "WWUS85 KBOI 012326" + ], + "NWSheadline": [ + "A STRONG THUNDERSTORM WILL IMPACT WEST CENTRAL TWIN FALLS...WEST CENTRAL JEROME AND SOUTHEASTERN GOODING COUNTIES THROUGH 615 PM MDT" + ], + "eventMotionDescription": [ + "2026-07-01T23:26:00-00:00...storm...216DEG...17KT...42.61,-114.77" + ], + "maxWindGust": [ + "40 MPH" + ], + "maxHailSize": [ + "0.50" + ], + "BLOCKCHANNEL": [ + "EAS", + "NWEM", + "CMAS" + ], + "EAS-ORG": [ + "WXR" + ] + }, + "scope": "Public", + "code": "IPAWSv1.0", + "language": "en-US", + "web": "http://www.weather.gov", + "eventCode": { + "SAME": [ + "SPS" + ], + "NationalWeatherService": [ + "SPS" + ] + } + } + } + }, + "subject": "central.wx.alert.us.id.county.fips16047", + "captured_epoch": 1783206513 +} \ No newline at end of file diff --git a/work/tests/fixtures/nws/0012.json b/work/tests/fixtures/nws/0012.json new file mode 100644 index 0000000..a3b5a2b --- /dev/null +++ b/work/tests/fixtures/nws/0012.json @@ -0,0 +1,155 @@ +{ + "envelope": { + "id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.38832779abf7df2df3c943a8ebd4af2692acd570.001.1", + "source": "central.echo6.co", + "type": "central.wx.alert.special_weather_statement.v1", + "time": "2026-07-01T23:57:00+00:00", + "datacontenttype": "application/json", + "centralschemaversion": "1.0", + "centralcategory": "wx.alert.special_weather_statement", + "centralseverity": 2, + "specversion": "1.0", + "data": { + "id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.38832779abf7df2df3c943a8ebd4af2692acd570.001.1", + "adapter": "nws", + "category": "wx.alert.special_weather_statement", + "time": "2026-07-01T23:57:00Z", + "expires": "2026-07-02T00:30:00Z", + "severity": 2, + "geo": { + "centroid": [ + -112.864, + 43.308 + ], + "bbox": [ + -113.26, + 42.72, + -112.38, + 43.75 + ], + "regions": [ + "US-ID-FIPS16005", + "US-ID-FIPS16011", + "US-ID-FIPS16013", + "US-ID-FIPS16019", + "US-ID-FIPS16023", + "US-ID-FIPS16029", + "US-ID-FIPS16031", + "US-ID-FIPS16037", + "US-ID-FIPS16051", + "US-ID-FIPS16063", + "US-ID-FIPS16067", + "US-ID-FIPS16077", + "US-ID-Z051", + "US-ID-Z052", + "US-ID-Z054", + "US-ID-Z055", + "US-ID-Z058", + "US-ID-Z068" + ], + "primary_region": "US-ID-FIPS16005", + "geometry": null + }, + "data": { + "@id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.38832779abf7df2df3c943a8ebd4af2692acd570.001.1", + "@type": "wx:Alert", + "id": "urn:oid:2.49.0.1.840.0.38832779abf7df2df3c943a8ebd4af2692acd570.001.1", + "areaDesc": "Shoshone/Lava Beds; Arco/Mud Lake Desert; Lower Snake River Plain; Eastern Magic Valley; Marsh and Arbon Highlands; Lost River Valleys", + "geocode": { + "SAME": [ + "016013", + "016063", + "016067", + "016011", + "016019", + "016023", + "016051", + "016005", + "016077", + "016031", + "016029", + "016037" + ], + "UGC": [ + "IDZ051", + "IDZ052", + "IDZ054", + "IDZ055", + "IDZ058", + "IDZ068" + ] + }, + "affectedZones": [ + "https://api.weather.gov/zones/forecast/IDZ051", + "https://api.weather.gov/zones/forecast/IDZ052", + "https://api.weather.gov/zones/forecast/IDZ054", + "https://api.weather.gov/zones/forecast/IDZ055", + "https://api.weather.gov/zones/forecast/IDZ058", + "https://api.weather.gov/zones/forecast/IDZ068" + ], + "references": [], + "sent": "2026-07-01T17:57:00-06:00", + "effective": "2026-07-01T17:57:00-06:00", + "onset": "2026-07-01T17:57:00-06:00", + "expires": "2026-07-01T18:30:00-06:00", + "ends": null, + "status": "Actual", + "messageType": "Alert", + "category": "Met", + "severity": "Moderate", + "certainty": "Observed", + "urgency": "Expected", + "event": "Special Weather Statement", + "sender": "w-nws.webmaster@noaa.gov", + "senderName": "NWS Pocatello ID", + "headline": "Special Weather Statement issued July 1 at 5:57PM MDT by NWS Pocatello ID", + "description": "At 556 PM MDT, Doppler radar was tracking gusty showers along a line\nextending from 13 miles southwest of Southwest Inl to American Falls.\nMovement was northeast at 30 mph.\n\nHAZARD...Wind gusts in excess of 45 mph.\n\nSOURCE...Radar indicated.\n\nIMPACT...Gusty winds could knock down tree limbs and blow around\nunsecured objects.\n\nLocations impacted include...\nWestern Pocatello, American Falls, American Falls Reservoir,\nChubbuck, Aberdeen, Atomic City, Fort Hall Buffalo Lodge, Central\nInl, Southwest Inl, Springfield, Pingree, Pocatello Airport, Fort\nHall Townsite, Sterling, and Big Southern Butte.", + "instruction": "If outdoors, consider seeking shelter inside a building.\n\nIf on or near American Falls Reservoir, get out of the water and\nmove indoors or inside a vehicle. Strong winds will create rough\nchop.\nRemember, lightning can strike out to 10 miles from the parent\nthunderstorm. If you can hear thunder, you are close enough to be\nstruck by lightning. Move to safe shelter now! Do not be caught on\nthe water in a thunderstorm.", + "response": "Execute", + "note": null, + "parameters": { + "AWIPSidentifier": [ + "SPSPIH" + ], + "WMOidentifier": [ + "WWUS85 KPIH 012357" + ], + "NWSheadline": [ + "GUSTY SHOWERS WILL IMPACT SOUTHEASTERN BLAINE...NORTHWESTERN BANNOCK...SOUTHEASTERN BUTTE...NORTHEASTERN POWER AND WESTERN BINGHAM COUNTIES THROUGH 630 PM MDT" + ], + "eventMotionDescription": [ + "2026-07-01T23:56:00-00:00...storm...216DEG...28KT...43.32,-113.16 42.79,-112.82" + ], + "maxWindGust": [ + "45 MPH" + ], + "maxHailSize": [ + "0.00" + ], + "BLOCKCHANNEL": [ + "EAS", + "NWEM", + "CMAS" + ], + "EAS-ORG": [ + "WXR" + ] + }, + "scope": "Public", + "code": "IPAWSv1.0", + "language": "en-US", + "web": "http://www.weather.gov", + "eventCode": { + "SAME": [ + "SPS" + ], + "NationalWeatherService": [ + "SPS" + ] + } + } + } + }, + "subject": "central.wx.alert.us.id.county.fips16005", + "captured_epoch": 1783206513 +} \ No newline at end of file diff --git a/work/tests/fixtures/nws/0013.json b/work/tests/fixtures/nws/0013.json new file mode 100644 index 0000000..5b2a708 --- /dev/null +++ b/work/tests/fixtures/nws/0013.json @@ -0,0 +1,136 @@ +{ + "envelope": { + "id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.e1a929f5a667f978203865e862ad31f369d5466a.001.1", + "source": "central.echo6.co", + "type": "central.wx.alert.special_weather_statement.v1", + "time": "2026-07-02T00:33:00+00:00", + "datacontenttype": "application/json", + "centralschemaversion": "1.0", + "centralcategory": "wx.alert.special_weather_statement", + "centralseverity": 2, + "specversion": "1.0", + "data": { + "id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.e1a929f5a667f978203865e862ad31f369d5466a.001.1", + "adapter": "nws", + "category": "wx.alert.special_weather_statement", + "time": "2026-07-02T00:33:00Z", + "expires": "2026-07-02T01:00:00Z", + "severity": 2, + "geo": { + "centroid": [ + -112.37, + 43.882 + ], + "bbox": [ + -112.64, + 43.69, + -111.99, + 44.18 + ], + "regions": [ + "US-ID-FIPS16011", + "US-ID-FIPS16019", + "US-ID-FIPS16023", + "US-ID-FIPS16033", + "US-ID-FIPS16043", + "US-ID-FIPS16051", + "US-ID-FIPS16065", + "US-ID-Z052", + "US-ID-Z053", + "US-ID-Z067" + ], + "primary_region": "US-ID-FIPS16011", + "geometry": null + }, + "data": { + "@id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.e1a929f5a667f978203865e862ad31f369d5466a.001.1", + "@type": "wx:Alert", + "id": "urn:oid:2.49.0.1.840.0.e1a929f5a667f978203865e862ad31f369d5466a.001.1", + "areaDesc": "Arco/Mud Lake Desert; Upper Snake River Plain; Beaverhead/Lemhi Highlands", + "geocode": { + "SAME": [ + "016011", + "016019", + "016023", + "016051", + "016043", + "016065", + "016033" + ], + "UGC": [ + "IDZ052", + "IDZ053", + "IDZ067" + ] + }, + "affectedZones": [ + "https://api.weather.gov/zones/forecast/IDZ052", + "https://api.weather.gov/zones/forecast/IDZ053", + "https://api.weather.gov/zones/forecast/IDZ067" + ], + "references": [], + "sent": "2026-07-01T18:33:00-06:00", + "effective": "2026-07-01T18:33:00-06:00", + "onset": "2026-07-01T18:33:00-06:00", + "expires": "2026-07-01T19:00:00-06:00", + "ends": null, + "status": "Actual", + "messageType": "Alert", + "category": "Met", + "severity": "Moderate", + "certainty": "Observed", + "urgency": "Expected", + "event": "Special Weather Statement", + "sender": "w-nws.webmaster@noaa.gov", + "senderName": "NWS Pocatello ID", + "headline": "Special Weather Statement issued July 1 at 6:33PM MDT by NWS Pocatello ID", + "description": "At 633 PM MDT, Doppler radar was tracking a cluster of strong\nthunderstorms over Terreton, moving northeast at 20 mph.\n\nHAZARD...Wind gusts in excess of 45 mph and half inch hail.\n\nSOURCE...Radar indicated.\n\nIMPACT...Gusty winds could knock down tree limbs and blow around\nunsecured objects. Minor hail damage to outdoor objects is\npossible.\n\nLocations impacted include...\nTerreton, Mud Lake, and Hamer.", + "instruction": "If outdoors, consider seeking shelter inside a building.", + "response": "Execute", + "note": null, + "parameters": { + "AWIPSidentifier": [ + "SPSPIH" + ], + "WMOidentifier": [ + "WWUS85 KPIH 020033" + ], + "NWSheadline": [ + "STRONG THUNDERSTORMS WILL IMPACT SOUTHWESTERN FREMONT...WESTERN JEFFERSON AND SOUTH CENTRAL CLARK COUNTIES THROUGH 700 PM MDT" + ], + "eventMotionDescription": [ + "2026-07-02T00:33:00-00:00...storm...211DEG...20KT...43.86,-112.43" + ], + "maxWindGust": [ + "45 MPH" + ], + "maxHailSize": [ + "0.50" + ], + "BLOCKCHANNEL": [ + "EAS", + "NWEM", + "CMAS" + ], + "EAS-ORG": [ + "WXR" + ] + }, + "scope": "Public", + "code": "IPAWSv1.0", + "language": "en-US", + "web": "http://www.weather.gov", + "eventCode": { + "SAME": [ + "SPS" + ], + "NationalWeatherService": [ + "SPS" + ] + } + } + } + }, + "subject": "central.wx.alert.us.id.county.fips16011", + "captured_epoch": 1783206513 +} \ No newline at end of file diff --git a/work/tests/fixtures/nws/0014.json b/work/tests/fixtures/nws/0014.json new file mode 100644 index 0000000..4c40d65 --- /dev/null +++ b/work/tests/fixtures/nws/0014.json @@ -0,0 +1,144 @@ +{ + "envelope": { + "id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.257d509d12328e220896c10ef7243f1ea48a41c8.001.1", + "source": "central.echo6.co", + "type": "central.wx.alert.special_weather_statement.v1", + "time": "2026-07-02T01:22:00+00:00", + "datacontenttype": "application/json", + "centralschemaversion": "1.0", + "centralcategory": "wx.alert.special_weather_statement", + "centralseverity": 2, + "specversion": "1.0", + "data": { + "id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.257d509d12328e220896c10ef7243f1ea48a41c8.001.1", + "adapter": "nws", + "category": "wx.alert.special_weather_statement", + "time": "2026-07-02T01:22:00Z", + "expires": "2026-07-02T01:45:00Z", + "severity": 2, + "geo": { + "centroid": [ + -113.186, + 42.562 + ], + "bbox": [ + -113.47, + 42.33, + -112.77, + 42.94 + ], + "regions": [ + "US-ID-FIPS16005", + "US-ID-FIPS16011", + "US-ID-FIPS16013", + "US-ID-FIPS16029", + "US-ID-FIPS16031", + "US-ID-FIPS16067", + "US-ID-FIPS16071", + "US-ID-FIPS16077", + "US-ID-Z054", + "US-ID-Z055", + "US-ID-Z056", + "US-ID-Z057", + "US-ID-Z058" + ], + "primary_region": "US-ID-FIPS16005", + "geometry": null + }, + "data": { + "@id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.257d509d12328e220896c10ef7243f1ea48a41c8.001.1", + "@type": "wx:Alert", + "id": "urn:oid:2.49.0.1.840.0.257d509d12328e220896c10ef7243f1ea48a41c8.001.1", + "areaDesc": "Lower Snake River Plain; Eastern Magic Valley; Southern Hills/Albion Mountains; Raft River Region; Marsh and Arbon Highlands", + "geocode": { + "SAME": [ + "016005", + "016011", + "016077", + "016013", + "016031", + "016067", + "016071", + "016029" + ], + "UGC": [ + "IDZ054", + "IDZ055", + "IDZ056", + "IDZ057", + "IDZ058" + ] + }, + "affectedZones": [ + "https://api.weather.gov/zones/forecast/IDZ054", + "https://api.weather.gov/zones/forecast/IDZ055", + "https://api.weather.gov/zones/forecast/IDZ056", + "https://api.weather.gov/zones/forecast/IDZ057", + "https://api.weather.gov/zones/forecast/IDZ058" + ], + "references": [], + "sent": "2026-07-01T19:22:00-06:00", + "effective": "2026-07-01T19:22:00-06:00", + "onset": "2026-07-01T19:22:00-06:00", + "expires": "2026-07-01T19:45:00-06:00", + "ends": null, + "status": "Actual", + "messageType": "Alert", + "category": "Met", + "severity": "Moderate", + "certainty": "Observed", + "urgency": "Expected", + "event": "Special Weather Statement", + "sender": "w-nws.webmaster@noaa.gov", + "senderName": "NWS Pocatello ID", + "headline": "Special Weather Statement issued July 1 at 7:22PM MDT by NWS Pocatello ID", + "description": "At 722 PM MDT, Doppler radar was tracking gusty showers 9 miles\nsouthwest of Cold Water Rest Area, or 12 miles southeast of Lake\nWalcott, moving north at 35 mph.\n\nHAZARD...Wind gusts of 50 to 55 mph.\n\nSOURCE...Radar indicated.\n\nIMPACT...Gusty winds could knock down tree limbs and blow around\nunsecured objects.\n\nLocations impacted include...\nAmerican Falls, Neeley, Lake Walcott, Cold Water Rest Area, Yale Rest\nArea, Pilar Butte, Idahome, and Massacre Rocks.", + "instruction": "If outdoors, consider seeking shelter inside a building.\n\nIf on or near American Falls Reservoir, get out of the water and\nmove indoors or inside a vehicle. Strong winds will create rough\nchop.\nRemember, lightning can strike out to 10 miles from the parent\nthunderstorm. If you can hear thunder, you are close enough to be\nstruck by lightning. Move to safe shelter now! Do not be caught on\nthe water in a thunderstorm.", + "response": "Execute", + "note": null, + "parameters": { + "AWIPSidentifier": [ + "SPSPIH" + ], + "WMOidentifier": [ + "WWUS85 KPIH 020122" + ], + "NWSheadline": [ + "GUSTY SHOWERS WILL IMPACT SOUTHEASTERN BLAINE...SOUTHWESTERN POWER...NORTHEASTERN CASSIA AND EASTERN MINIDOKA COUNTIES THROUGH 745 PM MDT" + ], + "eventMotionDescription": [ + "2026-07-02T01:22:00-00:00...storm...202DEG...32KT...42.52,-113.27" + ], + "maxWindGust": [ + "55 MPH" + ], + "maxHailSize": [ + "0.00" + ], + "BLOCKCHANNEL": [ + "EAS", + "NWEM", + "CMAS" + ], + "EAS-ORG": [ + "WXR" + ] + }, + "scope": "Public", + "code": "IPAWSv1.0", + "language": "en-US", + "web": "http://www.weather.gov", + "eventCode": { + "SAME": [ + "SPS" + ], + "NationalWeatherService": [ + "SPS" + ] + } + } + } + }, + "subject": "central.wx.alert.us.id.county.fips16005", + "captured_epoch": 1783206513 +} \ No newline at end of file diff --git a/work/tests/fixtures/nws/0015.json b/work/tests/fixtures/nws/0015.json new file mode 100644 index 0000000..fe2d6ff --- /dev/null +++ b/work/tests/fixtures/nws/0015.json @@ -0,0 +1,139 @@ +{ + "envelope": { + "id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.b421ed8ea37df16bc190ca51379d2c8637cfef9b.001.1", + "source": "central.echo6.co", + "type": "central.wx.alert.special_weather_statement.v1", + "time": "2026-07-02T01:39:00+00:00", + "datacontenttype": "application/json", + "centralschemaversion": "1.0", + "centralcategory": "wx.alert.special_weather_statement", + "centralseverity": 2, + "specversion": "1.0", + "data": { + "id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.b421ed8ea37df16bc190ca51379d2c8637cfef9b.001.1", + "adapter": "nws", + "category": "wx.alert.special_weather_statement", + "time": "2026-07-02T01:39:00Z", + "expires": "2026-07-02T02:15:00Z", + "severity": 2, + "geo": { + "centroid": [ + -112.86600000000001, + 43.552 + ], + "bbox": [ + -113.36, + 43.34, + -112.3, + 43.86 + ], + "regions": [ + "US-ID-FIPS16005", + "US-ID-FIPS16011", + "US-ID-FIPS16019", + "US-ID-FIPS16023", + "US-ID-FIPS16037", + "US-ID-FIPS16051", + "US-ID-FIPS16077", + "US-ID-Z052", + "US-ID-Z054", + "US-ID-Z068", + "US-ID-Z069" + ], + "primary_region": "US-ID-FIPS16005", + "geometry": null + }, + "data": { + "@id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.b421ed8ea37df16bc190ca51379d2c8637cfef9b.001.1", + "@type": "wx:Alert", + "id": "urn:oid:2.49.0.1.840.0.b421ed8ea37df16bc190ca51379d2c8637cfef9b.001.1", + "areaDesc": "Arco/Mud Lake Desert; Lower Snake River Plain; Lost River Valleys; Lost River Range", + "geocode": { + "SAME": [ + "016011", + "016019", + "016023", + "016051", + "016005", + "016077", + "016037" + ], + "UGC": [ + "IDZ052", + "IDZ054", + "IDZ068", + "IDZ069" + ] + }, + "affectedZones": [ + "https://api.weather.gov/zones/forecast/IDZ052", + "https://api.weather.gov/zones/forecast/IDZ054", + "https://api.weather.gov/zones/forecast/IDZ068", + "https://api.weather.gov/zones/forecast/IDZ069" + ], + "references": [], + "sent": "2026-07-01T19:39:00-06:00", + "effective": "2026-07-01T19:39:00-06:00", + "onset": "2026-07-01T19:39:00-06:00", + "expires": "2026-07-01T20:15:00-06:00", + "ends": null, + "status": "Actual", + "messageType": "Alert", + "category": "Met", + "severity": "Moderate", + "certainty": "Observed", + "urgency": "Expected", + "event": "Special Weather Statement", + "sender": "w-nws.webmaster@noaa.gov", + "senderName": "NWS Pocatello ID", + "headline": "Special Weather Statement issued July 1 at 7:39PM MDT by NWS Pocatello ID", + "description": "At 738 PM MDT, Doppler radar was tracking gusty showers near Atomic\nCity, or 23 miles southeast of Arco, moving north at 35 mph.\n\nHAZARD...Wind gusts in excess of 45 mph.\n\nSOURCE...Radar indicated.\n\nIMPACT...Gusty winds could knock down tree limbs and blow around\nunsecured objects.\n\nLocations impacted include...\nArco, Atomic City, Central Inl, Southeast Inl, Southwest Inl, Butte\nCity, Howe, East Butte, and Big Southern Butte.", + "instruction": "If outdoors, consider seeking shelter inside a building.", + "response": "Execute", + "note": null, + "parameters": { + "AWIPSidentifier": [ + "SPSPIH" + ], + "WMOidentifier": [ + "WWUS85 KPIH 020139" + ], + "NWSheadline": [ + "GUSTY SHOWERS WILL IMPACT SOUTHWESTERN JEFFERSON...SOUTHEASTERN BUTTE...NORTHWESTERN BONNEVILLE AND NORTHWESTERN BINGHAM COUNTIES THROUGH 815 PM MDT" + ], + "eventMotionDescription": [ + "2026-07-02T01:38:00-00:00...storm...198DEG...32KT...43.49,-112.87" + ], + "maxWindGust": [ + "45 MPH" + ], + "maxHailSize": [ + "0.00" + ], + "BLOCKCHANNEL": [ + "EAS", + "NWEM", + "CMAS" + ], + "EAS-ORG": [ + "WXR" + ] + }, + "scope": "Public", + "code": "IPAWSv1.0", + "language": "en-US", + "web": "http://www.weather.gov", + "eventCode": { + "SAME": [ + "SPS" + ], + "NationalWeatherService": [ + "SPS" + ] + } + } + } + }, + "subject": "central.wx.alert.us.id.county.fips16005", + "captured_epoch": 1783206513 +} \ No newline at end of file diff --git a/work/tests/fixtures/nws/0016.json b/work/tests/fixtures/nws/0016.json new file mode 100644 index 0000000..009f149 --- /dev/null +++ b/work/tests/fixtures/nws/0016.json @@ -0,0 +1,131 @@ +{ + "envelope": { + "id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.7e01dcbcd5953d0339f08bda5175bd1a6c17f915.001.1", + "source": "central.echo6.co", + "type": "central.wx.alert.special_weather_statement.v1", + "time": "2026-07-02T20:35:00+00:00", + "datacontenttype": "application/json", + "centralschemaversion": "1.0", + "centralcategory": "wx.alert.special_weather_statement", + "centralseverity": 2, + "specversion": "1.0", + "data": { + "id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.7e01dcbcd5953d0339f08bda5175bd1a6c17f915.001.1", + "adapter": "nws", + "category": "wx.alert.special_weather_statement", + "time": "2026-07-02T20:35:00Z", + "expires": "2026-07-02T21:00:00Z", + "severity": 2, + "geo": { + "centroid": [ + -112.35, + 42.598 + ], + "bbox": [ + -112.62, + 42.47, + -112.12, + 42.79 + ], + "regions": [ + "US-ID-FIPS16005", + "US-ID-FIPS16011", + "US-ID-FIPS16029", + "US-ID-FIPS16041", + "US-ID-FIPS16071", + "US-ID-FIPS16077", + "US-ID-Z058", + "US-ID-Z059" + ], + "primary_region": "US-ID-FIPS16005", + "geometry": null + }, + "data": { + "@id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.7e01dcbcd5953d0339f08bda5175bd1a6c17f915.001.1", + "@type": "wx:Alert", + "id": "urn:oid:2.49.0.1.840.0.7e01dcbcd5953d0339f08bda5175bd1a6c17f915.001.1", + "areaDesc": "Marsh and Arbon Highlands; Franklin/Eastern Oneida Region", + "geocode": { + "SAME": [ + "016005", + "016011", + "016029", + "016077", + "016041", + "016071" + ], + "UGC": [ + "IDZ058", + "IDZ059" + ] + }, + "affectedZones": [ + "https://api.weather.gov/zones/forecast/IDZ058", + "https://api.weather.gov/zones/forecast/IDZ059" + ], + "references": [], + "sent": "2026-07-02T14:35:00-06:00", + "effective": "2026-07-02T14:35:00-06:00", + "onset": "2026-07-02T14:35:00-06:00", + "expires": "2026-07-02T15:00:00-06:00", + "ends": null, + "status": "Actual", + "messageType": "Alert", + "category": "Met", + "severity": "Moderate", + "certainty": "Observed", + "urgency": "Expected", + "event": "Special Weather Statement", + "sender": "w-nws.webmaster@noaa.gov", + "senderName": "NWS Pocatello ID", + "headline": "Special Weather Statement issued July 2 at 2:35PM MDT by NWS Pocatello ID", + "description": "At 235 PM MDT, Doppler radar was tracking a strong thunderstorm 12\nmiles west of Arimo, or 13 miles southwest of McCammon, moving north\nat 25 mph.\n\nHAZARD...Wind gusts in excess of 45 mph and nickel size hail.\n\nSOURCE...Radar indicated.\n\nIMPACT...Gusty winds could knock down tree limbs and blow around\nunsecured objects. Minor hail damage to outdoor objects is\npossible.\n\nLocations impacted include...\nMcCammon, Hawkins Reservoir, Mink Creek Pass, and Pauline.", + "instruction": "If outdoors, consider seeking shelter inside a building.\n\nFrequent cloud to ground lightning is occurring with this storm.\nLightning can strike 10 miles away from a thunderstorm. Seek a safe\nshelter inside a building or vehicle.", + "response": "Execute", + "note": null, + "parameters": { + "AWIPSidentifier": [ + "SPSPIH" + ], + "WMOidentifier": [ + "WWUS85 KPIH 022035" + ], + "NWSheadline": [ + "A STRONG THUNDERSTORM WILL IMPACT WESTERN CARIBOU...NORTHERN ONEIDA...CENTRAL BANNOCK AND SOUTHEASTERN POWER COUNTIES THROUGH 300 PM MDT" + ], + "eventMotionDescription": [ + "2026-07-02T20:35:00-00:00...storm...199DEG...20KT...42.55,-112.42" + ], + "maxWindGust": [ + "45 MPH" + ], + "maxHailSize": [ + "0.88" + ], + "BLOCKCHANNEL": [ + "EAS", + "NWEM", + "CMAS" + ], + "EAS-ORG": [ + "WXR" + ] + }, + "scope": "Public", + "code": "IPAWSv1.0", + "language": "en-US", + "web": "http://www.weather.gov", + "eventCode": { + "SAME": [ + "SPS" + ], + "NationalWeatherService": [ + "SPS" + ] + } + } + } + }, + "subject": "central.wx.alert.us.id.county.fips16005", + "captured_epoch": 1783206513 +} \ No newline at end of file diff --git a/work/tests/fixtures/nws/0017.json b/work/tests/fixtures/nws/0017.json new file mode 100644 index 0000000..29d1774 --- /dev/null +++ b/work/tests/fixtures/nws/0017.json @@ -0,0 +1,129 @@ +{ + "envelope": { + "id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.aebbeb92eaee6e5c9c580a6c3327d2d4f63d1e57.001.1", + "source": "central.echo6.co", + "type": "central.wx.alert.special_weather_statement.v1", + "time": "2026-07-02T21:00:00+00:00", + "datacontenttype": "application/json", + "centralschemaversion": "1.0", + "centralcategory": "wx.alert.special_weather_statement", + "centralseverity": 2, + "specversion": "1.0", + "data": { + "id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.aebbeb92eaee6e5c9c580a6c3327d2d4f63d1e57.001.1", + "adapter": "nws", + "category": "wx.alert.special_weather_statement", + "time": "2026-07-02T21:00:00Z", + "expires": "2026-07-02T21:30:00Z", + "severity": 2, + "geo": { + "centroid": [ + -112.23600000000002, + 42.666 + ], + "bbox": [ + -112.47, + 42.51, + -111.86, + 42.94 + ], + "regions": [ + "US-ID-FIPS16005", + "US-ID-FIPS16011", + "US-ID-FIPS16019", + "US-ID-FIPS16029", + "US-ID-FIPS16077", + "US-ID-Z058", + "US-ID-Z062" + ], + "primary_region": "US-ID-FIPS16005", + "geometry": null + }, + "data": { + "@id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.aebbeb92eaee6e5c9c580a6c3327d2d4f63d1e57.001.1", + "@type": "wx:Alert", + "id": "urn:oid:2.49.0.1.840.0.aebbeb92eaee6e5c9c580a6c3327d2d4f63d1e57.001.1", + "areaDesc": "Marsh and Arbon Highlands; Blackfoot Mountains", + "geocode": { + "SAME": [ + "016005", + "016011", + "016029", + "016077", + "016019" + ], + "UGC": [ + "IDZ058", + "IDZ062" + ] + }, + "affectedZones": [ + "https://api.weather.gov/zones/forecast/IDZ058", + "https://api.weather.gov/zones/forecast/IDZ062" + ], + "references": [], + "sent": "2026-07-02T15:00:00-06:00", + "effective": "2026-07-02T15:00:00-06:00", + "onset": "2026-07-02T15:00:00-06:00", + "expires": "2026-07-02T15:30:00-06:00", + "ends": null, + "status": "Actual", + "messageType": "Alert", + "category": "Met", + "severity": "Moderate", + "certainty": "Observed", + "urgency": "Expected", + "event": "Special Weather Statement", + "sender": "w-nws.webmaster@noaa.gov", + "senderName": "NWS Pocatello ID", + "headline": "Special Weather Statement issued July 2 at 3:00PM MDT by NWS Pocatello ID", + "description": "At 259 PM MDT, Doppler radar was tracking a strong thunderstorm near\nMcCammon, moving northeast at 25 mph.\n\nHAZARD...Wind gusts in excess of 45 mph and nickel size hail.\n\nSOURCE...Radar indicated.\n\nIMPACT...Gusty winds could knock down tree limbs and blow around\nunsecured objects. Minor hail damage to outdoor objects is\npossible.\n\nLocations impacted include...\nMcCammon, Inkom, Arimo, and Portneuf Gap.", + "instruction": "If outdoors, consider seeking shelter inside a building.\n\nFrequent cloud to ground lightning is occurring with this storm.\nLightning can strike 10 miles away from a thunderstorm. Seek a safe\nshelter inside a building or vehicle.", + "response": "Execute", + "note": null, + "parameters": { + "AWIPSidentifier": [ + "SPSPIH" + ], + "WMOidentifier": [ + "WWUS85 KPIH 022100" + ], + "NWSheadline": [ + "A STRONG THUNDERSTORM WILL IMPACT WEST CENTRAL CARIBOU...CENTRAL BANNOCK AND EASTERN POWER COUNTIES THROUGH 330 PM MDT" + ], + "eventMotionDescription": [ + "2026-07-02T20:59:00-00:00...storm...219DEG...23KT...42.63,-112.31" + ], + "maxWindGust": [ + "45 MPH" + ], + "maxHailSize": [ + "0.88" + ], + "BLOCKCHANNEL": [ + "EAS", + "NWEM", + "CMAS" + ], + "EAS-ORG": [ + "WXR" + ] + }, + "scope": "Public", + "code": "IPAWSv1.0", + "language": "en-US", + "web": "http://www.weather.gov", + "eventCode": { + "SAME": [ + "SPS" + ], + "NationalWeatherService": [ + "SPS" + ] + } + } + } + }, + "subject": "central.wx.alert.us.id.county.fips16005", + "captured_epoch": 1783206513 +} \ No newline at end of file diff --git a/work/tests/fixtures/nws/0018.json b/work/tests/fixtures/nws/0018.json new file mode 100644 index 0000000..a6516f9 --- /dev/null +++ b/work/tests/fixtures/nws/0018.json @@ -0,0 +1,127 @@ +{ + "envelope": { + "id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.e553cac82421c22dbb9a50e020945b77a8d14ced.001.1", + "source": "central.echo6.co", + "type": "central.wx.alert.special_weather_statement.v1", + "time": "2026-07-02T22:39:00+00:00", + "datacontenttype": "application/json", + "centralschemaversion": "1.0", + "centralcategory": "wx.alert.special_weather_statement", + "centralseverity": 2, + "specversion": "1.0", + "data": { + "id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.e553cac82421c22dbb9a50e020945b77a8d14ced.001.1", + "adapter": "nws", + "category": "wx.alert.special_weather_statement", + "time": "2026-07-02T22:39:00Z", + "expires": "2026-07-02T23:30:00Z", + "severity": 2, + "geo": { + "centroid": [ + -113.005, + 41.3725 + ], + "bbox": [ + -113.9, + 40.64, + -112.62, + 41.93 + ], + "regions": [ + "US-UT-FIPS49003", + "US-UT-FIPS49035", + "US-UT-FIPS49045", + "US-UT-FIPS49049", + "US-UT-Z101", + "US-UT-Z102" + ], + "primary_region": "US-UT-FIPS49003", + "geometry": null + }, + "data": { + "@id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.e553cac82421c22dbb9a50e020945b77a8d14ced.001.1", + "@type": "wx:Alert", + "id": "urn:oid:2.49.0.1.840.0.e553cac82421c22dbb9a50e020945b77a8d14ced.001.1", + "areaDesc": "Great Salt Lake Desert and Mountains; Tooele and Rush Valleys", + "geocode": { + "SAME": [ + "049003", + "049035", + "049045", + "049049" + ], + "UGC": [ + "UTZ101", + "UTZ102" + ] + }, + "affectedZones": [ + "https://api.weather.gov/zones/forecast/UTZ101", + "https://api.weather.gov/zones/forecast/UTZ102" + ], + "references": [], + "sent": "2026-07-02T16:39:00-06:00", + "effective": "2026-07-02T16:39:00-06:00", + "onset": "2026-07-02T16:39:00-06:00", + "expires": "2026-07-02T17:30:00-06:00", + "ends": null, + "status": "Actual", + "messageType": "Alert", + "category": "Met", + "severity": "Moderate", + "certainty": "Observed", + "urgency": "Expected", + "event": "Special Weather Statement", + "sender": "w-nws.webmaster@noaa.gov", + "senderName": "NWS Salt Lake City UT", + "headline": "Special Weather Statement issued July 2 at 4:39PM MDT by NWS Salt Lake City UT", + "description": "At 438 PM MDT, Doppler radar was tracking strong outflow winds along\na line extending from 6 miles south of Park Valley to 27 miles west\nof Great Salt Lake North of the Causeway to 6 miles east of Utah\nTest and Training Range North to 7 miles north of Clive. Movement\nwas east at 25 mph.\n\nHAZARD...Wind gusts up to 50 mph.\n\nSOURCE...Radar indicated.\n\nIMPACT...Gusty winds could knock down tree limbs and blow around\nunsecured objects.\n\nLocations impacted include...\nDelle, Knolls, Utah Test and Training Range North, Great Salt Lake\nNorth of the Causeway, Great Salt Lake South of the Causeway,\nLocomotive Springs, Lakeside, Gunnison Island, and Bonneville Salt\nFlats.\n\nThis includes the following highways...\nInterstate 80 in Utah between mile markers 20 and 78.\nUtah Route 30 between mile markers 11 and 41, and between mile\nmarkers 60 and 65.", + "instruction": "If outdoors, consider seeking shelter inside a building.", + "response": "Execute", + "note": null, + "parameters": { + "AWIPSidentifier": [ + "SPSSLC" + ], + "WMOidentifier": [ + "WWUS85 KSLC 022239" + ], + "NWSheadline": [ + "Strong thunderstorm outflow winds will impact portions of western Box Elder and northwestern Tooele Counties through 530 PM MDT" + ], + "eventMotionDescription": [ + "2026-07-02T22:38:00-00:00...storm...247DEG...21KT...41.73,-113.35 41.47,-113.25 41.25,-113.18 41.0,-113.1 40.78,-113.06" + ], + "maxWindGust": [ + "50 MPH" + ], + "maxHailSize": [ + "0.00" + ], + "BLOCKCHANNEL": [ + "EAS", + "NWEM", + "CMAS" + ], + "EAS-ORG": [ + "WXR" + ] + }, + "scope": "Public", + "code": "IPAWSv1.0", + "language": "en-US", + "web": "http://www.weather.gov", + "eventCode": { + "SAME": [ + "SPS" + ], + "NationalWeatherService": [ + "SPS" + ] + } + } + } + }, + "subject": "central.wx.alert.us.ut.county.fips49003", + "captured_epoch": 1783206513 +} \ No newline at end of file diff --git a/work/tests/fixtures/nws/0019.json b/work/tests/fixtures/nws/0019.json new file mode 100644 index 0000000..5c9408d --- /dev/null +++ b/work/tests/fixtures/nws/0019.json @@ -0,0 +1,137 @@ +{ + "envelope": { + "id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.e58c2f30c093ce7defdf71a90e452af5ec568023.001.1", + "source": "central.echo6.co", + "type": "central.wx.alert.special_weather_statement.v1", + "time": "2026-07-02T23:30:00+00:00", + "datacontenttype": "application/json", + "centralschemaversion": "1.0", + "centralcategory": "wx.alert.special_weather_statement", + "centralseverity": 2, + "specversion": "1.0", + "data": { + "id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.e58c2f30c093ce7defdf71a90e452af5ec568023.001.1", + "adapter": "nws", + "category": "wx.alert.special_weather_statement", + "time": "2026-07-02T23:30:00Z", + "expires": "2026-07-03T00:15:00Z", + "severity": 2, + "geo": { + "centroid": [ + -112.62, + 41.378571428571426 + ], + "bbox": [ + -113.15, + 40.64, + -112.25, + 42.0 + ], + "regions": [ + "US-UT-FIPS49003", + "US-UT-FIPS49011", + "US-UT-FIPS49035", + "US-UT-FIPS49045", + "US-UT-FIPS49049", + "US-UT-FIPS49057", + "US-UT-Z101", + "US-UT-Z102", + "US-UT-Z103", + "US-UT-Z104" + ], + "primary_region": "US-UT-FIPS49003", + "geometry": null + }, + "data": { + "@id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.e58c2f30c093ce7defdf71a90e452af5ec568023.001.1", + "@type": "wx:Alert", + "id": "urn:oid:2.49.0.1.840.0.e58c2f30c093ce7defdf71a90e452af5ec568023.001.1", + "areaDesc": "Great Salt Lake Desert and Mountains; Tooele and Rush Valleys; Eastern Box Elder County; Northern Wasatch Front", + "geocode": { + "SAME": [ + "049003", + "049035", + "049045", + "049049", + "049011", + "049057" + ], + "UGC": [ + "UTZ101", + "UTZ102", + "UTZ103", + "UTZ104" + ] + }, + "affectedZones": [ + "https://api.weather.gov/zones/forecast/UTZ101", + "https://api.weather.gov/zones/forecast/UTZ102", + "https://api.weather.gov/zones/forecast/UTZ103", + "https://api.weather.gov/zones/forecast/UTZ104" + ], + "references": [], + "sent": "2026-07-02T17:30:00-06:00", + "effective": "2026-07-02T17:30:00-06:00", + "onset": "2026-07-02T17:30:00-06:00", + "expires": "2026-07-02T18:15:00-06:00", + "ends": null, + "status": "Actual", + "messageType": "Alert", + "category": "Met", + "severity": "Moderate", + "certainty": "Observed", + "urgency": "Expected", + "event": "Special Weather Statement", + "sender": "w-nws.webmaster@noaa.gov", + "senderName": "NWS Salt Lake City UT", + "headline": "Special Weather Statement issued July 2 at 5:30PM MDT by NWS Salt Lake City UT", + "description": "At 528 PM MDT, Doppler radar was tracking strong thunderstorm\noutflow winds along a line extending from 16 miles east of Park\nValley to 27 miles southwest of Howell to near Great Salt Lake North\nof the Causeway to 17 miles west of Great Salt Lake South of the\nCauseway to 8 miles northeast of Delle. Movement was east at 25 mph.\n\nHAZARD...Wind gusts up to 50 mph.\n\nSOURCE...Radar indicated.\n\nIMPACT...Gusty winds could knock down tree limbs and blow around\nunsecured objects.\n\nLocations impacted include...\nSnowville, Stansbury Park, Howell, Delle, Great Salt Lake North of\nthe Causeway, Great Salt Lake South of the Causeway, Hat Island,\nPromontory, Gunnison Island, Locomotive Springs, Curlew Junction,\nLake Point, Golden Spike Historic Site, and Antelope Island State\nPark.\n\nThis includes the following highways...\nInterstate 84 between mile markers 1 and 21.\nInterstate 80 in Utah between mile markers 67 and 99.\nUtah Route 30 between mile markers 69 and 90.", + "instruction": "If outdoors, consider seeking shelter inside a building.", + "response": "Execute", + "note": null, + "parameters": { + "AWIPSidentifier": [ + "SPSSLC" + ], + "WMOidentifier": [ + "WWUS85 KSLC 022330" + ], + "NWSheadline": [ + "Strong thunderstorm outflow winds will impact portions of southwestern Weber, central Box Elder, western Davis and northeastern Tooele Counties through 615 PM MDT" + ], + "eventMotionDescription": [ + "2026-07-02T23:28:00-00:00...storm...247DEG...21KT...41.83,-113.01 41.58,-112.9 41.36,-112.83 41.11,-112.75 40.89,-112.71" + ], + "maxWindGust": [ + "50 MPH" + ], + "maxHailSize": [ + "0.00" + ], + "BLOCKCHANNEL": [ + "EAS", + "NWEM", + "CMAS" + ], + "EAS-ORG": [ + "WXR" + ] + }, + "scope": "Public", + "code": "IPAWSv1.0", + "language": "en-US", + "web": "http://www.weather.gov", + "eventCode": { + "SAME": [ + "SPS" + ], + "NationalWeatherService": [ + "SPS" + ] + } + } + } + }, + "subject": "central.wx.alert.us.ut.county.fips49003", + "captured_epoch": 1783206513 +} \ No newline at end of file diff --git a/work/tests/fixtures/nws/0020.json b/work/tests/fixtures/nws/0020.json new file mode 100644 index 0000000..84b290b --- /dev/null +++ b/work/tests/fixtures/nws/0020.json @@ -0,0 +1,129 @@ +{ + "envelope": { + "id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.37174062117c7303b67edcfc370acf4d3c3131cb.001.1", + "source": "central.echo6.co", + "type": "central.wx.alert.special_weather_statement.v1", + "time": "2026-07-03T19:43:00+00:00", + "datacontenttype": "application/json", + "centralschemaversion": "1.0", + "centralcategory": "wx.alert.special_weather_statement", + "centralseverity": 2, + "specversion": "1.0", + "data": { + "id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.37174062117c7303b67edcfc370acf4d3c3131cb.001.1", + "adapter": "nws", + "category": "wx.alert.special_weather_statement", + "time": "2026-07-03T19:43:00Z", + "expires": "2026-07-03T20:15:00Z", + "severity": 2, + "geo": { + "centroid": [ + -111.208, + 43.37 + ], + "bbox": [ + -111.4, + 43.25, + -111.05, + 43.6 + ], + "regions": [ + "US-ID-FIPS16007", + "US-ID-FIPS16019", + "US-ID-FIPS16029", + "US-ID-FIPS16065", + "US-ID-FIPS16081", + "US-ID-Z063", + "US-ID-Z064" + ], + "primary_region": "US-ID-FIPS16007", + "geometry": null + }, + "data": { + "@id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.37174062117c7303b67edcfc370acf4d3c3131cb.001.1", + "@type": "wx:Alert", + "id": "urn:oid:2.49.0.1.840.0.37174062117c7303b67edcfc370acf4d3c3131cb.001.1", + "areaDesc": "Caribou Range; Big Hole Mountains", + "geocode": { + "SAME": [ + "016007", + "016019", + "016029", + "016065", + "016081" + ], + "UGC": [ + "IDZ063", + "IDZ064" + ] + }, + "affectedZones": [ + "https://api.weather.gov/zones/forecast/IDZ063", + "https://api.weather.gov/zones/forecast/IDZ064" + ], + "references": [], + "sent": "2026-07-03T13:43:00-06:00", + "effective": "2026-07-03T13:43:00-06:00", + "onset": "2026-07-03T13:43:00-06:00", + "expires": "2026-07-03T14:15:00-06:00", + "ends": null, + "status": "Actual", + "messageType": "Alert", + "category": "Met", + "severity": "Moderate", + "certainty": "Observed", + "urgency": "Expected", + "event": "Special Weather Statement", + "sender": "w-nws.webmaster@noaa.gov", + "senderName": "NWS Pocatello ID", + "headline": "Special Weather Statement issued July 3 at 1:43PM MDT by NWS Pocatello ID", + "description": "At 142 PM MDT, Doppler radar was tracking a strong thunderstorm 8\nmiles northeast of Irwin, or 10 miles east of Swan Valley, moving\nnortheast at 15 mph.\n\nHAZARD...Wind gusts in excess of 45 mph and half inch hail.\n\nSOURCE...Radar indicated.\n\nIMPACT...Gusty winds could knock down tree limbs and blow around\nunsecured objects. Minor hail damage to outdoor objects is\npossible.\n\nLocations impacted include...\nSwan Valley, northern Palisades Reservoir, and Irwin.", + "instruction": "If outdoors, consider seeking shelter inside a building.\n\nIf on or near Palisades Reservoir, get out of the water and move\nindoors or inside a vehicle. Strong winds will create rough chop.\nRemember, lightning can strike out to 10 miles from the parent\nthunderstorm. If you can hear thunder, you are close enough to be\nstruck by lightning. Move to safe shelter now! Do not be caught on\nthe water in a thunderstorm.", + "response": "Execute", + "note": null, + "parameters": { + "AWIPSidentifier": [ + "SPSPIH" + ], + "WMOidentifier": [ + "WWUS85 KPIH 031943" + ], + "NWSheadline": [ + "A STRONG THUNDERSTORM WILL IMPACT SOUTHEASTERN TETON AND EAST CENTRAL BONNEVILLE COUNTIES THROUGH 215 PM MDT" + ], + "eventMotionDescription": [ + "2026-07-03T19:42:00-00:00...storm...245DEG...12KT...43.44,-111.12" + ], + "maxWindGust": [ + "45 MPH" + ], + "maxHailSize": [ + "0.50" + ], + "BLOCKCHANNEL": [ + "EAS", + "NWEM", + "CMAS" + ], + "EAS-ORG": [ + "WXR" + ] + }, + "scope": "Public", + "code": "IPAWSv1.0", + "language": "en-US", + "web": "http://www.weather.gov", + "eventCode": { + "SAME": [ + "SPS" + ], + "NationalWeatherService": [ + "SPS" + ] + } + } + } + }, + "subject": "central.wx.alert.us.id.county.fips16007", + "captured_epoch": 1783206513 +} \ No newline at end of file diff --git a/work/tests/fixtures/nws/0021.json b/work/tests/fixtures/nws/0021.json new file mode 100644 index 0000000..0441171 --- /dev/null +++ b/work/tests/fixtures/nws/0021.json @@ -0,0 +1,136 @@ +{ + "envelope": { + "id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.fdef77b40b0d4bf6ffedeb3825c87ef0e1426f5f.001.1", + "source": "central.echo6.co", + "type": "central.wx.alert.special_weather_statement.v1", + "time": "2026-07-03T19:55:00+00:00", + "datacontenttype": "application/json", + "centralschemaversion": "1.0", + "centralcategory": "wx.alert.special_weather_statement", + "centralseverity": 2, + "specversion": "1.0", + "data": { + "id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.fdef77b40b0d4bf6ffedeb3825c87ef0e1426f5f.001.1", + "adapter": "nws", + "category": "wx.alert.special_weather_statement", + "time": "2026-07-03T19:55:00Z", + "expires": "2026-07-03T20:45:00Z", + "severity": 2, + "geo": { + "centroid": [ + -112.876, + 42.166 + ], + "bbox": [ + -113.11, + 42.03, + -112.55, + 42.41 + ], + "regions": [ + "US-ID-FIPS16005", + "US-ID-FIPS16011", + "US-ID-FIPS16029", + "US-ID-FIPS16031", + "US-ID-FIPS16041", + "US-ID-FIPS16071", + "US-ID-FIPS16077", + "US-ID-Z057", + "US-ID-Z058", + "US-ID-Z059" + ], + "primary_region": "US-ID-FIPS16005", + "geometry": null + }, + "data": { + "@id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.fdef77b40b0d4bf6ffedeb3825c87ef0e1426f5f.001.1", + "@type": "wx:Alert", + "id": "urn:oid:2.49.0.1.840.0.fdef77b40b0d4bf6ffedeb3825c87ef0e1426f5f.001.1", + "areaDesc": "Raft River Region; Marsh and Arbon Highlands; Franklin/Eastern Oneida Region", + "geocode": { + "SAME": [ + "016031", + "016071", + "016077", + "016005", + "016011", + "016029", + "016041" + ], + "UGC": [ + "IDZ057", + "IDZ058", + "IDZ059" + ] + }, + "affectedZones": [ + "https://api.weather.gov/zones/forecast/IDZ057", + "https://api.weather.gov/zones/forecast/IDZ058", + "https://api.weather.gov/zones/forecast/IDZ059" + ], + "references": [], + "sent": "2026-07-03T13:55:00-06:00", + "effective": "2026-07-03T13:55:00-06:00", + "onset": "2026-07-03T13:55:00-06:00", + "expires": "2026-07-03T14:45:00-06:00", + "ends": null, + "status": "Actual", + "messageType": "Alert", + "category": "Met", + "severity": "Moderate", + "certainty": "Observed", + "urgency": "Expected", + "event": "Special Weather Statement", + "sender": "w-nws.webmaster@noaa.gov", + "senderName": "NWS Pocatello ID", + "headline": "Special Weather Statement issued July 3 at 1:55PM MDT by NWS Pocatello ID", + "description": "At 155 PM MDT, Doppler radar was tracking a strong thunderstorm over\nJuniper, or 18 miles northwest of Snowville, moving northeast at 15\nmph.\n\nHAZARD...Wind gusts in excess of 45 mph.\n\nSOURCE...Radar indicated.\n\nIMPACT...Gusty winds could knock down tree limbs and blow around\nunsecured objects.\n\nLocations impacted include...\nJuniper, Holbrook, Roy, and Sweetzer Summit.", + "instruction": "If outdoors, consider seeking shelter inside a building.", + "response": "Execute", + "note": null, + "parameters": { + "AWIPSidentifier": [ + "SPSPIH" + ], + "WMOidentifier": [ + "WWUS85 KPIH 031955" + ], + "NWSheadline": [ + "A STRONG THUNDERSTORM WILL IMPACT SOUTHWESTERN ONEIDA...SOUTH CENTRAL POWER AND EAST CENTRAL CASSIA COUNTIES THROUGH 245 PM MDT" + ], + "eventMotionDescription": [ + "2026-07-03T19:55:00-00:00...storm...239DEG...15KT...42.19,-112.94" + ], + "maxWindGust": [ + "45 MPH" + ], + "maxHailSize": [ + "0.00" + ], + "BLOCKCHANNEL": [ + "EAS", + "NWEM", + "CMAS" + ], + "EAS-ORG": [ + "WXR" + ] + }, + "scope": "Public", + "code": "IPAWSv1.0", + "language": "en-US", + "web": "http://www.weather.gov", + "eventCode": { + "SAME": [ + "SPS" + ], + "NationalWeatherService": [ + "SPS" + ] + } + } + } + }, + "subject": "central.wx.alert.us.id.county.fips16005", + "captured_epoch": 1783206513 +} \ No newline at end of file diff --git a/work/tests/fixtures/nws/0022.json b/work/tests/fixtures/nws/0022.json new file mode 100644 index 0000000..ba86e5f --- /dev/null +++ b/work/tests/fixtures/nws/0022.json @@ -0,0 +1,139 @@ +{ + "envelope": { + "id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.07401e524299157e1996cee02a06844d3767d56b.001.1", + "source": "central.echo6.co", + "type": "central.wx.alert.special_weather_statement.v1", + "time": "2026-07-03T20:28:00+00:00", + "datacontenttype": "application/json", + "centralschemaversion": "1.0", + "centralcategory": "wx.alert.special_weather_statement", + "centralseverity": 2, + "specversion": "1.0", + "data": { + "id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.07401e524299157e1996cee02a06844d3767d56b.001.1", + "adapter": "nws", + "category": "wx.alert.special_weather_statement", + "time": "2026-07-03T20:28:00Z", + "expires": "2026-07-03T21:15:00Z", + "severity": 2, + "geo": { + "centroid": [ + -111.91599999999998, + 42.54 + ], + "bbox": [ + -112.19, + 42.39, + -111.53, + 42.81 + ], + "regions": [ + "US-ID-FIPS16005", + "US-ID-FIPS16007", + "US-ID-FIPS16011", + "US-ID-FIPS16019", + "US-ID-FIPS16029", + "US-ID-FIPS16041", + "US-ID-FIPS16077", + "US-ID-Z058", + "US-ID-Z060", + "US-ID-Z061", + "US-ID-Z062" + ], + "primary_region": "US-ID-FIPS16005", + "geometry": null + }, + "data": { + "@id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.07401e524299157e1996cee02a06844d3767d56b.001.1", + "@type": "wx:Alert", + "id": "urn:oid:2.49.0.1.840.0.07401e524299157e1996cee02a06844d3767d56b.001.1", + "areaDesc": "Marsh and Arbon Highlands; Bear River Range; Bear Lake Valley; Blackfoot Mountains", + "geocode": { + "SAME": [ + "016005", + "016011", + "016029", + "016077", + "016007", + "016041", + "016019" + ], + "UGC": [ + "IDZ058", + "IDZ060", + "IDZ061", + "IDZ062" + ] + }, + "affectedZones": [ + "https://api.weather.gov/zones/forecast/IDZ058", + "https://api.weather.gov/zones/forecast/IDZ060", + "https://api.weather.gov/zones/forecast/IDZ061", + "https://api.weather.gov/zones/forecast/IDZ062" + ], + "references": [], + "sent": "2026-07-03T14:28:00-06:00", + "effective": "2026-07-03T14:28:00-06:00", + "onset": "2026-07-03T14:28:00-06:00", + "expires": "2026-07-03T15:15:00-06:00", + "ends": null, + "status": "Actual", + "messageType": "Alert", + "category": "Met", + "severity": "Moderate", + "certainty": "Observed", + "urgency": "Expected", + "event": "Special Weather Statement", + "sender": "w-nws.webmaster@noaa.gov", + "senderName": "NWS Pocatello ID", + "headline": "Special Weather Statement issued July 3 at 2:28PM MDT by NWS Pocatello ID", + "description": "At 227 PM MDT, Doppler radar was tracking a strong thunderstorm near\nVirginia, or 7 miles south of Lava Hot Springs, moving northeast at\n25 mph.\n\nHAZARD...Wind gusts in excess of 45 mph.\n\nSOURCE...Radar indicated.\n\nIMPACT...Gusty winds could knock down tree limbs and blow around\nunsecured objects.\n\nLocations impacted include...\nSoda Springs, Lava Hot Springs, Grace, and Niter.", + "instruction": "If outdoors, consider seeking shelter inside a building.", + "response": "Execute", + "note": null, + "parameters": { + "AWIPSidentifier": [ + "SPSPIH" + ], + "WMOidentifier": [ + "WWUS85 KPIH 032028" + ], + "NWSheadline": [ + "A STRONG THUNDERSTORM WILL IMPACT SOUTHWESTERN CARIBOU... SOUTHEASTERN BANNOCK AND NORTHWESTERN BEAR LAKE COUNTIES THROUGH 315 PM MDT" + ], + "eventMotionDescription": [ + "2026-07-03T20:27:00-00:00...storm...238DEG...21KT...42.51,-112.05" + ], + "maxWindGust": [ + "45 MPH" + ], + "maxHailSize": [ + "0.00" + ], + "BLOCKCHANNEL": [ + "EAS", + "NWEM", + "CMAS" + ], + "EAS-ORG": [ + "WXR" + ] + }, + "scope": "Public", + "code": "IPAWSv1.0", + "language": "en-US", + "web": "http://www.weather.gov", + "eventCode": { + "SAME": [ + "SPS" + ], + "NationalWeatherService": [ + "SPS" + ] + } + } + } + }, + "subject": "central.wx.alert.us.id.county.fips16005", + "captured_epoch": 1783206513 +} \ No newline at end of file diff --git a/work/tests/fixtures/nws/0023.json b/work/tests/fixtures/nws/0023.json new file mode 100644 index 0000000..dfe8e71 --- /dev/null +++ b/work/tests/fixtures/nws/0023.json @@ -0,0 +1,131 @@ +{ + "envelope": { + "id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.7f65c3f4091756c06397c59f09937e835913ce7b.001.1", + "source": "central.echo6.co", + "type": "central.wx.alert.special_weather_statement.v1", + "time": "2026-07-03T21:01:00+00:00", + "datacontenttype": "application/json", + "centralschemaversion": "1.0", + "centralcategory": "wx.alert.special_weather_statement", + "centralseverity": 2, + "specversion": "1.0", + "data": { + "id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.7f65c3f4091756c06397c59f09937e835913ce7b.001.1", + "adapter": "nws", + "category": "wx.alert.special_weather_statement", + "time": "2026-07-03T21:01:00Z", + "expires": "2026-07-03T22:00:00Z", + "severity": 2, + "geo": { + "centroid": [ + -112.31599999999999, + 42.376 + ], + "bbox": [ + -112.74, + 42.16, + -111.74, + 42.79 + ], + "regions": [ + "US-ID-FIPS16005", + "US-ID-FIPS16011", + "US-ID-FIPS16029", + "US-ID-FIPS16041", + "US-ID-FIPS16071", + "US-ID-FIPS16077", + "US-ID-Z058", + "US-ID-Z059" + ], + "primary_region": "US-ID-FIPS16005", + "geometry": null + }, + "data": { + "@id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.7f65c3f4091756c06397c59f09937e835913ce7b.001.1", + "@type": "wx:Alert", + "id": "urn:oid:2.49.0.1.840.0.7f65c3f4091756c06397c59f09937e835913ce7b.001.1", + "areaDesc": "Marsh and Arbon Highlands; Franklin/Eastern Oneida Region", + "geocode": { + "SAME": [ + "016005", + "016011", + "016029", + "016077", + "016041", + "016071" + ], + "UGC": [ + "IDZ058", + "IDZ059" + ] + }, + "affectedZones": [ + "https://api.weather.gov/zones/forecast/IDZ058", + "https://api.weather.gov/zones/forecast/IDZ059" + ], + "references": [], + "sent": "2026-07-03T15:01:00-06:00", + "effective": "2026-07-03T15:01:00-06:00", + "onset": "2026-07-03T15:01:00-06:00", + "expires": "2026-07-03T16:00:00-06:00", + "ends": null, + "status": "Actual", + "messageType": "Alert", + "category": "Met", + "severity": "Moderate", + "certainty": "Observed", + "urgency": "Expected", + "event": "Special Weather Statement", + "sender": "w-nws.webmaster@noaa.gov", + "senderName": "NWS Pocatello ID", + "headline": "Special Weather Statement issued July 3 at 3:01PM MDT by NWS Pocatello ID", + "description": "At 300 PM MDT, Doppler radar was tracking a cluster of strong\nthunderstorms 13 miles southwest of Virginia, or 15 miles northwest\nof Malad, moving northeast at 25 mph.\n\nHAZARD...Wind gusts in excess of 45 mph.\n\nSOURCE...Radar indicated.\n\nIMPACT...Gusty winds could knock down tree limbs and blow around\nunsecured objects.\n\nLocations impacted include...\nMcCammon, Lava Hot Springs, Swanlake, Downey, Arimo, Virginia,\nOxford, Hawkins Reservoir, Daniels Reservoir, Arbon, and Malad Pass.", + "instruction": "If outdoors, consider seeking shelter inside a building.", + "response": "Execute", + "note": null, + "parameters": { + "AWIPSidentifier": [ + "SPSPIH" + ], + "WMOidentifier": [ + "WWUS85 KPIH 032101" + ], + "NWSheadline": [ + "STRONG THUNDERSTORMS WILL IMPACT SOUTHWESTERN CARIBOU...CENTRAL ONEIDA...SOUTHEASTERN BANNOCK...SOUTHEASTERN POWER AND NORTHWESTERN FRANKLIN COUNTIES THROUGH 400 PM MDT" + ], + "eventMotionDescription": [ + "2026-07-03T21:00:00-00:00...storm...241DEG...21KT...42.39,-112.39" + ], + "maxWindGust": [ + "45 MPH" + ], + "maxHailSize": [ + "0.00" + ], + "BLOCKCHANNEL": [ + "EAS", + "NWEM", + "CMAS" + ], + "EAS-ORG": [ + "WXR" + ] + }, + "scope": "Public", + "code": "IPAWSv1.0", + "language": "en-US", + "web": "http://www.weather.gov", + "eventCode": { + "SAME": [ + "SPS" + ], + "NationalWeatherService": [ + "SPS" + ] + } + } + } + }, + "subject": "central.wx.alert.us.id.county.fips16005", + "captured_epoch": 1783206513 +} \ No newline at end of file diff --git a/work/tests/fixtures/nws/0024.json b/work/tests/fixtures/nws/0024.json new file mode 100644 index 0000000..bff7008 --- /dev/null +++ b/work/tests/fixtures/nws/0024.json @@ -0,0 +1,135 @@ +{ + "envelope": { + "id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.b5db4468acb058b8b21fd03ca388c7dfe2bac2da.001.1", + "source": "central.echo6.co", + "type": "central.wx.alert.special_weather_statement.v1", + "time": "2026-07-03T21:13:00+00:00", + "datacontenttype": "application/json", + "centralschemaversion": "1.0", + "centralcategory": "wx.alert.special_weather_statement", + "centralseverity": 2, + "specversion": "1.0", + "data": { + "id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.b5db4468acb058b8b21fd03ca388c7dfe2bac2da.001.1", + "adapter": "nws", + "category": "wx.alert.special_weather_statement", + "time": "2026-07-03T21:13:00Z", + "expires": "2026-07-03T22:00:00Z", + "severity": 2, + "geo": { + "centroid": [ + -111.62, + 42.598 + ], + "bbox": [ + -111.85, + 42.47, + -111.3, + 42.83 + ], + "regions": [ + "US-ID-FIPS16007", + "US-ID-FIPS16011", + "US-ID-FIPS16019", + "US-ID-FIPS16029", + "US-ID-FIPS16041", + "US-ID-Z060", + "US-ID-Z061", + "US-ID-Z062", + "US-ID-Z063" + ], + "primary_region": "US-ID-FIPS16007", + "geometry": null + }, + "data": { + "@id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.b5db4468acb058b8b21fd03ca388c7dfe2bac2da.001.1", + "@type": "wx:Alert", + "id": "urn:oid:2.49.0.1.840.0.b5db4468acb058b8b21fd03ca388c7dfe2bac2da.001.1", + "areaDesc": "Bear River Range; Bear Lake Valley; Blackfoot Mountains; Caribou Range", + "geocode": { + "SAME": [ + "016007", + "016029", + "016041", + "016011", + "016019" + ], + "UGC": [ + "IDZ060", + "IDZ061", + "IDZ062", + "IDZ063" + ] + }, + "affectedZones": [ + "https://api.weather.gov/zones/forecast/IDZ060", + "https://api.weather.gov/zones/forecast/IDZ061", + "https://api.weather.gov/zones/forecast/IDZ062", + "https://api.weather.gov/zones/forecast/IDZ063" + ], + "references": [], + "sent": "2026-07-03T15:13:00-06:00", + "effective": "2026-07-03T15:13:00-06:00", + "onset": "2026-07-03T15:13:00-06:00", + "expires": "2026-07-03T16:00:00-06:00", + "ends": null, + "status": "Actual", + "messageType": "Alert", + "category": "Met", + "severity": "Moderate", + "certainty": "Observed", + "urgency": "Expected", + "event": "Special Weather Statement", + "sender": "w-nws.webmaster@noaa.gov", + "senderName": "NWS Pocatello ID", + "headline": "Special Weather Statement issued July 3 at 3:13PM MDT by NWS Pocatello ID", + "description": "At 313 PM MDT, Doppler radar was tracking a strong thunderstorm over\nGrace, or 7 miles southwest of Soda Springs, moving northeast at 15\nmph.\n\nHAZARD...Wind gusts in excess of 45 mph and pea size hail.\n\nSOURCE...Radar indicated.\n\nIMPACT...Gusty winds could knock down tree limbs and blow around\nunsecured objects. Minor hail damage to outdoor objects is\npossible.\n\nLocations impacted include...\nSoda Springs, Grace, and Niter.", + "instruction": "If outdoors, consider seeking shelter inside a building.", + "response": "Execute", + "note": null, + "parameters": { + "AWIPSidentifier": [ + "SPSPIH" + ], + "WMOidentifier": [ + "WWUS85 KPIH 032113" + ], + "NWSheadline": [ + "A STRONG THUNDERSTORM WILL IMPACT CENTRAL CARIBOU AND NORTHWESTERN BEAR LAKE COUNTIES THROUGH 400 PM MDT" + ], + "eventMotionDescription": [ + "2026-07-03T21:13:00-00:00...storm...240DEG...13KT...42.59,-111.7" + ], + "maxWindGust": [ + "45 MPH" + ], + "maxHailSize": [ + "0.25" + ], + "BLOCKCHANNEL": [ + "EAS", + "NWEM", + "CMAS" + ], + "EAS-ORG": [ + "WXR" + ] + }, + "scope": "Public", + "code": "IPAWSv1.0", + "language": "en-US", + "web": "http://www.weather.gov", + "eventCode": { + "SAME": [ + "SPS" + ], + "NationalWeatherService": [ + "SPS" + ] + } + } + } + }, + "subject": "central.wx.alert.us.id.county.fips16007", + "captured_epoch": 1783206513 +} \ No newline at end of file diff --git a/work/tests/fixtures/nws/0025.json b/work/tests/fixtures/nws/0025.json new file mode 100644 index 0000000..d00e321 --- /dev/null +++ b/work/tests/fixtures/nws/0025.json @@ -0,0 +1,147 @@ +{ + "envelope": { + "id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.e17d529642f7f75871d50c7b18b4212065361ee2.001.1", + "source": "central.echo6.co", + "type": "central.wx.alert.special_weather_statement.v1", + "time": "2026-07-03T22:22:00+00:00", + "datacontenttype": "application/json", + "centralschemaversion": "1.0", + "centralcategory": "wx.alert.special_weather_statement", + "centralseverity": 2, + "specversion": "1.0", + "data": { + "id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.e17d529642f7f75871d50c7b18b4212065361ee2.001.1", + "adapter": "nws", + "category": "wx.alert.special_weather_statement", + "time": "2026-07-03T22:22:00Z", + "expires": "2026-07-03T23:15:00Z", + "severity": 2, + "geo": { + "centroid": [ + -111.606, + 42.312 + ], + "bbox": [ + -111.92, + 42.13, + -111.16, + 42.62 + ], + "regions": [ + "US-ID-FIPS16005", + "US-ID-FIPS16007", + "US-ID-FIPS16011", + "US-ID-FIPS16019", + "US-ID-FIPS16029", + "US-ID-FIPS16041", + "US-ID-FIPS16071", + "US-ID-FIPS16077", + "US-ID-Z058", + "US-ID-Z059", + "US-ID-Z060", + "US-ID-Z061", + "US-ID-Z062", + "US-ID-Z063" + ], + "primary_region": "US-ID-FIPS16005", + "geometry": null + }, + "data": { + "@id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.e17d529642f7f75871d50c7b18b4212065361ee2.001.1", + "@type": "wx:Alert", + "id": "urn:oid:2.49.0.1.840.0.e17d529642f7f75871d50c7b18b4212065361ee2.001.1", + "areaDesc": "Marsh and Arbon Highlands; Franklin/Eastern Oneida Region; Bear River Range; Bear Lake Valley; Blackfoot Mountains; Caribou Range", + "geocode": { + "SAME": [ + "016005", + "016011", + "016029", + "016077", + "016041", + "016071", + "016007", + "016019" + ], + "UGC": [ + "IDZ058", + "IDZ059", + "IDZ060", + "IDZ061", + "IDZ062", + "IDZ063" + ] + }, + "affectedZones": [ + "https://api.weather.gov/zones/forecast/IDZ058", + "https://api.weather.gov/zones/forecast/IDZ059", + "https://api.weather.gov/zones/forecast/IDZ060", + "https://api.weather.gov/zones/forecast/IDZ061", + "https://api.weather.gov/zones/forecast/IDZ062", + "https://api.weather.gov/zones/forecast/IDZ063" + ], + "references": [], + "sent": "2026-07-03T16:22:00-06:00", + "effective": "2026-07-03T16:22:00-06:00", + "onset": "2026-07-03T16:22:00-06:00", + "expires": "2026-07-03T17:15:00-06:00", + "ends": null, + "status": "Actual", + "messageType": "Alert", + "category": "Met", + "severity": "Moderate", + "certainty": "Observed", + "urgency": "Expected", + "event": "Special Weather Statement", + "sender": "w-nws.webmaster@noaa.gov", + "senderName": "NWS Pocatello ID", + "headline": "Special Weather Statement issued July 3 at 4:22PM MDT by NWS Pocatello ID", + "description": "At 422 PM MDT, Doppler radar was tracking a strong thunderstorm near\nOneida Narrows Reservoir, or 12 miles northeast of Preston, moving\nnortheast at 35 mph.\n\nHAZARD...Wind gusts in excess of 45 mph and half inch hail.\n\nSOURCE...Radar indicated.\n\nIMPACT...Gusty winds could knock down tree limbs and blow around\nunsecured objects. Minor hail damage to outdoor objects is\npossible.\n\nLocations impacted include...\nGeorgetown, Oneida Narrows Reservoir, Bern, Mink Creek, Georgetown\nSummit, Emmigrant Summit, Liberty, and Bennington.", + "instruction": "If outdoors, consider seeking shelter inside a building.", + "response": "Execute", + "note": null, + "parameters": { + "AWIPSidentifier": [ + "SPSPIH" + ], + "WMOidentifier": [ + "WWUS85 KPIH 032222" + ], + "NWSheadline": [ + "A STRONG THUNDERSTORM WILL IMPACT SOUTH CENTRAL CARIBOU... SOUTHEASTERN BANNOCK...NORTHEASTERN FRANKLIN AND NORTHERN BEAR LAKE COUNTIES THROUGH 515 PM MDT" + ], + "eventMotionDescription": [ + "2026-07-03T22:22:00-00:00...storm...233DEG...29KT...42.26,-111.77" + ], + "maxWindGust": [ + "45 MPH" + ], + "maxHailSize": [ + "0.50" + ], + "BLOCKCHANNEL": [ + "EAS", + "NWEM", + "CMAS" + ], + "EAS-ORG": [ + "WXR" + ] + }, + "scope": "Public", + "code": "IPAWSv1.0", + "language": "en-US", + "web": "http://www.weather.gov", + "eventCode": { + "SAME": [ + "SPS" + ], + "NationalWeatherService": [ + "SPS" + ] + } + } + } + }, + "subject": "central.wx.alert.us.id.county.fips16005", + "captured_epoch": 1783206513 +} \ No newline at end of file diff --git a/work/tests/fixtures/nws/0026.json b/work/tests/fixtures/nws/0026.json new file mode 100644 index 0000000..62af8e6 --- /dev/null +++ b/work/tests/fixtures/nws/0026.json @@ -0,0 +1,130 @@ +{ + "envelope": { + "id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.7726eacdfa9d6f06287272c0cf5f5d4b01afcaad.001.1", + "source": "central.echo6.co", + "type": "central.wx.alert.special_weather_statement.v1", + "time": "2026-07-03T22:46:00+00:00", + "datacontenttype": "application/json", + "centralschemaversion": "1.0", + "centralcategory": "wx.alert.special_weather_statement", + "centralseverity": 2, + "specversion": "1.0", + "data": { + "id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.7726eacdfa9d6f06287272c0cf5f5d4b01afcaad.001.1", + "adapter": "nws", + "category": "wx.alert.special_weather_statement", + "time": "2026-07-03T22:46:00Z", + "expires": "2026-07-03T23:30:00Z", + "severity": 2, + "geo": { + "centroid": [ + -111.46199999999999, + 42.146 + ], + "bbox": [ + -111.7, + 42.0, + -111.07, + 42.39 + ], + "regions": [ + "US-ID-FIPS16007", + "US-ID-FIPS16019", + "US-ID-FIPS16029", + "US-ID-FIPS16041", + "US-ID-Z060", + "US-ID-Z061", + "US-ID-Z063" + ], + "primary_region": "US-ID-FIPS16007", + "geometry": null + }, + "data": { + "@id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.7726eacdfa9d6f06287272c0cf5f5d4b01afcaad.001.1", + "@type": "wx:Alert", + "id": "urn:oid:2.49.0.1.840.0.7726eacdfa9d6f06287272c0cf5f5d4b01afcaad.001.1", + "areaDesc": "Bear River Range; Bear Lake Valley; Caribou Range", + "geocode": { + "SAME": [ + "016007", + "016029", + "016041", + "016019" + ], + "UGC": [ + "IDZ060", + "IDZ061", + "IDZ063" + ] + }, + "affectedZones": [ + "https://api.weather.gov/zones/forecast/IDZ060", + "https://api.weather.gov/zones/forecast/IDZ061", + "https://api.weather.gov/zones/forecast/IDZ063" + ], + "references": [], + "sent": "2026-07-03T16:46:00-06:00", + "effective": "2026-07-03T16:46:00-06:00", + "onset": "2026-07-03T16:46:00-06:00", + "expires": "2026-07-03T17:30:00-06:00", + "ends": null, + "status": "Actual", + "messageType": "Alert", + "category": "Met", + "severity": "Moderate", + "certainty": "Observed", + "urgency": "Expected", + "event": "Special Weather Statement", + "sender": "w-nws.webmaster@noaa.gov", + "senderName": "NWS Pocatello ID", + "headline": "Special Weather Statement issued July 3 at 4:46PM MDT by NWS Pocatello ID", + "description": "At 446 PM MDT, Doppler radar was tracking a cluster of strong\nthunderstorms 9 miles west of Saint Charles, or 12 miles west of Bear\nLake Idaho Portion, moving east at 25 mph.\n\nHAZARD...Wind gusts in excess of 35 mph.\n\nSOURCE...Radar indicated.\n\nIMPACT...Gusty winds could knock down tree limbs and blow around\nunsecured objects.\n\nLocations impacted include...\nMontpelier, Bear Lake Idaho Portion, Saint Charles, Dingle, Paris,\nBloomington, Minnetonka Cave, Ovid, Fish Haven, and Pegram.", + "instruction": "If outdoors, consider seeking shelter inside a building.\n\nIf on or near Bear Lake, get out of the water and move indoors or\ninside a vehicle. Strong winds will create rough chop.\nRemember, lightning can strike out to 10 miles from the parent\nthunderstorm. If you can hear thunder, you are close enough to be\nstruck by lightning. Move to safe shelter now! Do not be caught on\nthe water in a thunderstorm.", + "response": "Execute", + "note": null, + "parameters": { + "AWIPSidentifier": [ + "SPSPIH" + ], + "WMOidentifier": [ + "WWUS85 KPIH 032246" + ], + "NWSheadline": [ + "STRONG THUNDERSTORMS WILL IMPACT SOUTHEASTERN FRANKLIN AND SOUTHERN BEAR LAKE COUNTIES THROUGH 530 PM MDT" + ], + "eventMotionDescription": [ + "2026-07-03T22:46:00-00:00...storm...255DEG...22KT...42.09,-111.57" + ], + "maxWindGust": [ + "35 MPH" + ], + "maxHailSize": [ + "0.00" + ], + "BLOCKCHANNEL": [ + "EAS", + "NWEM", + "CMAS" + ], + "EAS-ORG": [ + "WXR" + ] + }, + "scope": "Public", + "code": "IPAWSv1.0", + "language": "en-US", + "web": "http://www.weather.gov", + "eventCode": { + "SAME": [ + "SPS" + ], + "NationalWeatherService": [ + "SPS" + ] + } + } + } + }, + "subject": "central.wx.alert.us.id.county.fips16007", + "captured_epoch": 1783206513 +} \ No newline at end of file diff --git a/work/tests/fixtures/nws_last/0000.json b/work/tests/fixtures/nws_last/0000.json new file mode 100644 index 0000000..e5d346b --- /dev/null +++ b/work/tests/fixtures/nws_last/0000.json @@ -0,0 +1,118 @@ +{ + "envelope": { + "id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.77e566f81234fa136014003ec08b0d965ee7b4dc.001.1", + "source": "central.echo6.co", + "type": "central.wx.alert.special_weather_statement.v1", + "time": "2026-06-27T23:16:00+00:00", + "datacontenttype": "application/json", + "centralschemaversion": "1.0", + "centralcategory": "wx.alert.special_weather_statement", + "centralseverity": 2, + "specversion": "1.0", + "data": { + "id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.77e566f81234fa136014003ec08b0d965ee7b4dc.001.1", + "adapter": "nws", + "category": "wx.alert.special_weather_statement", + "time": "2026-06-27T23:16:00Z", + "expires": "2026-06-27T23:45:00Z", + "severity": 2, + "geo": { + "centroid": [ + -116.13, + 41.762 + ], + "bbox": [ + -116.29, + 41.68, + -115.89, + 41.92 + ], + "regions": [ + "US-NV-FIPS32007", + "US-NV-Z031" + ], + "primary_region": "US-NV-FIPS32007", + "geometry": null + }, + "data": { + "@id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.77e566f81234fa136014003ec08b0d965ee7b4dc.001.1", + "@type": "wx:Alert", + "id": "urn:oid:2.49.0.1.840.0.77e566f81234fa136014003ec08b0d965ee7b4dc.001.1", + "areaDesc": "Northern Elko County", + "geocode": { + "SAME": [ + "032007" + ], + "UGC": [ + "NVZ031" + ] + }, + "affectedZones": [ + "https://api.weather.gov/zones/forecast/NVZ031" + ], + "references": [], + "sent": "2026-06-27T16:16:00-07:00", + "effective": "2026-06-27T16:16:00-07:00", + "onset": "2026-06-27T16:16:00-07:00", + "expires": "2026-06-27T16:45:00-07:00", + "ends": null, + "status": "Actual", + "messageType": "Alert", + "category": "Met", + "severity": "Moderate", + "certainty": "Observed", + "urgency": "Expected", + "event": "Special Weather Statement", + "sender": "w-nws.webmaster@noaa.gov", + "senderName": "NWS Elko NV", + "headline": "Special Weather Statement issued June 27 at 4:16PM PDT by NWS Elko NV", + "description": "At 416 PM PDT, Doppler radar was tracking a strong thunderstorm\ncapable of producing a landspout 13 miles southwest of Owyhee, moving\nnortheast at 25 mph.\n\nHAZARD...Landspouts, wind gusts up to 40 mph, and half inch hail.\n\nSOURCE...Radar indicated.\n\nIMPACT...Minor damage to outdoor objects is possible. Gusty winds\ncould knock down tree limbs and blow around unsecured\nobjects.\n\nLocations impacted include...\nMountain City.", + "instruction": "If outdoors, consider seeking shelter inside a building.", + "response": "Execute", + "note": null, + "parameters": { + "AWIPSidentifier": [ + "SPSLKN" + ], + "WMOidentifier": [ + "WWUS85 KLKN 272316" + ], + "NWSheadline": [ + "A STRONG THUNDERSTORM WILL IMPACT NORTH CENTRAL ELKO COUNTY THROUGH 445 PM PDT" + ], + "eventMotionDescription": [ + "2026-06-27T23:16:00-00:00...storm...241DEG...22KT...41.76,-116.21" + ], + "maxWindGust": [ + "40 MPH" + ], + "maxHailSize": [ + "0.50" + ], + "BLOCKCHANNEL": [ + "EAS", + "NWEM", + "CMAS" + ], + "EAS-ORG": [ + "WXR" + ] + }, + "scope": "Public", + "code": "IPAWSv1.0", + "language": "en-US", + "web": "http://www.weather.gov", + "eventCode": { + "SAME": [ + "SPS" + ], + "NationalWeatherService": [ + "SPS" + ] + } + } + } + }, + "subject": "central.wx.alert.us.nv.county.fips32007", + "captured_epoch": 1783206518 +} \ No newline at end of file diff --git a/work/tests/fixtures/nws_last/0001.json b/work/tests/fixtures/nws_last/0001.json new file mode 100644 index 0000000..a2d6138 --- /dev/null +++ b/work/tests/fixtures/nws_last/0001.json @@ -0,0 +1,128 @@ +{ + "envelope": { + "id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.dfad3eef33e0c76c3d80f75f404dcd3e2e8b00a8.001.1", + "source": "central.echo6.co", + "type": "central.wx.alert.special_weather_statement.v1", + "time": "2026-06-30T20:09:00+00:00", + "datacontenttype": "application/json", + "centralschemaversion": "1.0", + "centralcategory": "wx.alert.special_weather_statement", + "centralseverity": 2, + "specversion": "1.0", + "data": { + "id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.dfad3eef33e0c76c3d80f75f404dcd3e2e8b00a8.001.1", + "adapter": "nws", + "category": "wx.alert.special_weather_statement", + "time": "2026-06-30T20:09:00Z", + "expires": "2026-06-30T21:00:00Z", + "severity": 2, + "geo": { + "centroid": [ + -113.244, + 43.95 + ], + "bbox": [ + -113.38, + 43.81, + -113.03, + 44.09 + ], + "regions": [ + "US-ID-FIPS16023", + "US-ID-FIPS16033", + "US-ID-FIPS16037", + "US-ID-Z067", + "US-ID-Z068", + "US-ID-Z069" + ], + "primary_region": "US-ID-FIPS16023", + "geometry": null + }, + "data": { + "@id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.dfad3eef33e0c76c3d80f75f404dcd3e2e8b00a8.001.1", + "@type": "wx:Alert", + "id": "urn:oid:2.49.0.1.840.0.dfad3eef33e0c76c3d80f75f404dcd3e2e8b00a8.001.1", + "areaDesc": "Beaverhead/Lemhi Highlands; Lost River Valleys; Lost River Range", + "geocode": { + "SAME": [ + "016023", + "016033", + "016037" + ], + "UGC": [ + "IDZ067", + "IDZ068", + "IDZ069" + ] + }, + "affectedZones": [ + "https://api.weather.gov/zones/forecast/IDZ067", + "https://api.weather.gov/zones/forecast/IDZ068", + "https://api.weather.gov/zones/forecast/IDZ069" + ], + "references": [], + "sent": "2026-06-30T14:09:00-06:00", + "effective": "2026-06-30T14:09:00-06:00", + "onset": "2026-06-30T14:09:00-06:00", + "expires": "2026-06-30T15:00:00-06:00", + "ends": null, + "status": "Actual", + "messageType": "Alert", + "category": "Met", + "severity": "Moderate", + "certainty": "Observed", + "urgency": "Expected", + "event": "Special Weather Statement", + "sender": "w-nws.webmaster@noaa.gov", + "senderName": "NWS Pocatello ID", + "headline": "Special Weather Statement issued June 30 at 2:09PM MDT by NWS Pocatello ID", + "description": "At 209 PM MDT, Doppler radar was tracking a strong thunderstorm 13\nmiles northeast of Darlington, or 17 miles east of Mackay, moving\neast at 5 mph.\n\nHAZARD...Wind gusts in excess of 45 mph and half inch hail.\n\nSOURCE...Radar indicated.\n\nIMPACT...Gusty winds could knock down tree limbs and blow around\nunsecured objects. Minor hail damage to outdoor objects is\npossible.\n\nThis storm will remain over mainly rural areas of north central Butte\nCounty.", + "instruction": "If outdoors, consider seeking shelter inside a building.", + "response": "Execute", + "note": null, + "parameters": { + "AWIPSidentifier": [ + "SPSPIH" + ], + "WMOidentifier": [ + "WWUS85 KPIH 302009" + ], + "NWSheadline": [ + "A STRONG THUNDERSTORM WILL IMPACT NORTH CENTRAL BUTTE COUNTY THROUGH 300 PM MDT" + ], + "eventMotionDescription": [ + "2026-06-30T20:09:00-00:00...storm...275DEG...5KT...43.98,-113.28" + ], + "maxWindGust": [ + "45 MPH" + ], + "maxHailSize": [ + "0.50" + ], + "BLOCKCHANNEL": [ + "EAS", + "NWEM", + "CMAS" + ], + "EAS-ORG": [ + "WXR" + ] + }, + "scope": "Public", + "code": "IPAWSv1.0", + "language": "en-US", + "web": "http://www.weather.gov", + "eventCode": { + "SAME": [ + "SPS" + ], + "NationalWeatherService": [ + "SPS" + ] + } + } + } + }, + "subject": "central.wx.alert.us.id.county.fips16023", + "captured_epoch": 1783206518 +} \ No newline at end of file diff --git a/work/tests/fixtures/nws_last/0002.json b/work/tests/fixtures/nws_last/0002.json new file mode 100644 index 0000000..3f1cbd4 --- /dev/null +++ b/work/tests/fixtures/nws_last/0002.json @@ -0,0 +1,122 @@ +{ + "envelope": { + "id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.c72d73e7d5f08fab03d9bc5e6fd22abd3158545a.001.1", + "source": "central.echo6.co", + "type": "central.wx.alert.special_weather_statement.v1", + "time": "2026-06-30T21:22:00+00:00", + "datacontenttype": "application/json", + "centralschemaversion": "1.0", + "centralcategory": "wx.alert.special_weather_statement", + "centralseverity": 2, + "specversion": "1.0", + "data": { + "id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.c72d73e7d5f08fab03d9bc5e6fd22abd3158545a.001.1", + "adapter": "nws", + "category": "wx.alert.special_weather_statement", + "time": "2026-06-30T21:22:00Z", + "expires": "2026-06-30T21:45:00Z", + "severity": 2, + "geo": { + "centroid": [ + -113.05, + 42.284 + ], + "bbox": [ + -113.26, + 42.18, + -112.76, + 42.48 + ], + "regions": [ + "US-ID-FIPS16031", + "US-ID-FIPS16071", + "US-ID-FIPS16077", + "US-ID-Z057" + ], + "primary_region": "US-ID-FIPS16031", + "geometry": null + }, + "data": { + "@id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.c72d73e7d5f08fab03d9bc5e6fd22abd3158545a.001.1", + "@type": "wx:Alert", + "id": "urn:oid:2.49.0.1.840.0.c72d73e7d5f08fab03d9bc5e6fd22abd3158545a.001.1", + "areaDesc": "Raft River Region", + "geocode": { + "SAME": [ + "016031", + "016071", + "016077" + ], + "UGC": [ + "IDZ057" + ] + }, + "affectedZones": [ + "https://api.weather.gov/zones/forecast/IDZ057" + ], + "references": [], + "sent": "2026-06-30T15:22:00-06:00", + "effective": "2026-06-30T15:22:00-06:00", + "onset": "2026-06-30T15:22:00-06:00", + "expires": "2026-06-30T15:45:00-06:00", + "ends": null, + "status": "Actual", + "messageType": "Alert", + "category": "Met", + "severity": "Moderate", + "certainty": "Observed", + "urgency": "Expected", + "event": "Special Weather Statement", + "sender": "w-nws.webmaster@noaa.gov", + "senderName": "NWS Pocatello ID", + "headline": "Special Weather Statement issued June 30 at 3:22PM MDT by NWS Pocatello ID", + "description": "At 321 PM MDT, Doppler radar was tracking a strong thunderstorm 10\nmiles northwest of Juniper, or 14 miles east of Malta, moving\nnortheast at 20 mph.\n\nHAZARD...Wind gusts in excess of 45 mph and pea size hail.\n\nSOURCE...Radar indicated.\n\nIMPACT...Gusty winds could knock down tree limbs and blow around\nunsecured objects. Minor hail damage to outdoor objects is\npossible.\n\nLocations impacted include...\nSweetzer Summit and Sublett Reservoir.", + "instruction": "If outdoors, consider seeking shelter inside a building.", + "response": "Execute", + "note": null, + "parameters": { + "AWIPSidentifier": [ + "SPSPIH" + ], + "WMOidentifier": [ + "WWUS85 KPIH 302122" + ], + "NWSheadline": [ + "A STRONG THUNDERSTORM WILL IMPACT WEST CENTRAL ONEIDA...SOUTH CENTRAL POWER AND EAST CENTRAL CASSIA COUNTIES THROUGH 345 PM MDT" + ], + "eventMotionDescription": [ + "2026-06-30T21:21:00-00:00...storm...242DEG...16KT...42.3,-113.09" + ], + "maxWindGust": [ + "45 MPH" + ], + "maxHailSize": [ + "0.25" + ], + "BLOCKCHANNEL": [ + "EAS", + "NWEM", + "CMAS" + ], + "EAS-ORG": [ + "WXR" + ] + }, + "scope": "Public", + "code": "IPAWSv1.0", + "language": "en-US", + "web": "http://www.weather.gov", + "eventCode": { + "SAME": [ + "SPS" + ], + "NationalWeatherService": [ + "SPS" + ] + } + } + } + }, + "subject": "central.wx.alert.us.id.county.fips16031", + "captured_epoch": 1783206518 +} \ No newline at end of file diff --git a/work/tests/fixtures/nws_last/0003.json b/work/tests/fixtures/nws_last/0003.json new file mode 100644 index 0000000..b597e22 --- /dev/null +++ b/work/tests/fixtures/nws_last/0003.json @@ -0,0 +1,127 @@ +{ + "envelope": { + "id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.2dede2c223ae88415b686fbbe38a0834933ae5a6.001.1", + "source": "central.echo6.co", + "type": "central.wx.alert.special_weather_statement.v1", + "time": "2026-07-01T21:26:00+00:00", + "datacontenttype": "application/json", + "centralschemaversion": "1.0", + "centralcategory": "wx.alert.special_weather_statement", + "centralseverity": 2, + "specversion": "1.0", + "data": { + "id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.2dede2c223ae88415b686fbbe38a0834933ae5a6.001.1", + "adapter": "nws", + "category": "wx.alert.special_weather_statement", + "time": "2026-07-01T21:26:00Z", + "expires": "2026-07-01T22:00:00Z", + "severity": 2, + "geo": { + "centroid": [ + -114.1211111111111, + 42.31777777777778 + ], + "bbox": [ + -114.29, + 42.0, + -113.71, + 42.52 + ], + "regions": [ + "US-ID-FIPS16013", + "US-ID-FIPS16031", + "US-ID-FIPS16067", + "US-ID-FIPS16077", + "US-ID-Z055", + "US-ID-Z056" + ], + "primary_region": "US-ID-FIPS16013", + "geometry": null + }, + "data": { + "@id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.2dede2c223ae88415b686fbbe38a0834933ae5a6.001.1", + "@type": "wx:Alert", + "id": "urn:oid:2.49.0.1.840.0.2dede2c223ae88415b686fbbe38a0834933ae5a6.001.1", + "areaDesc": "Eastern Magic Valley; Southern Hills/Albion Mountains", + "geocode": { + "SAME": [ + "016013", + "016031", + "016067", + "016077" + ], + "UGC": [ + "IDZ055", + "IDZ056" + ] + }, + "affectedZones": [ + "https://api.weather.gov/zones/forecast/IDZ055", + "https://api.weather.gov/zones/forecast/IDZ056" + ], + "references": [], + "sent": "2026-07-01T15:26:00-06:00", + "effective": "2026-07-01T15:26:00-06:00", + "onset": "2026-07-01T15:26:00-06:00", + "expires": "2026-07-01T16:00:00-06:00", + "ends": null, + "status": "Actual", + "messageType": "Alert", + "category": "Met", + "severity": "Moderate", + "certainty": "Observed", + "urgency": "Expected", + "event": "Special Weather Statement", + "sender": "w-nws.webmaster@noaa.gov", + "senderName": "NWS Pocatello ID", + "headline": "Special Weather Statement issued July 1 at 3:26PM MDT by NWS Pocatello ID", + "description": "At 326 PM MDT, Doppler radar was tracking a strong thunderstorm 11\nmiles west of Oakley Reservoir, moving northeast at 30 mph.\n\nHAZARD...Wind gusts in excess of 45 mph and nickel size hail.\n\nSOURCE...Radar indicated.\n\nIMPACT...Gusty winds could knock down tree limbs and blow around\nunsecured objects. Minor hail damage to outdoor objects is\npossible.\n\nLocations impacted include...\nOakley Reservoir, Oakley, and Bostetter Ranger Station.", + "instruction": "If outdoors, consider seeking shelter inside a building.\n\nFrequent cloud to ground lightning is occurring with this storm.\nLightning can strike 10 miles away from a thunderstorm. Seek a safe\nshelter inside a building or vehicle.", + "response": "Execute", + "note": null, + "parameters": { + "AWIPSidentifier": [ + "SPSPIH" + ], + "WMOidentifier": [ + "WWUS85 KPIH 012126" + ], + "NWSheadline": [ + "A STRONG THUNDERSTORM WILL IMPACT SOUTHWESTERN CASSIA COUNTY THROUGH 400 PM MDT" + ], + "eventMotionDescription": [ + "2026-07-01T21:26:00-00:00...storm...221DEG...24KT...42.19,-114.15" + ], + "maxWindGust": [ + "45 MPH" + ], + "maxHailSize": [ + "0.88" + ], + "BLOCKCHANNEL": [ + "EAS", + "NWEM", + "CMAS" + ], + "EAS-ORG": [ + "WXR" + ] + }, + "scope": "Public", + "code": "IPAWSv1.0", + "language": "en-US", + "web": "http://www.weather.gov", + "eventCode": { + "SAME": [ + "SPS" + ], + "NationalWeatherService": [ + "SPS" + ] + } + } + } + }, + "subject": "central.wx.alert.us.id.county.fips16013", + "captured_epoch": 1783206518 +} \ No newline at end of file diff --git a/work/tests/fixtures/nws_last/0004.json b/work/tests/fixtures/nws_last/0004.json new file mode 100644 index 0000000..86e4d12 --- /dev/null +++ b/work/tests/fixtures/nws_last/0004.json @@ -0,0 +1,137 @@ +{ + "envelope": { + "id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.d854d25f06637c7ac4dc7614f2d6254a7f227873.001.1", + "source": "central.echo6.co", + "type": "central.wx.alert.severe_thunderstorm_warning.v1", + "time": "2026-07-01T22:04:00+00:00", + "datacontenttype": "application/json", + "centralschemaversion": "1.0", + "centralcategory": "wx.alert.severe_thunderstorm_warning", + "centralseverity": 3, + "specversion": "1.0", + "data": { + "id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.d854d25f06637c7ac4dc7614f2d6254a7f227873.001.1", + "adapter": "nws", + "category": "wx.alert.severe_thunderstorm_warning", + "time": "2026-07-01T22:04:00Z", + "expires": "2026-07-01T22:30:00Z", + "severity": 3, + "geo": { + "centroid": [ + -113.902, + 42.257999999999996 + ], + "bbox": [ + -114.11, + 42.16, + -113.58, + 42.47 + ], + "regions": [ + "US-ID-C031", + "US-ID-FIPS16031" + ], + "primary_region": "US-ID-C031", + "geometry": null + }, + "data": { + "@id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.d854d25f06637c7ac4dc7614f2d6254a7f227873.001.1", + "@type": "wx:Alert", + "id": "urn:oid:2.49.0.1.840.0.d854d25f06637c7ac4dc7614f2d6254a7f227873.001.1", + "areaDesc": "Cassia, ID", + "geocode": { + "SAME": [ + "016031" + ], + "UGC": [ + "IDC031" + ] + }, + "affectedZones": [ + "https://api.weather.gov/zones/county/IDC031" + ], + "references": [ + { + "@id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.c6add6b54c87dec0372f5cac3b3e3da2c46c6320.001.1", + "identifier": "urn:oid:2.49.0.1.840.0.c6add6b54c87dec0372f5cac3b3e3da2c46c6320.001.1", + "sender": "w-nws.webmaster@noaa.gov", + "sent": "2026-07-01T15:50:00-06:00" + } + ], + "sent": "2026-07-01T16:04:00-06:00", + "effective": "2026-07-01T16:04:00-06:00", + "onset": "2026-07-01T16:04:00-06:00", + "expires": "2026-07-01T16:30:00-06:00", + "ends": "2026-07-01T16:30:00-06:00", + "status": "Actual", + "messageType": "Update", + "category": "Met", + "severity": "Severe", + "certainty": "Observed", + "urgency": "Immediate", + "event": "Severe Thunderstorm Warning", + "sender": "w-nws.webmaster@noaa.gov", + "senderName": "NWS Pocatello ID", + "headline": "Severe Thunderstorm Warning issued July 1 at 4:04PM MDT until July 1 at 4:30PM MDT by NWS Pocatello ID", + "description": "At 403 PM MDT, a severe thunderstorm was located over Oakley, or near\nOakley Reservoir, moving northeast at 20 mph.\n\nHAZARD...Quarter size hail.\n\nSOURCE...Radar indicated.\n\nIMPACT...Damage to vehicles is expected.\n\nLocations impacted include...\nOakley Reservoir and Oakley.", + "instruction": "For your protection move to an interior room on the lowest floor of a\nbuilding.\n\nTorrential rainfall is occurring with this storm, and may lead to\nflash flooding. Do not drive your vehicle through flooded roadways.", + "response": "Shelter", + "note": null, + "parameters": { + "AWIPSidentifier": [ + "SVSPIH" + ], + "WMOidentifier": [ + "WWUS55 KPIH 012204" + ], + "NWSheadline": [ + "A SEVERE THUNDERSTORM WARNING REMAINS IN EFFECT UNTIL 430 PM MDT FOR WEST CENTRAL CASSIA COUNTY" + ], + "eventMotionDescription": [ + "2026-07-01T22:03:00-00:00...storm...246DEG...17KT...42.25,-113.93" + ], + "windThreat": [ + "RADAR INDICATED" + ], + "maxWindGust": [ + "Up to 50 MPH" + ], + "hailThreat": [ + "RADAR INDICATED" + ], + "maxHailSize": [ + "1.00" + ], + "BLOCKCHANNEL": [ + "EAS", + "NWEM", + "CMAS" + ], + "EAS-ORG": [ + "WXR" + ], + "VTEC": [ + "/O.CON.KPIH.SV.W.0027.000000T0000Z-260701T2230Z/" + ], + "eventEndingTime": [ + "2026-07-01T16:30:00-06:00" + ] + }, + "scope": "Public", + "code": "IPAWSv1.0", + "language": "en-US", + "web": "http://www.weather.gov", + "eventCode": { + "SAME": [ + "SVS" + ], + "NationalWeatherService": [ + "SVW" + ] + } + } + } + }, + "subject": "central.wx.alert.us.id.county.c031", + "captured_epoch": 1783206518 +} \ No newline at end of file diff --git a/work/tests/fixtures/nws_last/0005.json b/work/tests/fixtures/nws_last/0005.json new file mode 100644 index 0000000..d259ea9 --- /dev/null +++ b/work/tests/fixtures/nws_last/0005.json @@ -0,0 +1,122 @@ +{ + "envelope": { + "id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.24dbf178f22821530104c9956cb76aa7971b6359.001.1", + "source": "central.echo6.co", + "type": "central.wx.alert.special_weather_statement.v1", + "time": "2026-07-01T23:26:00+00:00", + "datacontenttype": "application/json", + "centralschemaversion": "1.0", + "centralcategory": "wx.alert.special_weather_statement", + "centralseverity": 2, + "specversion": "1.0", + "data": { + "id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.24dbf178f22821530104c9956cb76aa7971b6359.001.1", + "adapter": "nws", + "category": "wx.alert.special_weather_statement", + "time": "2026-07-01T23:26:00Z", + "expires": "2026-07-02T00:15:00Z", + "severity": 2, + "geo": { + "centroid": [ + -114.67999999999999, + 42.642 + ], + "bbox": [ + -114.87, + 42.5, + -114.39, + 42.89 + ], + "regions": [ + "US-ID-FIPS16047", + "US-ID-FIPS16053", + "US-ID-FIPS16083", + "US-ID-Z016" + ], + "primary_region": "US-ID-FIPS16047", + "geometry": null + }, + "data": { + "@id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.24dbf178f22821530104c9956cb76aa7971b6359.001.1", + "@type": "wx:Alert", + "id": "urn:oid:2.49.0.1.840.0.24dbf178f22821530104c9956cb76aa7971b6359.001.1", + "areaDesc": "Western Magic Valley", + "geocode": { + "SAME": [ + "016047", + "016053", + "016083" + ], + "UGC": [ + "IDZ016" + ] + }, + "affectedZones": [ + "https://api.weather.gov/zones/forecast/IDZ016" + ], + "references": [], + "sent": "2026-07-01T17:26:00-06:00", + "effective": "2026-07-01T17:26:00-06:00", + "onset": "2026-07-01T17:26:00-06:00", + "expires": "2026-07-01T18:15:00-06:00", + "ends": null, + "status": "Actual", + "messageType": "Alert", + "category": "Met", + "severity": "Moderate", + "certainty": "Observed", + "urgency": "Expected", + "event": "Special Weather Statement", + "sender": "w-nws.webmaster@noaa.gov", + "senderName": "NWS Boise ID", + "headline": "Special Weather Statement issued July 1 at 5:26PM MDT by NWS Boise ID", + "description": "At 526 PM MDT, Doppler radar was tracking a strong thunderstorm over\nBuhl, or 14 miles southwest of Jerome, moving northeast at 20 mph.\n\nHAZARD...Wind gusts up to 40 mph and half inch hail.\n\nSOURCE...Radar indicated.\n\nIMPACT...Gusty winds could knock down tree limbs and blow around\nunsecured objects. Areas of blowing dust possible. Minor\ndamage to outdoor objects is possible.\n\nLocations impacted include...\nJerome, Buhl, Wendell, and Filer.", + "instruction": "If outdoors, consider seeking shelter inside a building.\n\nFrequent cloud to ground lightning is occurring with this storm.\nLightning can strike 10 miles away from a thunderstorm. Seek a safe\nshelter inside a building or vehicle.", + "response": "Execute", + "note": null, + "parameters": { + "AWIPSidentifier": [ + "SPSBOI" + ], + "WMOidentifier": [ + "WWUS85 KBOI 012326" + ], + "NWSheadline": [ + "A STRONG THUNDERSTORM WILL IMPACT WEST CENTRAL TWIN FALLS...WEST CENTRAL JEROME AND SOUTHEASTERN GOODING COUNTIES THROUGH 615 PM MDT" + ], + "eventMotionDescription": [ + "2026-07-01T23:26:00-00:00...storm...216DEG...17KT...42.61,-114.77" + ], + "maxWindGust": [ + "40 MPH" + ], + "maxHailSize": [ + "0.50" + ], + "BLOCKCHANNEL": [ + "EAS", + "NWEM", + "CMAS" + ], + "EAS-ORG": [ + "WXR" + ] + }, + "scope": "Public", + "code": "IPAWSv1.0", + "language": "en-US", + "web": "http://www.weather.gov", + "eventCode": { + "SAME": [ + "SPS" + ], + "NationalWeatherService": [ + "SPS" + ] + } + } + } + }, + "subject": "central.wx.alert.us.id.county.fips16047", + "captured_epoch": 1783206518 +} \ No newline at end of file diff --git a/work/tests/fixtures/nws_last/0006.json b/work/tests/fixtures/nws_last/0006.json new file mode 100644 index 0000000..80bbd18 --- /dev/null +++ b/work/tests/fixtures/nws_last/0006.json @@ -0,0 +1,136 @@ +{ + "envelope": { + "id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.e1a929f5a667f978203865e862ad31f369d5466a.001.1", + "source": "central.echo6.co", + "type": "central.wx.alert.special_weather_statement.v1", + "time": "2026-07-02T00:33:00+00:00", + "datacontenttype": "application/json", + "centralschemaversion": "1.0", + "centralcategory": "wx.alert.special_weather_statement", + "centralseverity": 2, + "specversion": "1.0", + "data": { + "id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.e1a929f5a667f978203865e862ad31f369d5466a.001.1", + "adapter": "nws", + "category": "wx.alert.special_weather_statement", + "time": "2026-07-02T00:33:00Z", + "expires": "2026-07-02T01:00:00Z", + "severity": 2, + "geo": { + "centroid": [ + -112.37, + 43.882 + ], + "bbox": [ + -112.64, + 43.69, + -111.99, + 44.18 + ], + "regions": [ + "US-ID-FIPS16011", + "US-ID-FIPS16019", + "US-ID-FIPS16023", + "US-ID-FIPS16033", + "US-ID-FIPS16043", + "US-ID-FIPS16051", + "US-ID-FIPS16065", + "US-ID-Z052", + "US-ID-Z053", + "US-ID-Z067" + ], + "primary_region": "US-ID-FIPS16011", + "geometry": null + }, + "data": { + "@id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.e1a929f5a667f978203865e862ad31f369d5466a.001.1", + "@type": "wx:Alert", + "id": "urn:oid:2.49.0.1.840.0.e1a929f5a667f978203865e862ad31f369d5466a.001.1", + "areaDesc": "Arco/Mud Lake Desert; Upper Snake River Plain; Beaverhead/Lemhi Highlands", + "geocode": { + "SAME": [ + "016011", + "016019", + "016023", + "016051", + "016043", + "016065", + "016033" + ], + "UGC": [ + "IDZ052", + "IDZ053", + "IDZ067" + ] + }, + "affectedZones": [ + "https://api.weather.gov/zones/forecast/IDZ052", + "https://api.weather.gov/zones/forecast/IDZ053", + "https://api.weather.gov/zones/forecast/IDZ067" + ], + "references": [], + "sent": "2026-07-01T18:33:00-06:00", + "effective": "2026-07-01T18:33:00-06:00", + "onset": "2026-07-01T18:33:00-06:00", + "expires": "2026-07-01T19:00:00-06:00", + "ends": null, + "status": "Actual", + "messageType": "Alert", + "category": "Met", + "severity": "Moderate", + "certainty": "Observed", + "urgency": "Expected", + "event": "Special Weather Statement", + "sender": "w-nws.webmaster@noaa.gov", + "senderName": "NWS Pocatello ID", + "headline": "Special Weather Statement issued July 1 at 6:33PM MDT by NWS Pocatello ID", + "description": "At 633 PM MDT, Doppler radar was tracking a cluster of strong\nthunderstorms over Terreton, moving northeast at 20 mph.\n\nHAZARD...Wind gusts in excess of 45 mph and half inch hail.\n\nSOURCE...Radar indicated.\n\nIMPACT...Gusty winds could knock down tree limbs and blow around\nunsecured objects. Minor hail damage to outdoor objects is\npossible.\n\nLocations impacted include...\nTerreton, Mud Lake, and Hamer.", + "instruction": "If outdoors, consider seeking shelter inside a building.", + "response": "Execute", + "note": null, + "parameters": { + "AWIPSidentifier": [ + "SPSPIH" + ], + "WMOidentifier": [ + "WWUS85 KPIH 020033" + ], + "NWSheadline": [ + "STRONG THUNDERSTORMS WILL IMPACT SOUTHWESTERN FREMONT...WESTERN JEFFERSON AND SOUTH CENTRAL CLARK COUNTIES THROUGH 700 PM MDT" + ], + "eventMotionDescription": [ + "2026-07-02T00:33:00-00:00...storm...211DEG...20KT...43.86,-112.43" + ], + "maxWindGust": [ + "45 MPH" + ], + "maxHailSize": [ + "0.50" + ], + "BLOCKCHANNEL": [ + "EAS", + "NWEM", + "CMAS" + ], + "EAS-ORG": [ + "WXR" + ] + }, + "scope": "Public", + "code": "IPAWSv1.0", + "language": "en-US", + "web": "http://www.weather.gov", + "eventCode": { + "SAME": [ + "SPS" + ], + "NationalWeatherService": [ + "SPS" + ] + } + } + } + }, + "subject": "central.wx.alert.us.id.county.fips16011", + "captured_epoch": 1783206518 +} \ No newline at end of file diff --git a/work/tests/fixtures/nws_last/0007.json b/work/tests/fixtures/nws_last/0007.json new file mode 100644 index 0000000..3c6f056 --- /dev/null +++ b/work/tests/fixtures/nws_last/0007.json @@ -0,0 +1,137 @@ +{ + "envelope": { + "id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.e58c2f30c093ce7defdf71a90e452af5ec568023.001.1", + "source": "central.echo6.co", + "type": "central.wx.alert.special_weather_statement.v1", + "time": "2026-07-02T23:30:00+00:00", + "datacontenttype": "application/json", + "centralschemaversion": "1.0", + "centralcategory": "wx.alert.special_weather_statement", + "centralseverity": 2, + "specversion": "1.0", + "data": { + "id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.e58c2f30c093ce7defdf71a90e452af5ec568023.001.1", + "adapter": "nws", + "category": "wx.alert.special_weather_statement", + "time": "2026-07-02T23:30:00Z", + "expires": "2026-07-03T00:15:00Z", + "severity": 2, + "geo": { + "centroid": [ + -112.62, + 41.378571428571426 + ], + "bbox": [ + -113.15, + 40.64, + -112.25, + 42.0 + ], + "regions": [ + "US-UT-FIPS49003", + "US-UT-FIPS49011", + "US-UT-FIPS49035", + "US-UT-FIPS49045", + "US-UT-FIPS49049", + "US-UT-FIPS49057", + "US-UT-Z101", + "US-UT-Z102", + "US-UT-Z103", + "US-UT-Z104" + ], + "primary_region": "US-UT-FIPS49003", + "geometry": null + }, + "data": { + "@id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.e58c2f30c093ce7defdf71a90e452af5ec568023.001.1", + "@type": "wx:Alert", + "id": "urn:oid:2.49.0.1.840.0.e58c2f30c093ce7defdf71a90e452af5ec568023.001.1", + "areaDesc": "Great Salt Lake Desert and Mountains; Tooele and Rush Valleys; Eastern Box Elder County; Northern Wasatch Front", + "geocode": { + "SAME": [ + "049003", + "049035", + "049045", + "049049", + "049011", + "049057" + ], + "UGC": [ + "UTZ101", + "UTZ102", + "UTZ103", + "UTZ104" + ] + }, + "affectedZones": [ + "https://api.weather.gov/zones/forecast/UTZ101", + "https://api.weather.gov/zones/forecast/UTZ102", + "https://api.weather.gov/zones/forecast/UTZ103", + "https://api.weather.gov/zones/forecast/UTZ104" + ], + "references": [], + "sent": "2026-07-02T17:30:00-06:00", + "effective": "2026-07-02T17:30:00-06:00", + "onset": "2026-07-02T17:30:00-06:00", + "expires": "2026-07-02T18:15:00-06:00", + "ends": null, + "status": "Actual", + "messageType": "Alert", + "category": "Met", + "severity": "Moderate", + "certainty": "Observed", + "urgency": "Expected", + "event": "Special Weather Statement", + "sender": "w-nws.webmaster@noaa.gov", + "senderName": "NWS Salt Lake City UT", + "headline": "Special Weather Statement issued July 2 at 5:30PM MDT by NWS Salt Lake City UT", + "description": "At 528 PM MDT, Doppler radar was tracking strong thunderstorm\noutflow winds along a line extending from 16 miles east of Park\nValley to 27 miles southwest of Howell to near Great Salt Lake North\nof the Causeway to 17 miles west of Great Salt Lake South of the\nCauseway to 8 miles northeast of Delle. Movement was east at 25 mph.\n\nHAZARD...Wind gusts up to 50 mph.\n\nSOURCE...Radar indicated.\n\nIMPACT...Gusty winds could knock down tree limbs and blow around\nunsecured objects.\n\nLocations impacted include...\nSnowville, Stansbury Park, Howell, Delle, Great Salt Lake North of\nthe Causeway, Great Salt Lake South of the Causeway, Hat Island,\nPromontory, Gunnison Island, Locomotive Springs, Curlew Junction,\nLake Point, Golden Spike Historic Site, and Antelope Island State\nPark.\n\nThis includes the following highways...\nInterstate 84 between mile markers 1 and 21.\nInterstate 80 in Utah between mile markers 67 and 99.\nUtah Route 30 between mile markers 69 and 90.", + "instruction": "If outdoors, consider seeking shelter inside a building.", + "response": "Execute", + "note": null, + "parameters": { + "AWIPSidentifier": [ + "SPSSLC" + ], + "WMOidentifier": [ + "WWUS85 KSLC 022330" + ], + "NWSheadline": [ + "Strong thunderstorm outflow winds will impact portions of southwestern Weber, central Box Elder, western Davis and northeastern Tooele Counties through 615 PM MDT" + ], + "eventMotionDescription": [ + "2026-07-02T23:28:00-00:00...storm...247DEG...21KT...41.83,-113.01 41.58,-112.9 41.36,-112.83 41.11,-112.75 40.89,-112.71" + ], + "maxWindGust": [ + "50 MPH" + ], + "maxHailSize": [ + "0.00" + ], + "BLOCKCHANNEL": [ + "EAS", + "NWEM", + "CMAS" + ], + "EAS-ORG": [ + "WXR" + ] + }, + "scope": "Public", + "code": "IPAWSv1.0", + "language": "en-US", + "web": "http://www.weather.gov", + "eventCode": { + "SAME": [ + "SPS" + ], + "NationalWeatherService": [ + "SPS" + ] + } + } + } + }, + "subject": "central.wx.alert.us.ut.county.fips49003", + "captured_epoch": 1783206518 +} \ No newline at end of file diff --git a/work/tests/fixtures/nws_last/0008.json b/work/tests/fixtures/nws_last/0008.json new file mode 100644 index 0000000..a64e35a --- /dev/null +++ b/work/tests/fixtures/nws_last/0008.json @@ -0,0 +1,147 @@ +{ + "envelope": { + "id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.e17d529642f7f75871d50c7b18b4212065361ee2.001.1", + "source": "central.echo6.co", + "type": "central.wx.alert.special_weather_statement.v1", + "time": "2026-07-03T22:22:00+00:00", + "datacontenttype": "application/json", + "centralschemaversion": "1.0", + "centralcategory": "wx.alert.special_weather_statement", + "centralseverity": 2, + "specversion": "1.0", + "data": { + "id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.e17d529642f7f75871d50c7b18b4212065361ee2.001.1", + "adapter": "nws", + "category": "wx.alert.special_weather_statement", + "time": "2026-07-03T22:22:00Z", + "expires": "2026-07-03T23:15:00Z", + "severity": 2, + "geo": { + "centroid": [ + -111.606, + 42.312 + ], + "bbox": [ + -111.92, + 42.13, + -111.16, + 42.62 + ], + "regions": [ + "US-ID-FIPS16005", + "US-ID-FIPS16007", + "US-ID-FIPS16011", + "US-ID-FIPS16019", + "US-ID-FIPS16029", + "US-ID-FIPS16041", + "US-ID-FIPS16071", + "US-ID-FIPS16077", + "US-ID-Z058", + "US-ID-Z059", + "US-ID-Z060", + "US-ID-Z061", + "US-ID-Z062", + "US-ID-Z063" + ], + "primary_region": "US-ID-FIPS16005", + "geometry": null + }, + "data": { + "@id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.e17d529642f7f75871d50c7b18b4212065361ee2.001.1", + "@type": "wx:Alert", + "id": "urn:oid:2.49.0.1.840.0.e17d529642f7f75871d50c7b18b4212065361ee2.001.1", + "areaDesc": "Marsh and Arbon Highlands; Franklin/Eastern Oneida Region; Bear River Range; Bear Lake Valley; Blackfoot Mountains; Caribou Range", + "geocode": { + "SAME": [ + "016005", + "016011", + "016029", + "016077", + "016041", + "016071", + "016007", + "016019" + ], + "UGC": [ + "IDZ058", + "IDZ059", + "IDZ060", + "IDZ061", + "IDZ062", + "IDZ063" + ] + }, + "affectedZones": [ + "https://api.weather.gov/zones/forecast/IDZ058", + "https://api.weather.gov/zones/forecast/IDZ059", + "https://api.weather.gov/zones/forecast/IDZ060", + "https://api.weather.gov/zones/forecast/IDZ061", + "https://api.weather.gov/zones/forecast/IDZ062", + "https://api.weather.gov/zones/forecast/IDZ063" + ], + "references": [], + "sent": "2026-07-03T16:22:00-06:00", + "effective": "2026-07-03T16:22:00-06:00", + "onset": "2026-07-03T16:22:00-06:00", + "expires": "2026-07-03T17:15:00-06:00", + "ends": null, + "status": "Actual", + "messageType": "Alert", + "category": "Met", + "severity": "Moderate", + "certainty": "Observed", + "urgency": "Expected", + "event": "Special Weather Statement", + "sender": "w-nws.webmaster@noaa.gov", + "senderName": "NWS Pocatello ID", + "headline": "Special Weather Statement issued July 3 at 4:22PM MDT by NWS Pocatello ID", + "description": "At 422 PM MDT, Doppler radar was tracking a strong thunderstorm near\nOneida Narrows Reservoir, or 12 miles northeast of Preston, moving\nnortheast at 35 mph.\n\nHAZARD...Wind gusts in excess of 45 mph and half inch hail.\n\nSOURCE...Radar indicated.\n\nIMPACT...Gusty winds could knock down tree limbs and blow around\nunsecured objects. Minor hail damage to outdoor objects is\npossible.\n\nLocations impacted include...\nGeorgetown, Oneida Narrows Reservoir, Bern, Mink Creek, Georgetown\nSummit, Emmigrant Summit, Liberty, and Bennington.", + "instruction": "If outdoors, consider seeking shelter inside a building.", + "response": "Execute", + "note": null, + "parameters": { + "AWIPSidentifier": [ + "SPSPIH" + ], + "WMOidentifier": [ + "WWUS85 KPIH 032222" + ], + "NWSheadline": [ + "A STRONG THUNDERSTORM WILL IMPACT SOUTH CENTRAL CARIBOU... SOUTHEASTERN BANNOCK...NORTHEASTERN FRANKLIN AND NORTHERN BEAR LAKE COUNTIES THROUGH 515 PM MDT" + ], + "eventMotionDescription": [ + "2026-07-03T22:22:00-00:00...storm...233DEG...29KT...42.26,-111.77" + ], + "maxWindGust": [ + "45 MPH" + ], + "maxHailSize": [ + "0.50" + ], + "BLOCKCHANNEL": [ + "EAS", + "NWEM", + "CMAS" + ], + "EAS-ORG": [ + "WXR" + ] + }, + "scope": "Public", + "code": "IPAWSv1.0", + "language": "en-US", + "web": "http://www.weather.gov", + "eventCode": { + "SAME": [ + "SPS" + ], + "NationalWeatherService": [ + "SPS" + ] + } + } + } + }, + "subject": "central.wx.alert.us.id.county.fips16005", + "captured_epoch": 1783206518 +} \ No newline at end of file diff --git a/work/tests/fixtures/nws_last/0009.json b/work/tests/fixtures/nws_last/0009.json new file mode 100644 index 0000000..5873310 --- /dev/null +++ b/work/tests/fixtures/nws_last/0009.json @@ -0,0 +1,130 @@ +{ + "envelope": { + "id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.7726eacdfa9d6f06287272c0cf5f5d4b01afcaad.001.1", + "source": "central.echo6.co", + "type": "central.wx.alert.special_weather_statement.v1", + "time": "2026-07-03T22:46:00+00:00", + "datacontenttype": "application/json", + "centralschemaversion": "1.0", + "centralcategory": "wx.alert.special_weather_statement", + "centralseverity": 2, + "specversion": "1.0", + "data": { + "id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.7726eacdfa9d6f06287272c0cf5f5d4b01afcaad.001.1", + "adapter": "nws", + "category": "wx.alert.special_weather_statement", + "time": "2026-07-03T22:46:00Z", + "expires": "2026-07-03T23:30:00Z", + "severity": 2, + "geo": { + "centroid": [ + -111.46199999999999, + 42.146 + ], + "bbox": [ + -111.7, + 42.0, + -111.07, + 42.39 + ], + "regions": [ + "US-ID-FIPS16007", + "US-ID-FIPS16019", + "US-ID-FIPS16029", + "US-ID-FIPS16041", + "US-ID-Z060", + "US-ID-Z061", + "US-ID-Z063" + ], + "primary_region": "US-ID-FIPS16007", + "geometry": null + }, + "data": { + "@id": "https://api.weather.gov/alerts/urn:oid:2.49.0.1.840.0.7726eacdfa9d6f06287272c0cf5f5d4b01afcaad.001.1", + "@type": "wx:Alert", + "id": "urn:oid:2.49.0.1.840.0.7726eacdfa9d6f06287272c0cf5f5d4b01afcaad.001.1", + "areaDesc": "Bear River Range; Bear Lake Valley; Caribou Range", + "geocode": { + "SAME": [ + "016007", + "016029", + "016041", + "016019" + ], + "UGC": [ + "IDZ060", + "IDZ061", + "IDZ063" + ] + }, + "affectedZones": [ + "https://api.weather.gov/zones/forecast/IDZ060", + "https://api.weather.gov/zones/forecast/IDZ061", + "https://api.weather.gov/zones/forecast/IDZ063" + ], + "references": [], + "sent": "2026-07-03T16:46:00-06:00", + "effective": "2026-07-03T16:46:00-06:00", + "onset": "2026-07-03T16:46:00-06:00", + "expires": "2026-07-03T17:30:00-06:00", + "ends": null, + "status": "Actual", + "messageType": "Alert", + "category": "Met", + "severity": "Moderate", + "certainty": "Observed", + "urgency": "Expected", + "event": "Special Weather Statement", + "sender": "w-nws.webmaster@noaa.gov", + "senderName": "NWS Pocatello ID", + "headline": "Special Weather Statement issued July 3 at 4:46PM MDT by NWS Pocatello ID", + "description": "At 446 PM MDT, Doppler radar was tracking a cluster of strong\nthunderstorms 9 miles west of Saint Charles, or 12 miles west of Bear\nLake Idaho Portion, moving east at 25 mph.\n\nHAZARD...Wind gusts in excess of 35 mph.\n\nSOURCE...Radar indicated.\n\nIMPACT...Gusty winds could knock down tree limbs and blow around\nunsecured objects.\n\nLocations impacted include...\nMontpelier, Bear Lake Idaho Portion, Saint Charles, Dingle, Paris,\nBloomington, Minnetonka Cave, Ovid, Fish Haven, and Pegram.", + "instruction": "If outdoors, consider seeking shelter inside a building.\n\nIf on or near Bear Lake, get out of the water and move indoors or\ninside a vehicle. Strong winds will create rough chop.\nRemember, lightning can strike out to 10 miles from the parent\nthunderstorm. If you can hear thunder, you are close enough to be\nstruck by lightning. Move to safe shelter now! Do not be caught on\nthe water in a thunderstorm.", + "response": "Execute", + "note": null, + "parameters": { + "AWIPSidentifier": [ + "SPSPIH" + ], + "WMOidentifier": [ + "WWUS85 KPIH 032246" + ], + "NWSheadline": [ + "STRONG THUNDERSTORMS WILL IMPACT SOUTHEASTERN FRANKLIN AND SOUTHERN BEAR LAKE COUNTIES THROUGH 530 PM MDT" + ], + "eventMotionDescription": [ + "2026-07-03T22:46:00-00:00...storm...255DEG...22KT...42.09,-111.57" + ], + "maxWindGust": [ + "35 MPH" + ], + "maxHailSize": [ + "0.00" + ], + "BLOCKCHANNEL": [ + "EAS", + "NWEM", + "CMAS" + ], + "EAS-ORG": [ + "WXR" + ] + }, + "scope": "Public", + "code": "IPAWSv1.0", + "language": "en-US", + "web": "http://www.weather.gov", + "eventCode": { + "SAME": [ + "SPS" + ], + "NationalWeatherService": [ + "SPS" + ] + } + } + } + }, + "subject": "central.wx.alert.us.id.county.fips16007", + "captured_epoch": 1783206518 +} \ No newline at end of file diff --git a/work/tests/fixtures/traffic/0000.json b/work/tests/fixtures/traffic/0000.json new file mode 100644 index 0000000..6ed2f6f --- /dev/null +++ b/work/tests/fixtures/traffic/0000.json @@ -0,0 +1,118 @@ +{ + "envelope": { + "id": "ID:tomtom:TTI-4ee2bdba-b112-4223-894d-2b10d1a81de0-TTL14772873060027000", + "source": "central.echo6.co", + "type": "central.incident.tomtom_incidents.v1", + "time": "2026-06-27T23:39:30+00:00", + "datacontenttype": "application/json", + "centralschemaversion": "1.0", + "centralcategory": "incident.tomtom_incidents", + "centralseverity": 3, + "specversion": "1.0", + "data": { + "id": "ID:tomtom:TTI-4ee2bdba-b112-4223-894d-2b10d1a81de0-TTL14772873060027000", + "adapter": "tomtom_incidents", + "category": "incident.tomtom_incidents", + "time": "2026-06-27T23:39:30Z", + "expires": "2026-06-28T00:13:00Z", + "severity": 3, + "geo": { + "centroid": [ + -116.4736578559, + 43.6194590068 + ], + "bbox": null, + "regions": [ + "US-ID" + ], + "primary_region": "US-ID", + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -116.4736578559, + 43.6194590068 + ], + [ + -116.4734982644, + 43.6194590068 + ], + [ + -116.4725098704, + 43.6194576719 + ], + [ + -116.4721893464, + 43.6194563369 + ], + [ + -116.4716542457, + 43.619453667 + ], + [ + -116.4711003696, + 43.6194522714 + ], + [ + -116.4705451523, + 43.6194496015 + ], + [ + -116.4703399633, + 43.6194496015 + ], + [ + -116.4703265523, + 43.6194496015 + ], + [ + -116.4699872528, + 43.6194482665 + ], + [ + -116.4686997925, + 43.6194442616 + ], + [ + -116.4670086597, + 43.6194375262 + ] + ] + } + }, + "data": { + "description": "Stationary traffic", + "event_code": 101, + "from": "Star Rd (Cherry Ln)", + "to": "N Black Cat Rd (W Cherry Ln)", + "magnitude_of_delay": 3, + "icon_category": 6, + "length": 535.2546004508, + "delay": 298, + "road_numbers": [], + "start_time": "2026-06-27T23:39:30Z", + "end_time": "2026-06-28T00:13:00Z", + "time_validity": "present", + "state_code": "ID", + "bbox_name": "treasure_valley_ext", + "latitude": 43.6194590068, + "longitude": -116.4736578559, + "_enriched": { + "geocoder": { + "name": null, + "city": "Meridian", + "county": "Ada", + "state": "ID", + "country": "United States", + "postal_code": "83642", + "timezone": "America/Boise", + "landclass": null, + "elevation_m": 772.2109375 + } + } + } + } + }, + "subject": "central.traffic.incident.id", + "captured_epoch": 1783206527 +} \ No newline at end of file diff --git a/work/tests/fixtures/traffic/0001.json b/work/tests/fixtures/traffic/0001.json new file mode 100644 index 0000000..0e581a7 --- /dev/null +++ b/work/tests/fixtures/traffic/0001.json @@ -0,0 +1,146 @@ +{ + "envelope": { + "id": "ID:tomtom:TTI-4ee2bdba-b112-4223-894d-2b10d1a81de0-TTL14775758604035000", + "source": "central.echo6.co", + "type": "central.incident.tomtom_incidents.v1", + "time": "2026-06-27T23:39:30+00:00", + "datacontenttype": "application/json", + "centralschemaversion": "1.0", + "centralcategory": "incident.tomtom_incidents", + "centralseverity": 3, + "specversion": "1.0", + "data": { + "id": "ID:tomtom:TTI-4ee2bdba-b112-4223-894d-2b10d1a81de0-TTL14775758604035000", + "adapter": "tomtom_incidents", + "category": "incident.tomtom_incidents", + "time": "2026-06-27T23:39:30Z", + "expires": "2026-06-28T00:14:00Z", + "severity": 3, + "geo": { + "centroid": [ + -116.4606947397, + 43.6194161062 + ], + "bbox": null, + "regions": [ + "US-ID" + ], + "primary_region": "US-ID", + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -116.4606947397, + 43.6194161062 + ], + [ + -116.4611963128, + 43.6194174412 + ], + [ + -116.4612030183, + 43.6194174412 + ], + [ + -116.4632039462, + 43.619424116 + ], + [ + -116.4644149636, + 43.6194281815 + ], + [ + -116.4655924534, + 43.6194321864 + ], + [ + -116.4660832976, + 43.6194348563 + ], + [ + -116.4670086597, + 43.6194375262 + ], + [ + -116.4686997925, + 43.6194442616 + ], + [ + -116.4699872528, + 43.6194482665 + ], + [ + -116.4703265523, + 43.6194496015 + ], + [ + -116.4703399633, + 43.6194496015 + ], + [ + -116.4705451523, + 43.6194496015 + ], + [ + -116.4711003696, + 43.6194522714 + ], + [ + -116.4716542457, + 43.619453667 + ], + [ + -116.4721893464, + 43.6194563369 + ], + [ + -116.4725098704, + 43.6194576719 + ], + [ + -116.4734982644, + 43.6194590068 + ], + [ + -116.4736578559, + 43.6194590068 + ] + ] + } + }, + "data": { + "description": "Stationary traffic", + "event_code": 101, + "from": "N Black Cat Rd (W Cherry Ln)", + "to": "Star Rd (Cherry Ln)", + "magnitude_of_delay": 3, + "icon_category": 6, + "length": 1043.5197884031, + "delay": 402, + "road_numbers": [], + "start_time": "2026-06-27T23:39:30Z", + "end_time": "2026-06-28T00:14:00Z", + "time_validity": "present", + "state_code": "ID", + "bbox_name": "treasure_valley_ext", + "latitude": 43.6194161062, + "longitude": -116.4606947397, + "_enriched": { + "geocoder": { + "name": "Ten Mile Creek", + "city": null, + "county": "Ada", + "state": "Idaho", + "country": "United States", + "postal_code": "83687", + "timezone": "America/Boise", + "landclass": null, + "elevation_m": 772.3828125 + } + } + } + } + }, + "subject": "central.traffic.incident.id", + "captured_epoch": 1783206527 +} \ No newline at end of file diff --git a/work/tests/fixtures/traffic/0002.json b/work/tests/fixtures/traffic/0002.json new file mode 100644 index 0000000..42b3f8d --- /dev/null +++ b/work/tests/fixtures/traffic/0002.json @@ -0,0 +1,222 @@ +{ + "envelope": { + "id": "ID:tomtom:TTI-4ee2bdba-b112-4223-894d-2b10d1a81de0-TTR14772874384078000", + "source": "central.echo6.co", + "type": "central.incident.tomtom_incidents.v1", + "time": "2026-06-27T23:22:30+00:00", + "datacontenttype": "application/json", + "centralschemaversion": "1.0", + "centralcategory": "incident.tomtom_incidents", + "centralseverity": 4, + "specversion": "1.0", + "data": { + "id": "ID:tomtom:TTI-4ee2bdba-b112-4223-894d-2b10d1a81de0-TTR14772874384078000", + "adapter": "tomtom_incidents", + "category": "incident.tomtom_incidents", + "time": "2026-06-27T23:22:30Z", + "expires": null, + "severity": 4, + "geo": { + "centroid": [ + -116.3962975834, + 43.6954298706 + ], + "bbox": null, + "regions": [ + "US-ID" + ], + "primary_region": "US-ID", + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -116.3962975834, + 43.6954298706 + ], + [ + -116.396292219, + 43.6952904229 + ], + [ + -116.3962814902, + 43.6950289197 + ], + [ + -116.3962332104, + 43.6938313233 + ], + [ + -116.3962305282, + 43.6937736277 + ], + [ + -116.3962305282, + 43.6937561735 + ], + [ + -116.3962265049, + 43.6936730238 + ], + [ + -116.3962466215, + 43.693506724 + ], + [ + -116.3964665626, + 43.6928375202 + ], + [ + -116.3964947258, + 43.6927517026 + ], + [ + -116.3965175246, + 43.6926122486 + ], + [ + -116.3965081369, + 43.6924741277 + ], + [ + -116.3964638804, + 43.6923534004 + ], + [ + -116.3963646387, + 43.6922246728 + ], + [ + -116.3962707614, + 43.6921347936 + ], + [ + -116.3962063883, + 43.692090551 + ], + [ + -116.3961446975, + 43.6920543084 + ], + [ + -116.3960186337, + 43.6919980049 + ], + [ + -116.3957329785, + 43.69187061 + ], + [ + -116.3950034176, + 43.6915460606 + ], + [ + -116.3944455181, + 43.6912711469 + ], + [ + -116.3937977646, + 43.6909519278 + ], + [ + -116.3937883769, + 43.6909465943 + ], + [ + -116.3937709426, + 43.6909385335 + ], + [ + -116.3937159573, + 43.6909116843 + ], + [ + -116.3936207389, + 43.690864774 + ], + [ + -116.393455783, + 43.6907829537 + ], + [ + -116.3934517597, + 43.6907802869 + ], + [ + -116.3932586406, + 43.6906837387 + ], + [ + -116.3932438885, + 43.6906756779 + ], + [ + -116.3931111191, + 43.6905885238 + ], + [ + -116.3930091952, + 43.6904986422 + ], + [ + -116.3929354344, + 43.690407427 + ], + [ + -116.3929193412, + 43.6903793049 + ], + [ + -116.3928764258, + 43.6903095449 + ], + [ + -116.3928281461, + 43.6901768128 + ], + [ + -116.3928133939, + 43.6900372922 + ], + [ + -116.3927973007, + 43.6888638401 + ] + ] + } + }, + "data": { + "description": "Closed", + "event_code": 401, + "from": "West State Street / North Fisher Park Way", + "to": "West Hatchery Road", + "magnitude_of_delay": 4, + "icon_category": 8, + "length": 879.0725350843, + "delay": null, + "road_numbers": [], + "start_time": "2026-06-27T23:22:30Z", + "end_time": null, + "time_validity": "present", + "state_code": "ID", + "bbox_name": "treasure_valley_ext", + "latitude": 43.6954298706, + "longitude": -116.3962975834, + "_enriched": { + "geocoder": { + "name": "West State Street", + "city": "Eagle", + "county": "Ada", + "state": "Idaho", + "country": "United States", + "postal_code": "83616", + "timezone": "America/Boise", + "landclass": null, + "elevation_m": 773.0625 + } + } + } + } + }, + "subject": "central.traffic.incident.id", + "captured_epoch": 1783206527 +} \ No newline at end of file diff --git a/work/tests/fixtures/traffic/0003.json b/work/tests/fixtures/traffic/0003.json new file mode 100644 index 0000000..6673fb8 --- /dev/null +++ b/work/tests/fixtures/traffic/0003.json @@ -0,0 +1,234 @@ +{ + "envelope": { + "id": "ID:tomtom:TTI-4ee2bdba-b112-4223-894d-2b10d1a81de0-TTL14813270316028000", + "source": "central.echo6.co", + "type": "central.incident.tomtom_incidents.v1", + "time": "2026-06-27T23:41:05+00:00", + "datacontenttype": "application/json", + "centralschemaversion": "1.0", + "centralcategory": "incident.tomtom_incidents", + "centralseverity": 2, + "specversion": "1.0", + "data": { + "id": "ID:tomtom:TTI-4ee2bdba-b112-4223-894d-2b10d1a81de0-TTL14813270316028000", + "adapter": "tomtom_incidents", + "category": "incident.tomtom_incidents", + "time": "2026-06-27T23:41:05Z", + "expires": "2026-06-28T00:03:00Z", + "severity": 2, + "geo": { + "centroid": [ + -116.2744770144, + 43.6495547551 + ], + "bbox": null, + "regions": [ + "US-ID" + ], + "primary_region": "US-ID", + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -116.2744770144, + 43.6495547551 + ], + [ + -116.2744770144, + 43.6496539774 + ], + [ + -116.2744783555, + 43.6501716166 + ], + [ + -116.2744783555, + 43.6502226222 + ], + [ + -116.2744796966, + 43.650296371 + ], + [ + -116.2744796966, + 43.6503017081 + ], + [ + -116.2744823788, + 43.6506369727 + ], + [ + -116.2745024954, + 43.6507308561 + ], + [ + -116.2745024954, + 43.6507362538 + ], + [ + -116.2745172475, + 43.6507630603 + ], + [ + -116.274549434, + 43.6508194024 + ], + [ + -116.2745883261, + 43.6508891477 + ], + [ + -116.2746741567, + 43.650989702 + ], + [ + -116.2747814451, + 43.6510755187 + ], + [ + -116.2749262844, + 43.6511519956 + ], + [ + -116.2750349139, + 43.6511922051 + ], + [ + -116.2750657593, + 43.6512042739 + ], + [ + -116.2751596366, + 43.6512257432 + ], + [ + -116.2751757298, + 43.651229746 + ], + [ + -116.2752629016, + 43.6512364779 + ], + [ + -116.2753366624, + 43.6512378121 + ], + [ + -116.2755136882, + 43.6512378121 + ], + [ + -116.2756705974, + 43.6512378121 + ], + [ + -116.2758409177, + 43.6512391464 + ], + [ + -116.2760166024, + 43.6512391464 + ], + [ + -116.2760313545, + 43.6512391464 + ], + [ + -116.2761118208, + 43.6512391464 + ], + [ + -116.276589254, + 43.6512404806 + ], + [ + -116.2770412062, + 43.6512418149 + ], + [ + -116.2771404479, + 43.6512418149 + ], + [ + -116.2772343252, + 43.6512418149 + ], + [ + -116.2773670946, + 43.6512418149 + ], + [ + -116.2783179377, + 43.651244544 + ], + [ + -116.2786196862, + 43.6512458783 + ], + [ + -116.2787028347, + 43.6512458783 + ], + [ + -116.2788570617, + 43.6512458783 + ], + [ + -116.27919502, + 43.6512472125 + ], + [ + -116.279220501, + 43.6512472125 + ], + [ + -116.2794659232, + 43.6512472125 + ], + [ + -116.2795329784, + 43.6512472125 + ], + [ + -116.2797341441, + 43.6512485468 + ] + ] + } + }, + "data": { + "description": "Queuing traffic", + "event_code": 108, + "from": "North Alworth Street / Fairpark Lane", + "to": "North Glenwood Street", + "magnitude_of_delay": 2, + "icon_category": 6, + "length": 579.0122767146, + "delay": 145, + "road_numbers": [], + "start_time": "2026-06-27T23:41:05Z", + "end_time": "2026-06-28T00:03:00Z", + "time_validity": "present", + "state_code": "ID", + "bbox_name": "treasure_valley_ext", + "latitude": 43.6495547551, + "longitude": -116.2744770144, + "_enriched": { + "geocoder": { + "name": "North Kent Lane", + "city": null, + "county": "Ada", + "state": "Idaho", + "country": "United States", + "postal_code": "83704", + "timezone": "America/Boise", + "landclass": "Expo Idaho", + "elevation_m": 799.53125 + } + } + } + } + }, + "subject": "central.traffic.incident.id", + "captured_epoch": 1783206527 +} \ No newline at end of file diff --git a/work/tests/fixtures/traffic/0004.json b/work/tests/fixtures/traffic/0004.json new file mode 100644 index 0000000..4d3d8a8 --- /dev/null +++ b/work/tests/fixtures/traffic/0004.json @@ -0,0 +1,162 @@ +{ + "envelope": { + "id": "ID:tomtom:TTI-4ee2bdba-b112-4223-894d-2b10d1a81de0-TTL15462486252051000", + "source": "central.echo6.co", + "type": "central.incident.tomtom_incidents.v1", + "time": "2026-06-27T23:42:30+00:00", + "datacontenttype": "application/json", + "centralschemaversion": "1.0", + "centralcategory": "incident.tomtom_incidents", + "centralseverity": 3, + "specversion": "1.0", + "data": { + "id": "ID:tomtom:TTI-4ee2bdba-b112-4223-894d-2b10d1a81de0-TTL15462486252051000", + "adapter": "tomtom_incidents", + "category": "incident.tomtom_incidents", + "time": "2026-06-27T23:42:30Z", + "expires": "2026-06-28T00:03:00Z", + "severity": 3, + "geo": { + "centroid": [ + -114.5080640108, + 42.6950973548 + ], + "bbox": null, + "regions": [ + "US-ID" + ], + "primary_region": "US-ID", + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -114.5080640108, + 42.6950973548 + ], + [ + -114.509093979, + 42.6951027144 + ], + [ + -114.5093863398, + 42.6951040697 + ], + [ + -114.5096572429, + 42.6951067187 + ], + [ + -114.5103063375, + 42.6951121399 + ], + [ + -114.5105262786, + 42.695108074 + ], + [ + -114.5109473855, + 42.6951013591 + ], + [ + -114.5109970063, + 42.6951013591 + ], + [ + -114.5110332161, + 42.6951000654 + ], + [ + -114.5110882014, + 42.6950987101 + ], + [ + -114.5113027782, + 42.6950947058 + ], + [ + -114.5113591045, + 42.6950933505 + ], + [ + -114.5113872677, + 42.6950919952 + ], + [ + -114.511454323, + 42.6950906399 + ], + [ + -114.5114757806, + 42.6950906399 + ], + [ + -114.5121047586, + 42.6950678461 + ], + [ + -114.5128825993, + 42.6950718504 + ], + [ + -114.5143444032, + 42.6950692014 + ], + [ + -114.5143604964, + 42.6950692014 + ], + [ + -114.5147856266, + 42.6950678461 + ], + [ + -114.5151799113, + 42.6950678461 + ], + [ + -114.5158625335, + 42.6950664908 + ], + [ + -114.5158960611, + 42.6950664908 + ] + ] + } + }, + "data": { + "description": "Stationary traffic", + "event_code": 101, + "from": "South Garfield Street", + "to": "South Lincoln Avenue", + "magnitude_of_delay": 3, + "icon_category": 6, + "length": 640.1840094512, + "delay": 180, + "road_numbers": [], + "start_time": "2026-06-27T23:42:30Z", + "end_time": "2026-06-28T00:03:00Z", + "time_validity": "present", + "state_code": "ID", + "bbox_name": "magic_valley_burley", + "latitude": 42.6950973548, + "longitude": -114.5080640108, + "_enriched": { + "geocoder": { + "name": "South Garfield Street", + "city": "Jerome", + "county": "Jerome", + "state": "Idaho", + "country": "United States", + "postal_code": "83338", + "timezone": "America/Boise", + "landclass": null, + "elevation_m": 1147.796875 + } + } + } + } + }, + "subject": "central.traffic.incident.id", + "captured_epoch": 1783206527 +} \ No newline at end of file diff --git a/work/tests/fixtures/traffic/0005.json b/work/tests/fixtures/traffic/0005.json new file mode 100644 index 0000000..5dcf8fd --- /dev/null +++ b/work/tests/fixtures/traffic/0005.json @@ -0,0 +1,102 @@ +{ + "envelope": { + "id": "ID:tomtom:TTI-4ee2bdba-b112-4223-894d-2b10d1a81de0-TTL14570896572030000", + "source": "central.echo6.co", + "type": "central.incident.tomtom_incidents.v1", + "time": "2026-06-28T00:42:30+00:00", + "datacontenttype": "application/json", + "centralschemaversion": "1.0", + "centralcategory": "incident.tomtom_incidents", + "centralseverity": 3, + "specversion": "1.0", + "data": { + "id": "ID:tomtom:TTI-4ee2bdba-b112-4223-894d-2b10d1a81de0-TTL14570896572030000", + "adapter": "tomtom_incidents", + "category": "incident.tomtom_incidents", + "time": "2026-06-28T00:42:30Z", + "expires": "2026-06-28T01:03:00Z", + "severity": 3, + "geo": { + "centroid": [ + -116.9250763154, + 44.0236169542 + ], + "bbox": null, + "regions": [ + "US-ID" + ], + "primary_region": "US-ID", + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -116.9250763154, + 44.0236169542 + ], + [ + -116.9255282676, + 44.0236156282 + ], + [ + -116.9256543314, + 44.0236156282 + ], + [ + -116.925893048, + 44.023614242 + ], + [ + -116.9260298407, + 44.023614242 + ], + [ + -116.9263409769, + 44.0236129161 + ], + [ + -116.9284626043, + 44.0235941117 + ], + [ + -116.9286315834, + 44.0235927858 + ] + ] + } + }, + "data": { + "description": "Stationary traffic", + "event_code": 101, + "from": "North Whitley Drive", + "to": "North Arizona Avenue", + "magnitude_of_delay": 3, + "icon_category": 6, + "length": 284.2768770846, + "delay": 154, + "road_numbers": [], + "start_time": "2026-06-28T00:42:30Z", + "end_time": "2026-06-28T01:03:00Z", + "time_validity": "present", + "state_code": "ID", + "bbox_name": "treasure_valley_ext", + "latitude": 44.0236169542, + "longitude": -116.9250763154, + "_enriched": { + "geocoder": { + "name": "Ogawa's Sushi", + "city": "Fruitland", + "county": "Payette", + "state": "Idaho", + "country": "United States", + "postal_code": "83619", + "timezone": "America/Boise", + "landclass": null, + "elevation_m": 669.35546875 + } + } + } + } + }, + "subject": "central.traffic.incident.id", + "captured_epoch": 1783206527 +} \ No newline at end of file diff --git a/work/tests/fixtures/traffic/0006.json b/work/tests/fixtures/traffic/0006.json new file mode 100644 index 0000000..c9a7217 --- /dev/null +++ b/work/tests/fixtures/traffic/0006.json @@ -0,0 +1,88 @@ +{ + "envelope": { + "id": "ID:tomtom:TTI-4ee2bdba-b112-4223-894d-2b10d1a81de0-TTR14570896680001001", + "source": "central.echo6.co", + "type": "central.incident.tomtom_incidents.v1", + "time": "2026-06-27T19:16:06+00:00", + "datacontenttype": "application/json", + "centralschemaversion": "1.0", + "centralcategory": "incident.tomtom_incidents", + "centralseverity": 1, + "specversion": "1.0", + "data": { + "id": "ID:tomtom:TTI-4ee2bdba-b112-4223-894d-2b10d1a81de0-TTR14570896680001001", + "adapter": "tomtom_incidents", + "category": "incident.tomtom_incidents", + "time": "2026-06-27T19:16:06Z", + "expires": "2026-06-28T05:59:00Z", + "severity": 1, + "geo": { + "centroid": [ + -116.9235541618, + 44.0267644902 + ], + "bbox": null, + "regions": [ + "US-ID" + ], + "primary_region": "US-ID", + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -116.9235541618, + 44.0267644902 + ], + [ + -116.9235541618, + 44.0267189282 + ], + [ + -116.9235555029, + 44.0265338473 + ], + [ + -116.9235555029, + 44.0263863125 + ] + ] + } + }, + "data": { + "description": "Roadworks", + "event_code": 701, + "from": "US-95 Bus (Payette) (US-95)", + "to": "N 16th St (US-95)", + "magnitude_of_delay": 0, + "icon_category": 9, + "length": 42.0517138378, + "delay": null, + "road_numbers": [ + "US-95" + ], + "start_time": "2026-06-27T19:16:06Z", + "end_time": "2026-06-28T05:59:00Z", + "time_validity": "present", + "state_code": "ID", + "bbox_name": "treasure_valley_ext", + "latitude": 44.0267644902, + "longitude": -116.9235541618, + "_enriched": { + "geocoder": { + "name": "Idaho Kids Dentistry and Orthodontics", + "city": "Fruitland", + "county": "Payette", + "state": "Idaho", + "country": "United States", + "postal_code": "83619", + "timezone": "America/Boise", + "landclass": null, + "elevation_m": 668.46484375 + } + } + } + } + }, + "subject": "central.traffic.incident.id", + "captured_epoch": 1783206527 +} \ No newline at end of file diff --git a/work/tests/fixtures/traffic/0007.json b/work/tests/fixtures/traffic/0007.json new file mode 100644 index 0000000..60c3411 --- /dev/null +++ b/work/tests/fixtures/traffic/0007.json @@ -0,0 +1,114 @@ +{ + "envelope": { + "id": "ID:tomtom:TTI-4ee2bdba-b112-4223-894d-2b10d1a81de0-TTL14752673928028000", + "source": "central.echo6.co", + "type": "central.incident.tomtom_incidents.v1", + "time": "2026-06-28T00:40:00+00:00", + "datacontenttype": "application/json", + "centralschemaversion": "1.0", + "centralcategory": "incident.tomtom_incidents", + "centralseverity": 3, + "specversion": "1.0", + "data": { + "id": "ID:tomtom:TTI-4ee2bdba-b112-4223-894d-2b10d1a81de0-TTL14752673928028000", + "adapter": "tomtom_incidents", + "category": "incident.tomtom_incidents", + "time": "2026-06-28T00:40:00Z", + "expires": "2026-06-28T01:03:00Z", + "severity": 3, + "geo": { + "centroid": [ + -116.6187076961, + 43.5655211212 + ], + "bbox": null, + "regions": [ + "US-ID" + ], + "primary_region": "US-ID", + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -116.6187076961, + 43.5655211212 + ], + [ + -116.6188525354, + 43.5656525497 + ], + [ + -116.619116733, + 43.5660320755 + ], + [ + -116.6191757416, + 43.5661836057 + ], + [ + -116.6191945171, + 43.566426357 + ], + [ + -116.6191462373, + 43.5666704434 + ], + [ + -116.6190389489, + 43.566889021 + ], + [ + -116.6188967919, + 43.5671813879 + ], + [ + -116.6188270544, + 43.5673610951 + ], + [ + -116.6188149845, + 43.5675757227 + ], + [ + -116.6188123023, + 43.5677325934 + ] + ] + } + }, + "data": { + "description": "Stationary traffic", + "event_code": 101, + "from": "S Pine River Way", + "to": "Roosevelt Avenue", + "magnitude_of_delay": 3, + "icon_category": 6, + "length": 260.2802031929, + "delay": 249, + "road_numbers": [], + "start_time": "2026-06-28T00:40:00Z", + "end_time": "2026-06-28T01:03:00Z", + "time_validity": "present", + "state_code": "ID", + "bbox_name": "treasure_valley_ext", + "latitude": 43.5655211212, + "longitude": -116.6187076961, + "_enriched": { + "geocoder": { + "name": "Copper River Basin", + "city": "Nampa", + "county": "Canyon", + "state": "Idaho", + "country": "United States", + "postal_code": null, + "timezone": "America/Boise", + "landclass": null, + "elevation_m": 756.8046875 + } + } + } + } + }, + "subject": "central.traffic.incident.id", + "captured_epoch": 1783206527 +} \ No newline at end of file diff --git a/work/tests/fixtures/traffic/0008.json b/work/tests/fixtures/traffic/0008.json new file mode 100644 index 0000000..47a783f --- /dev/null +++ b/work/tests/fixtures/traffic/0008.json @@ -0,0 +1,82 @@ +{ + "envelope": { + "id": "ID:tomtom:TTI-4ee2bdba-b112-4223-894d-2b10d1a81de0-TTL14784413868015000", + "source": "central.echo6.co", + "type": "central.incident.tomtom_incidents.v1", + "time": "2026-06-28T00:41:30+00:00", + "datacontenttype": "application/json", + "centralschemaversion": "1.0", + "centralcategory": "incident.tomtom_incidents", + "centralseverity": 3, + "specversion": "1.0", + "data": { + "id": "ID:tomtom:TTI-4ee2bdba-b112-4223-894d-2b10d1a81de0-TTL14784413868015000", + "adapter": "tomtom_incidents", + "category": "incident.tomtom_incidents", + "time": "2026-06-28T00:41:30Z", + "expires": "2026-06-28T01:03:00Z", + "severity": 3, + "geo": { + "centroid": [ + -116.5129307603, + 43.5403110514 + ], + "bbox": null, + "regions": [ + "US-ID" + ], + "primary_region": "US-ID", + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -116.5129307603, + 43.5403110514 + ], + [ + -116.5135221874, + 43.5403110514 + ], + [ + -116.5137689506, + 43.5403123881 + ] + ] + } + }, + "data": { + "description": "Stationary traffic", + "event_code": 101, + "from": "South Happy Valley Road", + "to": "East Brooklyn Drive", + "magnitude_of_delay": 3, + "icon_category": 6, + "length": 67.5621131966, + "delay": 104, + "road_numbers": [], + "start_time": "2026-06-28T00:41:30Z", + "end_time": "2026-06-28T01:03:00Z", + "time_validity": "present", + "state_code": "ID", + "bbox_name": "treasure_valley_ext", + "latitude": 43.5403110514, + "longitude": -116.5129307603, + "_enriched": { + "geocoder": { + "name": "East Walcott Street", + "city": "Nampa", + "county": "Canyon", + "state": "Idaho", + "country": "United States", + "postal_code": null, + "timezone": "America/Boise", + "landclass": null, + "elevation_m": 776.4609375 + } + } + } + } + }, + "subject": "central.traffic.incident.id", + "captured_epoch": 1783206527 +} \ No newline at end of file diff --git a/work/tests/fixtures/traffic/0009.json b/work/tests/fixtures/traffic/0009.json new file mode 100644 index 0000000..10996ff --- /dev/null +++ b/work/tests/fixtures/traffic/0009.json @@ -0,0 +1,110 @@ +{ + "envelope": { + "id": "ID:tomtom:TTI-4ee2bdba-b112-4223-894d-2b10d1a81de0-TTL14772873060051000", + "source": "central.echo6.co", + "type": "central.incident.tomtom_incidents.v1", + "time": "2026-06-28T00:40:00+00:00", + "datacontenttype": "application/json", + "centralschemaversion": "1.0", + "centralcategory": "incident.tomtom_incidents", + "centralseverity": 3, + "specversion": "1.0", + "data": { + "id": "ID:tomtom:TTI-4ee2bdba-b112-4223-894d-2b10d1a81de0-TTL14772873060051000", + "adapter": "tomtom_incidents", + "category": "incident.tomtom_incidents", + "time": "2026-06-28T00:40:00Z", + "expires": "2026-06-28T01:03:00Z", + "severity": 3, + "geo": { + "centroid": [ + -116.4725098704, + 43.6194576719 + ], + "bbox": null, + "regions": [ + "US-ID" + ], + "primary_region": "US-ID", + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -116.4725098704, + 43.6194576719 + ], + [ + -116.4721893464, + 43.6194563369 + ], + [ + -116.4716542457, + 43.619453667 + ], + [ + -116.4711003696, + 43.6194522714 + ], + [ + -116.4705451523, + 43.6194496015 + ], + [ + -116.4703399633, + 43.6194496015 + ], + [ + -116.4703265523, + 43.6194496015 + ], + [ + -116.4699872528, + 43.6194482665 + ], + [ + -116.4686997925, + 43.6194442616 + ], + [ + -116.4670086597, + 43.6194375262 + ] + ] + } + }, + "data": { + "description": "Stationary traffic", + "event_code": 101, + "from": "Star Rd (Cherry Ln)", + "to": "N Black Cat Rd (W Cherry Ln)", + "magnitude_of_delay": 3, + "icon_category": 6, + "length": 442.8437103658, + "delay": 126, + "road_numbers": [], + "start_time": "2026-06-28T00:40:00Z", + "end_time": "2026-06-28T01:03:00Z", + "time_validity": "present", + "state_code": "ID", + "bbox_name": "treasure_valley_ext", + "latitude": 43.6194576719, + "longitude": -116.4725098704, + "_enriched": { + "geocoder": { + "name": null, + "city": "Meridian", + "county": "Ada", + "state": "ID", + "country": "United States", + "postal_code": "83642", + "timezone": "America/Boise", + "landclass": null, + "elevation_m": 772.39453125 + } + } + } + } + }, + "subject": "central.traffic.incident.id", + "captured_epoch": 1783206527 +} \ No newline at end of file diff --git a/work/tests/fixtures/traffic/0010.json b/work/tests/fixtures/traffic/0010.json new file mode 100644 index 0000000..7b5639f --- /dev/null +++ b/work/tests/fixtures/traffic/0010.json @@ -0,0 +1,102 @@ +{ + "envelope": { + "id": "ID:tomtom:TTI-4ee2bdba-b112-4223-894d-2b10d1a81de0-TTL14775758604011000", + "source": "central.echo6.co", + "type": "central.incident.tomtom_incidents.v1", + "time": "2026-06-28T00:40:30+00:00", + "datacontenttype": "application/json", + "centralschemaversion": "1.0", + "centralcategory": "incident.tomtom_incidents", + "centralseverity": 3, + "specversion": "1.0", + "data": { + "id": "ID:tomtom:TTI-4ee2bdba-b112-4223-894d-2b10d1a81de0-TTL14775758604011000", + "adapter": "tomtom_incidents", + "category": "incident.tomtom_incidents", + "time": "2026-06-28T00:40:30Z", + "expires": "2026-06-28T01:03:00Z", + "severity": 3, + "geo": { + "centroid": [ + -116.4660832976, + 43.6194348563 + ], + "bbox": null, + "regions": [ + "US-ID" + ], + "primary_region": "US-ID", + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -116.4660832976, + 43.6194348563 + ], + [ + -116.4670086597, + 43.6194375262 + ], + [ + -116.4686997925, + 43.6194442616 + ], + [ + -116.4699872528, + 43.6194482665 + ], + [ + -116.4703265523, + 43.6194496015 + ], + [ + -116.4703399633, + 43.6194496015 + ], + [ + -116.4705451523, + 43.6194496015 + ], + [ + -116.4711003696, + 43.6194522714 + ] + ] + } + }, + "data": { + "description": "Stationary traffic", + "event_code": 101, + "from": "N Black Cat Rd (W Cherry Ln)", + "to": "Star Rd (Cherry Ln)", + "magnitude_of_delay": 3, + "icon_category": 6, + "length": 403.8703109877, + "delay": 102, + "road_numbers": [], + "start_time": "2026-06-28T00:40:30Z", + "end_time": "2026-06-28T01:03:00Z", + "time_validity": "present", + "state_code": "ID", + "bbox_name": "treasure_valley_ext", + "latitude": 43.6194348563, + "longitude": -116.4660832976, + "_enriched": { + "geocoder": { + "name": null, + "city": "Meridian", + "county": "Ada", + "state": "ID", + "country": "United States", + "postal_code": "83642", + "timezone": "America/Boise", + "landclass": null, + "elevation_m": 773.28125 + } + } + } + } + }, + "subject": "central.traffic.incident.id", + "captured_epoch": 1783206527 +} \ No newline at end of file diff --git a/work/tests/fixtures/traffic/0011.json b/work/tests/fixtures/traffic/0011.json new file mode 100644 index 0000000..5702877 --- /dev/null +++ b/work/tests/fixtures/traffic/0011.json @@ -0,0 +1,84 @@ +{ + "envelope": { + "id": "ID:tomtom:TTI-4ee2bdba-b112-4223-894d-2b10d1a81de0-TTR14801727888001000", + "source": "central.echo6.co", + "type": "central.incident.tomtom_incidents.v1", + "time": "2026-06-28T00:17:30+00:00", + "datacontenttype": "application/json", + "centralschemaversion": "1.0", + "centralcategory": "incident.tomtom_incidents", + "centralseverity": 1, + "specversion": "1.0", + "data": { + "id": "ID:tomtom:TTI-4ee2bdba-b112-4223-894d-2b10d1a81de0-TTR14801727888001000", + "adapter": "tomtom_incidents", + "category": "incident.tomtom_incidents", + "time": "2026-06-28T00:17:30Z", + "expires": "2026-06-28T05:59:00Z", + "severity": 1, + "geo": { + "centroid": [ + -116.3543961142, + 43.6200531193 + ], + "bbox": null, + "regions": [ + "US-ID" + ], + "primary_region": "US-ID", + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -116.3543961142, + 43.6200531193 + ], + [ + -116.3543961142, + 43.6201000243 + ], + [ + -116.354393432, + 43.6203441358 + ] + ] + } + }, + "data": { + "description": "Lane closed", + "event_code": 500, + "from": "E Fairview Ave (ID-55)", + "to": "E Ustick Rd (ID-55)", + "magnitude_of_delay": 0, + "icon_category": 7, + "length": 32.3604168487, + "delay": null, + "road_numbers": [ + "ID-55" + ], + "start_time": "2026-06-28T00:17:30Z", + "end_time": "2026-06-28T05:59:00Z", + "time_validity": "present", + "state_code": "ID", + "bbox_name": "treasure_valley_ext", + "latitude": 43.6200531193, + "longitude": -116.3543961142, + "_enriched": { + "geocoder": { + "name": "North Eagle Road", + "city": "Meridian", + "county": "Ada", + "state": "Idaho", + "country": "United States", + "postal_code": "83713", + "timezone": "America/Boise", + "landclass": null, + "elevation_m": 800.25 + } + } + } + } + }, + "subject": "central.traffic.incident.id", + "captured_epoch": 1783206527 +} \ No newline at end of file diff --git a/work/tests/fixtures/traffic/0012.json b/work/tests/fixtures/traffic/0012.json new file mode 100644 index 0000000..de798c8 --- /dev/null +++ b/work/tests/fixtures/traffic/0012.json @@ -0,0 +1,86 @@ +{ + "envelope": { + "id": "ID:tomtom:TTI-4ee2bdba-b112-4223-894d-2b10d1a81de0-TTL14793072084015000", + "source": "central.echo6.co", + "type": "central.incident.tomtom_incidents.v1", + "time": "2026-06-28T00:32:00+00:00", + "datacontenttype": "application/json", + "centralschemaversion": "1.0", + "centralcategory": "incident.tomtom_incidents", + "centralseverity": 3, + "specversion": "1.0", + "data": { + "id": "ID:tomtom:TTI-4ee2bdba-b112-4223-894d-2b10d1a81de0-TTL14793072084015000", + "adapter": "tomtom_incidents", + "category": "incident.tomtom_incidents", + "time": "2026-06-28T00:32:00Z", + "expires": "2026-06-28T01:03:00Z", + "severity": 3, + "geo": { + "centroid": [ + -116.3543652688, + 43.6613631783 + ], + "bbox": null, + "regions": [ + "US-ID" + ], + "primary_region": "US-ID", + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -116.3543652688, + 43.6613631783 + ], + [ + -116.3541989718, + 43.6613577816 + ], + [ + -116.3541520332, + 43.6613564476 + ], + [ + -116.3532427643, + 43.6613296458 + ] + ] + } + }, + "data": { + "description": "Stationary traffic", + "event_code": 101, + "from": "North Eagle Road", + "to": "North Eagle Road", + "magnitude_of_delay": 3, + "icon_category": 6, + "length": 90.373657946, + "delay": 67, + "road_numbers": [], + "start_time": "2026-06-28T00:32:00Z", + "end_time": "2026-06-28T01:03:00Z", + "time_validity": "present", + "state_code": "ID", + "bbox_name": "treasure_valley_ext", + "latitude": 43.6613631783, + "longitude": -116.3543652688, + "_enriched": { + "geocoder": { + "name": "North Eagle Road", + "city": "Boise", + "county": "Ada", + "state": "Idaho", + "country": "United States", + "postal_code": "83713", + "timezone": "America/Boise", + "landclass": null, + "elevation_m": 798.0 + } + } + } + } + }, + "subject": "central.traffic.incident.id", + "captured_epoch": 1783206527 +} \ No newline at end of file diff --git a/work/tests/fixtures/traffic/0013.json b/work/tests/fixtures/traffic/0013.json new file mode 100644 index 0000000..8d04c41 --- /dev/null +++ b/work/tests/fixtures/traffic/0013.json @@ -0,0 +1,94 @@ +{ + "envelope": { + "id": "ID:tomtom:TTI-4ee2bdba-b112-4223-894d-2b10d1a81de0-TTL14790187848039000", + "source": "central.echo6.co", + "type": "central.incident.tomtom_incidents.v1", + "time": "2026-06-28T00:33:31+00:00", + "datacontenttype": "application/json", + "centralschemaversion": "1.0", + "centralcategory": "incident.tomtom_incidents", + "centralseverity": 3, + "specversion": "1.0", + "data": { + "id": "ID:tomtom:TTI-4ee2bdba-b112-4223-894d-2b10d1a81de0-TTL14790187848039000", + "adapter": "tomtom_incidents", + "category": "incident.tomtom_incidents", + "time": "2026-06-28T00:33:31Z", + "expires": "2026-06-28T01:03:00Z", + "severity": 3, + "geo": { + "centroid": [ + -116.2824418341, + 43.7338485397 + ], + "bbox": null, + "regions": [ + "US-ID" + ], + "primary_region": "US-ID", + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -116.2824418341, + 43.7338485397 + ], + [ + -116.2850891744, + 43.7348878727 + ], + [ + -116.2855612431, + 43.7350219593 + ], + [ + -116.2859944199, + 43.7351172248 + ], + [ + -116.286009172, + 43.7351198896 + ], + [ + -116.2865804826, + 43.7351574386 + ] + ] + } + }, + "data": { + "description": "Stationary traffic", + "event_code": 101, + "from": "North Tower Creek Avenue / West Cardinal Drive", + "to": "North Power Way", + "magnitude_of_delay": 3, + "icon_category": 6, + "length": 366.5118565356, + "delay": 227, + "road_numbers": [], + "start_time": "2026-06-28T00:33:31Z", + "end_time": "2026-06-28T01:03:00Z", + "time_validity": "present", + "state_code": "ID", + "bbox_name": "treasure_valley_ext", + "latitude": 43.7338485397, + "longitude": -116.2824418341, + "_enriched": { + "geocoder": { + "name": null, + "city": "Boise", + "county": "Ada", + "state": "ID", + "country": "United States", + "postal_code": "83714", + "timezone": "America/Boise", + "landclass": null, + "elevation_m": 844.75 + } + } + } + } + }, + "subject": "central.traffic.incident.id", + "captured_epoch": 1783206527 +} \ No newline at end of file diff --git a/work/tests/fixtures/traffic/0014.json b/work/tests/fixtures/traffic/0014.json new file mode 100644 index 0000000..0daff39 --- /dev/null +++ b/work/tests/fixtures/traffic/0014.json @@ -0,0 +1,86 @@ +{ + "envelope": { + "id": "ID:tomtom:TTI-4ee2bdba-b112-4223-894d-2b10d1a81de0-TTL14813270244014001", + "source": "central.echo6.co", + "type": "central.incident.tomtom_incidents.v1", + "time": "2026-06-28T00:43:00+00:00", + "datacontenttype": "application/json", + "centralschemaversion": "1.0", + "centralcategory": "incident.tomtom_incidents", + "centralseverity": 3, + "specversion": "1.0", + "data": { + "id": "ID:tomtom:TTI-4ee2bdba-b112-4223-894d-2b10d1a81de0-TTL14813270244014001", + "adapter": "tomtom_incidents", + "category": "incident.tomtom_incidents", + "time": "2026-06-28T00:43:00Z", + "expires": "2026-06-28T01:03:00Z", + "severity": 3, + "geo": { + "centroid": [ + -116.2753353213, + 43.648436249 + ], + "bbox": null, + "regions": [ + "US-ID" + ], + "primary_region": "US-ID", + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -116.2753353213, + 43.648436249 + ], + [ + -116.2752870415, + 43.6484228453 + ], + [ + -116.2751703654, + 43.6484121101 + ], + [ + -116.2746915911, + 43.6484161131 + ] + ] + } + }, + "data": { + "description": "Stationary traffic", + "event_code": 101, + "from": "Lorimer Lane", + "to": "Kent Lane / North Alworth Street", + "magnitude_of_delay": 3, + "icon_category": 6, + "length": 52.1484204338, + "delay": 68, + "road_numbers": [], + "start_time": "2026-06-28T00:43:00Z", + "end_time": "2026-06-28T01:03:00Z", + "time_validity": "present", + "state_code": "ID", + "bbox_name": "treasure_valley_ext", + "latitude": 43.648436249, + "longitude": -116.2753353213, + "_enriched": { + "geocoder": { + "name": "North Kent Lane", + "city": null, + "county": "Ada", + "state": "Idaho", + "country": "United States", + "postal_code": "83704", + "timezone": "America/Boise", + "landclass": "Expo Idaho", + "elevation_m": 801.02734375 + } + } + } + } + }, + "subject": "central.traffic.incident.id", + "captured_epoch": 1783206527 +} \ No newline at end of file diff --git a/work/tests/fixtures/traffic/0015.json b/work/tests/fixtures/traffic/0015.json new file mode 100644 index 0000000..20ce343 --- /dev/null +++ b/work/tests/fixtures/traffic/0015.json @@ -0,0 +1,82 @@ +{ + "envelope": { + "id": "ID:tomtom:TTI-4ee2bdba-b112-4223-894d-2b10d1a81de0-TTL14917143868025000", + "source": "central.echo6.co", + "type": "central.incident.tomtom_incidents.v1", + "time": "2026-06-28T00:42:00+00:00", + "datacontenttype": "application/json", + "centralschemaversion": "1.0", + "centralcategory": "incident.tomtom_incidents", + "centralseverity": 1, + "specversion": "1.0", + "data": { + "id": "ID:tomtom:TTI-4ee2bdba-b112-4223-894d-2b10d1a81de0-TTL14917143868025000", + "adapter": "tomtom_incidents", + "category": "incident.tomtom_incidents", + "time": "2026-06-28T00:42:00Z", + "expires": "2026-06-28T01:03:00Z", + "severity": 1, + "geo": { + "centroid": [ + -116.0550884102, + 43.4310579885 + ], + "bbox": null, + "regions": [ + "US-ID" + ], + "primary_region": "US-ID", + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -116.0550884102, + 43.4310579885 + ], + [ + -116.0561411772, + 43.4317097154 + ], + [ + -116.0570839737, + 43.432293141 + ] + ] + } + }, + "data": { + "description": "Slow traffic", + "event_code": 115, + "from": "I-84", + "to": "I-84", + "magnitude_of_delay": 1, + "icon_category": 6, + "length": 211.7289391155, + "delay": 95, + "road_numbers": [], + "start_time": "2026-06-28T00:42:00Z", + "end_time": "2026-06-28T01:03:00Z", + "time_validity": "present", + "state_code": "ID", + "bbox_name": "treasure_valley_ext", + "latitude": 43.4310579885, + "longitude": -116.0550884102, + "_enriched": { + "geocoder": { + "name": "Westbound Port of Entry Scale lane", + "city": null, + "county": "Ada", + "state": "Idaho", + "country": "United States", + "postal_code": null, + "timezone": "America/Boise", + "landclass": null, + "elevation_m": 1018.71875 + } + } + } + } + }, + "subject": "central.traffic.incident.id", + "captured_epoch": 1783206527 +} \ No newline at end of file diff --git a/work/tests/fixtures/traffic/0016.json b/work/tests/fixtures/traffic/0016.json new file mode 100644 index 0000000..7968610 --- /dev/null +++ b/work/tests/fixtures/traffic/0016.json @@ -0,0 +1,84 @@ +{ + "envelope": { + "id": "ID:tomtom:TTI-4ee2bdba-b112-4223-894d-2b10d1a81de0-TTR15355727568011000", + "source": "central.echo6.co", + "type": "central.incident.tomtom_incidents.v1", + "time": "2026-06-26T19:54:44+00:00", + "datacontenttype": "application/json", + "centralschemaversion": "1.0", + "centralcategory": "incident.tomtom_incidents", + "centralseverity": 4, + "specversion": "1.0", + "data": { + "id": "ID:tomtom:TTI-4ee2bdba-b112-4223-894d-2b10d1a81de0-TTR15355727568011000", + "adapter": "tomtom_incidents", + "category": "incident.tomtom_incidents", + "time": "2026-06-26T19:54:44Z", + "expires": null, + "severity": 4, + "geo": { + "centroid": [ + -114.7129096777, + 42.943797166 + ], + "bbox": null, + "regions": [ + "US-ID" + ], + "primary_region": "US-ID", + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -114.7129096777, + 42.943797166 + ], + [ + -114.7129096777, + 42.9438695677 + ], + [ + -114.7129083366, + 42.9444582885 + ] + ] + } + }, + "data": { + "description": "Closed", + "event_code": 401, + "from": "4th Avenue East / 4th Avenue West", + "to": "North Main Street / 1st Avenue East", + "magnitude_of_delay": 4, + "icon_category": 8, + "length": 73.5135560313, + "delay": null, + "road_numbers": [ + "ID-46" + ], + "start_time": "2026-06-26T19:54:44Z", + "end_time": null, + "time_validity": "present", + "state_code": "ID", + "bbox_name": "magic_valley_burley", + "latitude": 42.943797166, + "longitude": -114.7129096777, + "_enriched": { + "geocoder": { + "name": "Main Street", + "city": "Gooding", + "county": "Gooding", + "state": "Idaho", + "country": "United States", + "postal_code": "83330", + "timezone": "America/Boise", + "landclass": null, + "elevation_m": 1089.28515625 + } + } + } + } + }, + "subject": "central.traffic.incident.id", + "captured_epoch": 1783206527 +} \ No newline at end of file diff --git a/work/tests/fixtures/traffic/0017.json b/work/tests/fixtures/traffic/0017.json new file mode 100644 index 0000000..925e4bb --- /dev/null +++ b/work/tests/fixtures/traffic/0017.json @@ -0,0 +1,90 @@ +{ + "envelope": { + "id": "ID:tomtom:TTI-4ee2bdba-b112-4223-894d-2b10d1a81de0-TTL15517307856023000", + "source": "central.echo6.co", + "type": "central.incident.tomtom_incidents.v1", + "time": "2026-06-28T00:42:00+00:00", + "datacontenttype": "application/json", + "centralschemaversion": "1.0", + "centralcategory": "incident.tomtom_incidents", + "centralseverity": 3, + "specversion": "1.0", + "data": { + "id": "ID:tomtom:TTI-4ee2bdba-b112-4223-894d-2b10d1a81de0-TTL15517307856023000", + "adapter": "tomtom_incidents", + "category": "incident.tomtom_incidents", + "time": "2026-06-28T00:42:00Z", + "expires": "2026-06-28T01:04:00Z", + "severity": 3, + "geo": { + "centroid": [ + -114.4255874246, + 42.55346463 + ], + "bbox": null, + "regions": [ + "US-ID" + ], + "primary_region": "US-ID", + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -114.4255874246, + 42.55346463 + ], + [ + -114.4255860835, + 42.5541553058 + ], + [ + -114.4255686492, + 42.5548540624 + ], + [ + -114.4255766958, + 42.5553864754 + ], + [ + -114.4255807191, + 42.5555728776 + ] + ] + } + }, + "data": { + "description": "Stationary traffic", + "event_code": 101, + "from": "Leeann Drive", + "to": "Elizabeth Boulevard", + "magnitude_of_delay": 3, + "icon_category": 6, + "length": 234.4459237532, + "delay": 258, + "road_numbers": [], + "start_time": "2026-06-28T00:42:00Z", + "end_time": "2026-06-28T01:04:00Z", + "time_validity": "present", + "state_code": "ID", + "bbox_name": "magic_valley_burley", + "latitude": 42.55346463, + "longitude": -114.4255874246, + "_enriched": { + "geocoder": { + "name": "Meadowview Lane", + "city": "Twin Falls", + "county": "Twin Falls", + "state": "Idaho", + "country": "United States", + "postal_code": "83301", + "timezone": "America/Boise", + "landclass": null, + "elevation_m": 1152.9375 + } + } + } + } + }, + "subject": "central.traffic.incident.id", + "captured_epoch": 1783206527 +} \ No newline at end of file diff --git a/work/tests/fixtures/traffic/0018.json b/work/tests/fixtures/traffic/0018.json new file mode 100644 index 0000000..b1a2b2e --- /dev/null +++ b/work/tests/fixtures/traffic/0018.json @@ -0,0 +1,102 @@ +{ + "envelope": { + "id": "ID:tomtom:TTI-4ee2bdba-b112-4223-894d-2b10d1a81de0-TTL14570896572022000", + "source": "central.echo6.co", + "type": "central.incident.tomtom_incidents.v1", + "time": "2026-06-28T01:34:00+00:00", + "datacontenttype": "application/json", + "centralschemaversion": "1.0", + "centralcategory": "incident.tomtom_incidents", + "centralseverity": 3, + "specversion": "1.0", + "data": { + "id": "ID:tomtom:TTI-4ee2bdba-b112-4223-894d-2b10d1a81de0-TTL14570896572022000", + "adapter": "tomtom_incidents", + "category": "incident.tomtom_incidents", + "time": "2026-06-28T01:34:00Z", + "expires": "2026-06-28T02:03:30Z", + "severity": 3, + "geo": { + "centroid": [ + -116.9250763154, + 44.0236169542 + ], + "bbox": null, + "regions": [ + "US-ID" + ], + "primary_region": "US-ID", + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -116.9250763154, + 44.0236169542 + ], + [ + -116.9255282676, + 44.0236156282 + ], + [ + -116.9256543314, + 44.0236156282 + ], + [ + -116.925893048, + 44.023614242 + ], + [ + -116.9260298407, + 44.023614242 + ], + [ + -116.9263409769, + 44.0236129161 + ], + [ + -116.9284626043, + 44.0235941117 + ], + [ + -116.9286315834, + 44.0235927858 + ] + ] + } + }, + "data": { + "description": "Stationary traffic", + "event_code": 101, + "from": "North Whitley Drive", + "to": "North Arizona Avenue", + "magnitude_of_delay": 3, + "icon_category": 6, + "length": 284.2768770846, + "delay": 197, + "road_numbers": [], + "start_time": "2026-06-28T01:34:00Z", + "end_time": "2026-06-28T02:03:30Z", + "time_validity": "present", + "state_code": "ID", + "bbox_name": "treasure_valley_ext", + "latitude": 44.0236169542, + "longitude": -116.9250763154, + "_enriched": { + "geocoder": { + "name": "Ogawa's Sushi", + "city": "Fruitland", + "county": "Payette", + "state": "Idaho", + "country": "United States", + "postal_code": "83619", + "timezone": "America/Boise", + "landclass": null, + "elevation_m": 669.35546875 + } + } + } + } + }, + "subject": "central.traffic.incident.id", + "captured_epoch": 1783206527 +} \ No newline at end of file diff --git a/work/tests/fixtures/traffic/0019.json b/work/tests/fixtures/traffic/0019.json new file mode 100644 index 0000000..abcd216 --- /dev/null +++ b/work/tests/fixtures/traffic/0019.json @@ -0,0 +1,102 @@ +{ + "envelope": { + "id": "ID:tomtom:TTI-4ee2bdba-b112-4223-894d-2b10d1a81de0-TTL14720934188027000", + "source": "central.echo6.co", + "type": "central.incident.tomtom_incidents.v1", + "time": "2026-06-28T01:40:30+00:00", + "datacontenttype": "application/json", + "centralschemaversion": "1.0", + "centralcategory": "incident.tomtom_incidents", + "centralseverity": 3, + "specversion": "1.0", + "data": { + "id": "ID:tomtom:TTI-4ee2bdba-b112-4223-894d-2b10d1a81de0-TTL14720934188027000", + "adapter": "tomtom_incidents", + "category": "incident.tomtom_incidents", + "time": "2026-06-28T01:40:30Z", + "expires": "2026-06-28T02:03:30Z", + "severity": 3, + "geo": { + "centroid": [ + -116.723612914, + 43.5925524427 + ], + "bbox": null, + "regions": [ + "US-ID" + ], + "primary_region": "US-ID", + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -116.723612914, + 43.5925524427 + ], + [ + -116.7240259742, + 43.5923056686 + ], + [ + -116.724844048, + 43.5918631119 + ], + [ + -116.7250586247, + 43.5917531097 + ], + [ + -116.7256111597, + 43.5914701504 + ], + [ + -116.7257077193, + 43.5914205519 + ], + [ + -116.7258525586, + 43.591340053 + ], + [ + -116.7262830531, + 43.5911026838 + ] + ] + } + }, + "data": { + "description": "Stationary traffic", + "event_code": 101, + "from": "Orchard Ave (Riverside Rd)", + "to": "Marsing Rd (Riverside Rd)", + "magnitude_of_delay": 3, + "icon_category": 6, + "length": 268.8367110723, + "delay": 138, + "road_numbers": [], + "start_time": "2026-06-28T01:40:30Z", + "end_time": "2026-06-28T02:03:30Z", + "time_validity": "present", + "state_code": "ID", + "bbox_name": "treasure_valley_ext", + "latitude": 43.5925524427, + "longitude": -116.723612914, + "_enriched": { + "geocoder": { + "name": "Lower Dam Recreation Area--Deer Flat NWR", + "city": null, + "county": "Canyon", + "state": "Idaho", + "country": "United States", + "postal_code": null, + "timezone": "America/Boise", + "landclass": "Deer Flat National Wildlife Refuge", + "elevation_m": 781.33984375 + } + } + } + } + }, + "subject": "central.traffic.incident.id", + "captured_epoch": 1783206527 +} \ No newline at end of file diff --git a/work/tests/fixtures/traffic/0020.json b/work/tests/fixtures/traffic/0020.json new file mode 100644 index 0000000..1407e6b --- /dev/null +++ b/work/tests/fixtures/traffic/0020.json @@ -0,0 +1,172 @@ +{ + "envelope": { + "id": "ID:tomtom:TTI-4ee2bdba-b112-4223-894d-2b10d1a81de0-TTL14746903664017000", + "source": "central.echo6.co", + "type": "central.incident.tomtom_incidents.v1", + "time": "2026-06-28T01:43:00+00:00", + "datacontenttype": "application/json", + "centralschemaversion": "1.0", + "centralcategory": "incident.tomtom_incidents", + "centralseverity": 3, + "specversion": "1.0", + "data": { + "id": "ID:tomtom:TTI-4ee2bdba-b112-4223-894d-2b10d1a81de0-TTL14746903664017000", + "adapter": "tomtom_incidents", + "category": "incident.tomtom_incidents", + "time": "2026-06-28T01:43:00Z", + "expires": "2026-06-28T02:03:30Z", + "severity": 3, + "geo": { + "centroid": [ + -116.6074330305, + 43.6049777402 + ], + "bbox": null, + "regions": [ + "US-ID" + ], + "primary_region": "US-ID", + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -116.6074330305, + 43.6049777402 + ], + [ + -116.6070897078, + 43.6049750696 + ], + [ + -116.6070052182, + 43.6049737344 + ], + [ + -116.6067906415, + 43.6049710638 + ], + [ + -116.6065639948, + 43.6049697285 + ], + [ + -116.6060396229, + 43.6049643267 + ], + [ + -116.6058921014, + 43.6049629915 + ], + [ + -116.6058156585, + 43.604965662 + ], + [ + -116.605027089, + 43.6049898184 + ], + [ + -116.604808489, + 43.6049965554 + ], + [ + -116.6047789847, + 43.6049965554 + ], + [ + -116.6045563614, + 43.6050032318 + ], + [ + -116.6044195687, + 43.60501531 + ], + [ + -116.604359219, + 43.6050193158 + ], + [ + -116.6042841171, + 43.6050260529 + ], + [ + -116.6040909981, + 43.6050555503 + ], + [ + -116.6040655171, + 43.6050582209 + ], + [ + -116.6040118729, + 43.6050662932 + ], + [ + -116.6038361882, + 43.605102467 + ], + [ + -116.6038120484, + 43.6051078688 + ], + [ + -116.6037704741, + 43.6051158804 + ], + [ + -116.6035465097, + 43.6051748752 + ], + [ + -116.6032916998, + 43.6052714395 + ], + [ + -116.6032286679, + 43.605303668 + ], + [ + -116.6030730998, + 43.6053827523 + ] + ] + } + }, + "data": { + "description": "Stationary traffic", + "event_code": 101, + "from": "N Middleton Rd (ID-55)", + "to": "I-84 Bus (ID-55)", + "magnitude_of_delay": 3, + "icon_category": 6, + "length": 360.2418990839, + "delay": 132, + "road_numbers": [ + "ID-55" + ], + "start_time": "2026-06-28T01:43:00Z", + "end_time": "2026-06-28T02:03:30Z", + "time_validity": "present", + "state_code": "ID", + "bbox_name": "treasure_valley_ext", + "latitude": 43.6049777402, + "longitude": -116.6074330305, + "_enriched": { + "geocoder": { + "name": "West Karcher Road", + "city": "Nampa", + "county": "Canyon", + "state": "Idaho", + "country": "United States", + "postal_code": "83651", + "timezone": "America/Boise", + "landclass": null, + "elevation_m": 749.34375 + } + } + } + } + }, + "subject": "central.traffic.incident.id", + "captured_epoch": 1783206527 +} \ No newline at end of file diff --git a/work/tests/fixtures/traffic/0021.json b/work/tests/fixtures/traffic/0021.json new file mode 100644 index 0000000..d51f1ff --- /dev/null +++ b/work/tests/fixtures/traffic/0021.json @@ -0,0 +1,166 @@ +{ + "envelope": { + "id": "ID:tomtom:TTI-4ee2bdba-b112-4223-894d-2b10d1a81de0-TTL14746903848011000", + "source": "central.echo6.co", + "type": "central.incident.tomtom_incidents.v1", + "time": "2026-06-28T01:14:02+00:00", + "datacontenttype": "application/json", + "centralschemaversion": "1.0", + "centralcategory": "incident.tomtom_incidents", + "centralseverity": 3, + "specversion": "1.0", + "data": { + "id": "ID:tomtom:TTI-4ee2bdba-b112-4223-894d-2b10d1a81de0-TTL14746903848011000", + "adapter": "tomtom_incidents", + "category": "incident.tomtom_incidents", + "time": "2026-06-28T01:14:02Z", + "expires": "2026-06-28T02:04:30Z", + "severity": 3, + "geo": { + "centroid": [ + -116.5932159816, + 43.6158903249 + ], + "bbox": null, + "regions": [ + "US-ID" + ], + "primary_region": "US-ID", + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -116.5932159816, + 43.6158903249 + ], + [ + -116.5932106172, + 43.6157951126 + ], + [ + -116.5932106172, + 43.6156958344 + ], + [ + -116.5932092761, + 43.615500069 + ], + [ + -116.593207935, + 43.615382039 + ], + [ + -116.5932065939, + 43.6151232217 + ], + [ + -116.593207935, + 43.6144405821 + ], + [ + -116.5932092761, + 43.6144003481 + ], + [ + -116.5932092761, + 43.6143172098 + ], + [ + -116.5932092761, + 43.6141991775 + ], + [ + -116.593207935, + 43.6138692926 + ], + [ + -116.593207935, + 43.6138558811 + ], + [ + -116.5932092761, + 43.613713695 + ], + [ + -116.5932186638, + 43.6133220899 + ], + [ + -116.5932790135, + 43.6131316571 + ], + [ + -116.5933554565, + 43.6128849068 + ], + [ + -116.5933728909, + 43.6128299249 + ], + [ + -116.5934251939, + 43.6127467844 + ], + [ + -116.593438605, + 43.6127253014 + ], + [ + -116.5934962725, + 43.6126341502 + ], + [ + -116.5937296247, + 43.6122666308 + ], + [ + -116.5941587781, + 43.6119125817 + ], + [ + -116.594247291, + 43.6118401816 + ], + [ + -116.5943854248, + 43.6117248754 + ] + ] + } + }, + "data": { + "description": "Stationary traffic", + "event_code": 101, + "from": "Cherry Lane", + "to": "Karcher Connector", + "magnitude_of_delay": 3, + "icon_category": 6, + "length": 490.7193235348, + "delay": 137, + "road_numbers": [], + "start_time": "2026-06-28T01:14:02Z", + "end_time": "2026-06-28T02:04:30Z", + "time_validity": "present", + "state_code": "ID", + "bbox_name": "treasure_valley_ext", + "latitude": 43.6158903249, + "longitude": -116.5932159816, + "_enriched": { + "geocoder": { + "name": "Midland Boulevard", + "city": "Nampa", + "county": "Canyon", + "state": "Idaho", + "country": "United States", + "postal_code": "83652", + "timezone": "America/Boise", + "landclass": null, + "elevation_m": 749.484375 + } + } + } + } + }, + "subject": "central.traffic.incident.id", + "captured_epoch": 1783206527 +} \ No newline at end of file diff --git a/work/tests/fixtures/traffic/0022.json b/work/tests/fixtures/traffic/0022.json new file mode 100644 index 0000000..3f5e4ba --- /dev/null +++ b/work/tests/fixtures/traffic/0022.json @@ -0,0 +1,122 @@ +{ + "envelope": { + "id": "ID:tomtom:TTI-4ee2bdba-b112-4223-894d-2b10d1a81de0-TTL14819038596027000", + "source": "central.echo6.co", + "type": "central.incident.tomtom_incidents.v1", + "time": "2026-06-28T01:38:00+00:00", + "datacontenttype": "application/json", + "centralschemaversion": "1.0", + "centralcategory": "incident.tomtom_incidents", + "centralseverity": 3, + "specversion": "1.0", + "data": { + "id": "ID:tomtom:TTI-4ee2bdba-b112-4223-894d-2b10d1a81de0-TTL14819038596027000", + "adapter": "tomtom_incidents", + "category": "incident.tomtom_incidents", + "time": "2026-06-28T01:38:00Z", + "expires": "2026-06-28T02:04:30Z", + "severity": 3, + "geo": { + "centroid": [ + -116.4236172234, + 43.4884357486 + ], + "bbox": null, + "regions": [ + "US-ID" + ], + "primary_region": "US-ID", + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -116.4236172234, + 43.4884357486 + ], + [ + -116.423586378, + 43.4884357486 + ], + [ + -116.4235582148, + 43.4884357486 + ], + [ + -116.4235300516, + 43.4884357486 + ], + [ + -116.4230780994, + 43.4884384851 + ], + [ + -116.4228487705, + 43.488439823 + ], + [ + -116.4226771091, + 43.4884411608 + ], + [ + -116.4222090636, + 43.4884438365 + ], + [ + -116.4221393262, + 43.4884438365 + ], + [ + -116.4218751286, + 43.4884558773 + ], + [ + -116.4216283654, + 43.4885149255 + ], + [ + -116.4214204942, + 43.4886007308 + ], + [ + -116.4213266169, + 43.4886610559 + ] + ] + } + }, + "data": { + "description": "Stationary traffic", + "event_code": 101, + "from": "Ten Mile Rd (W Avalon St)", + "to": "W Shortline St (W Avalon St)", + "magnitude_of_delay": 3, + "icon_category": 6, + "length": 190.9593507059, + "delay": 255, + "road_numbers": [], + "start_time": "2026-06-28T01:38:00Z", + "end_time": "2026-06-28T02:04:30Z", + "time_validity": "present", + "state_code": "ID", + "bbox_name": "treasure_valley_ext", + "latitude": 43.4884357486, + "longitude": -116.4236172234, + "_enriched": { + "geocoder": { + "name": null, + "city": "Kuna", + "county": "Ada", + "state": "ID", + "country": "United States", + "postal_code": "83634", + "timezone": "America/Boise", + "landclass": null, + "elevation_m": 819.4453125 + } + } + } + } + }, + "subject": "central.traffic.incident.id", + "captured_epoch": 1783206527 +} \ No newline at end of file diff --git a/work/tests/fixtures/traffic/0023.json b/work/tests/fixtures/traffic/0023.json new file mode 100644 index 0000000..5a949c6 --- /dev/null +++ b/work/tests/fixtures/traffic/0023.json @@ -0,0 +1,142 @@ +{ + "envelope": { + "id": "ID:tomtom:TTI-4ee2bdba-b112-4223-894d-2b10d1a81de0-TTL14781529848047000", + "source": "central.echo6.co", + "type": "central.incident.tomtom_incidents.v1", + "time": "2026-06-28T01:38:30+00:00", + "datacontenttype": "application/json", + "centralschemaversion": "1.0", + "centralcategory": "incident.tomtom_incidents", + "centralseverity": 2, + "specversion": "1.0", + "data": { + "id": "ID:tomtom:TTI-4ee2bdba-b112-4223-894d-2b10d1a81de0-TTL14781529848047000", + "adapter": "tomtom_incidents", + "category": "incident.tomtom_incidents", + "time": "2026-06-28T01:38:30Z", + "expires": "2026-06-28T02:03:30Z", + "severity": 2, + "geo": { + "centroid": [ + -116.4176332151, + 43.6361758949 + ], + "bbox": null, + "regions": [ + "US-ID" + ], + "primary_region": "US-ID", + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -116.4176332151, + 43.6361758949 + ], + [ + -116.4177257513, + 43.6361731651 + ], + [ + -116.4178933893, + 43.6361678268 + ], + [ + -116.4181669747, + 43.636217449 + ], + [ + -116.4181790446, + 43.6362187836 + ], + [ + -116.4187047576, + 43.6363153587 + ], + [ + -116.4194611405, + 43.6364950415 + ], + [ + -116.419968078, + 43.6366693854 + ], + [ + -116.4202590977, + 43.6367901034 + ], + [ + -116.4203114008, + 43.6368129124 + ], + [ + -116.4208867346, + 43.6371227137 + ], + [ + -116.4220910464, + 43.6378791024 + ], + [ + -116.4223284219, + 43.6380279647 + ], + [ + -116.4224477802, + 43.6381338787 + ], + [ + -116.4225000833, + 43.6382063078 + ], + [ + -116.4225443398, + 43.6383068833 + ], + [ + -116.4226006662, + 43.6386368161 + ], + [ + -116.4226100539, + 43.6386998422 + ] + ] + } + }, + "data": { + "description": "Queuing traffic", + "event_code": 108, + "from": "North Sirocco Avenue / West Winddrift Street", + "to": "North Towerbridge Way / West Teano Drive", + "magnitude_of_delay": 2, + "icon_category": 6, + "length": 520.771513768, + "delay": 122, + "road_numbers": [], + "start_time": "2026-06-28T01:38:30Z", + "end_time": "2026-06-28T02:03:30Z", + "time_validity": "present", + "state_code": "ID", + "bbox_name": "treasure_valley_ext", + "latitude": 43.6361758949, + "longitude": -116.4176332151, + "_enriched": { + "geocoder": { + "name": null, + "city": "Meridian", + "county": "Ada", + "state": "ID", + "country": "United States", + "postal_code": "83646", + "timezone": "America/Boise", + "landclass": null, + "elevation_m": 781.5 + } + } + } + } + }, + "subject": "central.traffic.incident.id", + "captured_epoch": 1783206527 +} \ No newline at end of file diff --git a/work/tests/fixtures/traffic/0024.json b/work/tests/fixtures/traffic/0024.json new file mode 100644 index 0000000..25435eb --- /dev/null +++ b/work/tests/fixtures/traffic/0024.json @@ -0,0 +1,110 @@ +{ + "envelope": { + "id": "ID:tomtom:TTI-4ee2bdba-b112-4223-894d-2b10d1a81de0-TTL14821924028004000", + "source": "central.echo6.co", + "type": "central.incident.tomtom_incidents.v1", + "time": "2026-06-28T01:43:00+00:00", + "datacontenttype": "application/json", + "centralschemaversion": "1.0", + "centralcategory": "incident.tomtom_incidents", + "centralseverity": 3, + "specversion": "1.0", + "data": { + "id": "ID:tomtom:TTI-4ee2bdba-b112-4223-894d-2b10d1a81de0-TTL14821924028004000", + "adapter": "tomtom_incidents", + "category": "incident.tomtom_incidents", + "time": "2026-06-28T01:43:00Z", + "expires": "2026-06-28T02:03:30Z", + "severity": 3, + "geo": { + "centroid": [ + -116.4137453531, + 43.4836923011 + ], + "bbox": null, + "regions": [ + "US-ID" + ], + "primary_region": "US-ID", + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -116.4137453531, + 43.4836923011 + ], + [ + -116.4137560819, + 43.4845935355 + ], + [ + -116.4137587641, + 43.4847584674 + ], + [ + -116.4137601052, + 43.4848710977 + ], + [ + -116.4137627875, + 43.4850172977 + ], + [ + -116.4137627875, + 43.4850307379 + ], + [ + -116.4137654697, + 43.4851768768 + ], + [ + -116.4137654697, + 43.4852023583 + ], + [ + -116.4137668108, + 43.4853364554 + ], + [ + -116.413769493, + 43.4855229137 + ] + ] + } + }, + "data": { + "description": "Stationary traffic", + "event_code": 101, + "from": "W Kuna Mora Rd (S Swan Falls Rd)", + "to": "E Avalon St/N Linder Ave (S Swan Falls Rd)", + "magnitude_of_delay": 3, + "icon_category": 6, + "length": 203.5647239426, + "delay": 193, + "road_numbers": [], + "start_time": "2026-06-28T01:43:00Z", + "end_time": "2026-06-28T02:03:30Z", + "time_validity": "present", + "state_code": "ID", + "bbox_name": "treasure_valley_ext", + "latitude": 43.4836923011, + "longitude": -116.4137453531, + "_enriched": { + "geocoder": { + "name": null, + "city": "Kuna", + "county": "Ada", + "state": "ID", + "country": "United States", + "postal_code": "83634", + "timezone": "America/Boise", + "landclass": null, + "elevation_m": 824.18359375 + } + } + } + } + }, + "subject": "central.traffic.incident.id", + "captured_epoch": 1783206527 +} \ No newline at end of file diff --git a/work/tests/fixtures/traffic/0025.json b/work/tests/fixtures/traffic/0025.json new file mode 100644 index 0000000..7d0c86a --- /dev/null +++ b/work/tests/fixtures/traffic/0025.json @@ -0,0 +1,80 @@ +{ + "envelope": { + "id": "ID:tomtom:TTI-4ee2bdba-b112-4223-894d-2b10d1a81de0-TTR14801727888001001", + "source": "central.echo6.co", + "type": "central.incident.tomtom_incidents.v1", + "time": "2026-06-27T21:16:40+00:00", + "datacontenttype": "application/json", + "centralschemaversion": "1.0", + "centralcategory": "incident.tomtom_incidents", + "centralseverity": 1, + "specversion": "1.0", + "data": { + "id": "ID:tomtom:TTI-4ee2bdba-b112-4223-894d-2b10d1a81de0-TTR14801727888001001", + "adapter": "tomtom_incidents", + "category": "incident.tomtom_incidents", + "time": "2026-06-27T21:16:40Z", + "expires": "2026-06-28T05:59:00Z", + "severity": 1, + "geo": { + "centroid": [ + -116.3543813621, + 43.6213204507 + ], + "bbox": null, + "regions": [ + "US-ID" + ], + "primary_region": "US-ID", + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -116.3543813621, + 43.6213204507 + ], + [ + -116.3543786798, + 43.6214988434 + ] + ] + } + }, + "data": { + "description": "Lane closed", + "event_code": 500, + "from": "E Fairview Ave (ID-55)", + "to": "E Ustick Rd (ID-55)", + "magnitude_of_delay": 0, + "icon_category": 7, + "length": 19.8375354125, + "delay": null, + "road_numbers": [ + "ID-55" + ], + "start_time": "2026-06-27T21:16:40Z", + "end_time": "2026-06-28T05:59:00Z", + "time_validity": "present", + "state_code": "ID", + "bbox_name": "treasure_valley_ext", + "latitude": 43.6213204507, + "longitude": -116.3543813621, + "_enriched": { + "geocoder": { + "name": "North Eagle Road", + "city": "Meridian", + "county": "Ada", + "state": "Idaho", + "country": "United States", + "postal_code": "83713", + "timezone": "America/Boise", + "landclass": null, + "elevation_m": 800.0625 + } + } + } + } + }, + "subject": "central.traffic.incident.id", + "captured_epoch": 1783206527 +} \ No newline at end of file diff --git a/work/tests/fixtures/traffic/0026.json b/work/tests/fixtures/traffic/0026.json new file mode 100644 index 0000000..e38c5b4 --- /dev/null +++ b/work/tests/fixtures/traffic/0026.json @@ -0,0 +1,90 @@ +{ + "envelope": { + "id": "ID:tomtom:TTI-4ee2bdba-b112-4223-894d-2b10d1a81de0-TTL14795958064015000", + "source": "central.echo6.co", + "type": "central.incident.tomtom_incidents.v1", + "time": "2026-06-28T01:35:30+00:00", + "datacontenttype": "application/json", + "centralschemaversion": "1.0", + "centralcategory": "incident.tomtom_incidents", + "centralseverity": 3, + "specversion": "1.0", + "data": { + "id": "ID:tomtom:TTI-4ee2bdba-b112-4223-894d-2b10d1a81de0-TTL14795958064015000", + "adapter": "tomtom_incidents", + "category": "incident.tomtom_incidents", + "time": "2026-06-28T01:35:30Z", + "expires": "2026-06-28T02:03:30Z", + "severity": 3, + "geo": { + "centroid": [ + -116.3128486966, + 43.6850845875 + ], + "bbox": null, + "regions": [ + "US-ID" + ], + "primary_region": "US-ID", + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -116.3128486966, + 43.6850845875 + ], + [ + -116.3135527764, + 43.6851865393 + ], + [ + -116.3137405311, + 43.6852119969 + ], + [ + -116.313893417, + 43.6852321205 + ], + [ + -116.3140114342, + 43.6852227254 + ] + ] + } + }, + "data": { + "description": "Stationary traffic", + "event_code": 101, + "from": "Jennie Lane", + "to": "Horseshoe Bend Road", + "magnitude_of_delay": 3, + "icon_category": 6, + "length": 95.1421760956, + "delay": 73, + "road_numbers": [], + "start_time": "2026-06-28T01:35:30Z", + "end_time": "2026-06-28T02:03:30Z", + "time_validity": "present", + "state_code": "ID", + "bbox_name": "treasure_valley_ext", + "latitude": 43.6850845875, + "longitude": -116.3128486966, + "_enriched": { + "geocoder": { + "name": null, + "city": "Boise", + "county": "Ada", + "state": "ID", + "country": "United States", + "postal_code": "83714", + "timezone": "America/Boise", + "landclass": null, + "elevation_m": 787.5 + } + } + } + } + }, + "subject": "central.traffic.incident.id", + "captured_epoch": 1783206527 +} \ No newline at end of file diff --git a/work/tests/fixtures/traffic/0027.json b/work/tests/fixtures/traffic/0027.json new file mode 100644 index 0000000..061022a --- /dev/null +++ b/work/tests/fixtures/traffic/0027.json @@ -0,0 +1,86 @@ +{ + "envelope": { + "id": "ID:tomtom:TTI-4ee2bdba-b112-4223-894d-2b10d1a81de0-TTL15508651644023000", + "source": "central.echo6.co", + "type": "central.incident.tomtom_incidents.v1", + "time": "2026-06-28T01:37:00+00:00", + "datacontenttype": "application/json", + "centralschemaversion": "1.0", + "centralcategory": "incident.tomtom_incidents", + "centralseverity": 3, + "specversion": "1.0", + "data": { + "id": "ID:tomtom:TTI-4ee2bdba-b112-4223-894d-2b10d1a81de0-TTL15508651644023000", + "adapter": "tomtom_incidents", + "category": "incident.tomtom_incidents", + "time": "2026-06-28T01:37:00Z", + "expires": "2026-06-28T02:03:30Z", + "severity": 3, + "geo": { + "centroid": [ + -114.4463101715, + 42.5667523455 + ], + "bbox": null, + "regions": [ + "US-ID" + ], + "primary_region": "US-ID", + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -114.4463101715, + 42.5667523455 + ], + [ + -114.4464442819, + 42.5667523455 + ], + [ + -114.4471362919, + 42.5667536419 + ], + [ + -114.448536405, + 42.566755 + ] + ] + } + }, + "data": { + "description": "Stationary traffic", + "event_code": 101, + "from": "Sunrise Boulevard North", + "to": "Teton Street", + "magnitude_of_delay": 3, + "icon_category": 6, + "length": 182.3152714831, + "delay": 203, + "road_numbers": [], + "start_time": "2026-06-28T01:37:00Z", + "end_time": "2026-06-28T02:03:30Z", + "time_validity": "present", + "state_code": "ID", + "bbox_name": "magic_valley_burley", + "latitude": 42.5667523455, + "longitude": -114.4463101715, + "_enriched": { + "geocoder": { + "name": "Sunrise Boulevard North", + "city": "Twin Falls", + "county": "Twin Falls", + "state": "Idaho", + "country": "United States", + "postal_code": "83301", + "timezone": "America/Boise", + "landclass": null, + "elevation_m": 1139.76953125 + } + } + } + } + }, + "subject": "central.traffic.incident.id", + "captured_epoch": 1783206527 +} \ No newline at end of file diff --git a/work/tests/fixtures/traffic/0028.json b/work/tests/fixtures/traffic/0028.json new file mode 100644 index 0000000..fc5fd9a --- /dev/null +++ b/work/tests/fixtures/traffic/0028.json @@ -0,0 +1,86 @@ +{ + "envelope": { + "id": "ID:tomtom:TTI-4ee2bdba-b112-4223-894d-2b10d1a81de0-TTL15528849480028000", + "source": "central.echo6.co", + "type": "central.incident.tomtom_incidents.v1", + "time": "2026-06-28T01:41:30+00:00", + "datacontenttype": "application/json", + "centralschemaversion": "1.0", + "centralcategory": "incident.tomtom_incidents", + "centralseverity": 3, + "specversion": "1.0", + "data": { + "id": "ID:tomtom:TTI-4ee2bdba-b112-4223-894d-2b10d1a81de0-TTL15528849480028000", + "adapter": "tomtom_incidents", + "category": "incident.tomtom_incidents", + "time": "2026-06-28T01:41:30Z", + "expires": "2026-06-28T02:03:30Z", + "severity": 3, + "geo": { + "centroid": [ + -114.4015481263, + 42.5380164647 + ], + "bbox": null, + "regions": [ + "US-ID" + ], + "primary_region": "US-ID", + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -114.4015481263, + 42.5380164647 + ], + [ + -114.4015454441, + 42.5375028023 + ], + [ + -114.401544103, + 42.5373539586 + ], + [ + -114.4015347153, + 42.5357151105 + ] + ] + } + }, + "data": { + "description": "Stationary traffic", + "event_code": 101, + "from": "Jayco Avenue", + "to": "East 3700 North", + "magnitude_of_delay": 3, + "icon_category": 6, + "length": 255.9013201715, + "delay": 147, + "road_numbers": [], + "start_time": "2026-06-28T01:41:30Z", + "end_time": "2026-06-28T02:03:30Z", + "time_validity": "present", + "state_code": "ID", + "bbox_name": "magic_valley_burley", + "latitude": 42.5380164647, + "longitude": -114.4015481263, + "_enriched": { + "geocoder": { + "name": "Jayco Avenue", + "city": "Twin Falls", + "county": "Twin Falls", + "state": "Idaho", + "country": "United States", + "postal_code": "83341", + "timezone": "America/Boise", + "landclass": null, + "elevation_m": 1169.60546875 + } + } + } + } + }, + "subject": "central.traffic.incident.id", + "captured_epoch": 1783206527 +} \ No newline at end of file diff --git a/work/tests/fixtures/traffic/0029.json b/work/tests/fixtures/traffic/0029.json new file mode 100644 index 0000000..5a3328a --- /dev/null +++ b/work/tests/fixtures/traffic/0029.json @@ -0,0 +1,84 @@ +{ + "envelope": { + "id": "ID:tomtom:TTI-4ee2bdba-b112-4223-894d-2b10d1a81de0-TTR15673123080002000", + "source": "central.echo6.co", + "type": "central.incident.tomtom_incidents.v1", + "time": "2026-06-28T01:17:30+00:00", + "datacontenttype": "application/json", + "centralschemaversion": "1.0", + "centralcategory": "incident.tomtom_incidents", + "centralseverity": 1, + "specversion": "1.0", + "data": { + "id": "ID:tomtom:TTI-4ee2bdba-b112-4223-894d-2b10d1a81de0-TTR15673123080002000", + "adapter": "tomtom_incidents", + "category": "incident.tomtom_incidents", + "time": "2026-06-28T01:17:30Z", + "expires": null, + "severity": 1, + "geo": { + "centroid": [ + -113.7932781069, + 42.5386869925 + ], + "bbox": null, + "regions": [ + "US-ID" + ], + "primary_region": "US-ID", + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -113.7932781069, + 42.5386869925 + ], + [ + -113.7932781069, + 42.5386212803 + ], + [ + -113.7932781069, + 42.5386159689 + ] + ] + } + }, + "data": { + "description": "Roadworks", + "event_code": 701, + "from": "5th St (Overland Ave/ID-27)", + "to": "US-30/Main St (Overland Ave/ID-27)", + "magnitude_of_delay": 0, + "icon_category": 9, + "length": 7.8974647221, + "delay": null, + "road_numbers": [ + "I-84 Business" + ], + "start_time": "2026-06-28T01:17:30Z", + "end_time": null, + "time_validity": "present", + "state_code": "ID", + "bbox_name": "magic_valley_burley", + "latitude": 42.5386869925, + "longitude": -113.7932781069, + "_enriched": { + "geocoder": { + "name": "U.S. Bank", + "city": "Burley", + "county": "Cassia", + "state": "Idaho", + "country": "United States", + "postal_code": "83318", + "timezone": "America/Boise", + "landclass": null, + "elevation_m": 1268.1796875 + } + } + } + } + }, + "subject": "central.traffic.incident.id", + "captured_epoch": 1783206527 +} \ No newline at end of file diff --git a/work/tests/fixtures/traffic/0030.json b/work/tests/fixtures/traffic/0030.json new file mode 100644 index 0000000..4b0e200 --- /dev/null +++ b/work/tests/fixtures/traffic/0030.json @@ -0,0 +1,94 @@ +{ + "envelope": { + "id": "ID:tomtom:TTI-4ee2bdba-b112-4223-894d-2b10d1a81de0-TTL14617060700009000", + "source": "central.echo6.co", + "type": "central.incident.tomtom_incidents.v1", + "time": "2026-06-28T02:39:30+00:00", + "datacontenttype": "application/json", + "centralschemaversion": "1.0", + "centralcategory": "incident.tomtom_incidents", + "centralseverity": 3, + "specversion": "1.0", + "data": { + "id": "ID:tomtom:TTI-4ee2bdba-b112-4223-894d-2b10d1a81de0-TTL14617060700009000", + "adapter": "tomtom_incidents", + "category": "incident.tomtom_incidents", + "time": "2026-06-28T02:39:30Z", + "expires": "2026-06-28T03:03:00Z", + "severity": 3, + "geo": { + "centroid": [ + -116.9522175884, + 43.799407079 + ], + "bbox": null, + "regions": [ + "US-ID" + ], + "primary_region": "US-ID", + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -116.9522175884, + 43.799407079 + ], + [ + -116.9522417283, + 43.8000413955 + ], + [ + -116.9522497749, + 43.8002305095 + ], + [ + -116.9522497749, + 43.8002505945 + ], + [ + -116.9522578216, + 43.8006167211 + ] + ] + } + }, + "data": { + "description": "Stationary traffic", + "event_code": 101, + "from": "ID-18/N Roswell Blvd (US-95)", + "to": "US-20/US-26/Anderson Corner Rd (US-95)", + "magnitude_of_delay": 3, + "icon_category": 6, + "length": 134.5477074353, + "delay": 150, + "road_numbers": [ + "US-26", + "US-20", + "US-95" + ], + "start_time": "2026-06-28T02:39:30Z", + "end_time": "2026-06-28T03:03:00Z", + "time_validity": "present", + "state_code": "ID", + "bbox_name": "treasure_valley_ext", + "latitude": 43.799407079, + "longitude": -116.9522175884, + "_enriched": { + "geocoder": { + "name": "Parma Drive-In", + "city": "Parma", + "county": "Canyon", + "state": "Idaho", + "country": "United States", + "postal_code": "83660", + "timezone": "America/Boise", + "landclass": null, + "elevation_m": 697.1015625 + } + } + } + } + }, + "subject": "central.traffic.incident.id", + "captured_epoch": 1783206527 +} \ No newline at end of file diff --git a/work/tests/fixtures/traffic/0031.json b/work/tests/fixtures/traffic/0031.json new file mode 100644 index 0000000..7bdb8a7 --- /dev/null +++ b/work/tests/fixtures/traffic/0031.json @@ -0,0 +1,114 @@ +{ + "envelope": { + "id": "ID:tomtom:TTI-4ee2bdba-b112-4223-894d-2b10d1a81de0-TTL15765457940017000", + "source": "central.echo6.co", + "type": "central.incident.tomtom_incidents.v1", + "time": "2026-06-28T02:35:00+00:00", + "datacontenttype": "application/json", + "centralschemaversion": "1.0", + "centralcategory": "incident.tomtom_incidents", + "centralseverity": 3, + "specversion": "1.0", + "data": { + "id": "ID:tomtom:TTI-4ee2bdba-b112-4223-894d-2b10d1a81de0-TTL15765457940017000", + "adapter": "tomtom_incidents", + "category": "incident.tomtom_incidents", + "time": "2026-06-28T02:35:00Z", + "expires": "2026-06-28T03:03:00Z", + "severity": 3, + "geo": { + "centroid": [ + -113.421596357, + 42.5161497433 + ], + "bbox": null, + "regions": [ + "US-ID" + ], + "primary_region": "US-ID", + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -113.421596357, + 42.5161497433 + ], + [ + -113.4216365901, + 42.5161725408 + ], + [ + -113.4218042282, + 42.5162342609 + ], + [ + -113.4218645779, + 42.5162556992 + ], + [ + -113.4219718663, + 42.5162959191 + ], + [ + -113.4220348982, + 42.51631606 + ], + [ + -113.4230098811, + 42.5166285513 + ], + [ + -113.4232687143, + 42.516726413 + ], + [ + -113.4234886555, + 42.5168203205 + ], + [ + -113.4236911622, + 42.5169182437 + ], + [ + -113.4237716285, + 42.5169611198 + ] + ] + } + }, + "data": { + "description": "Stationary traffic", + "event_code": 101, + "from": "North Yale Road", + "to": "I-84", + "magnitude_of_delay": 3, + "icon_category": 6, + "length": 200.3039255056, + "delay": 220, + "road_numbers": [], + "start_time": "2026-06-28T02:35:00Z", + "end_time": "2026-06-28T03:03:00Z", + "time_validity": "present", + "state_code": "ID", + "bbox_name": "magic_valley_burley", + "latitude": 42.5161497433, + "longitude": -113.421596357, + "_enriched": { + "geocoder": { + "name": "Yale Road", + "city": null, + "county": "Cassia", + "state": "Idaho", + "country": "United States", + "postal_code": null, + "timezone": "America/Boise", + "landclass": null, + "elevation_m": 1375.94140625 + } + } + } + } + }, + "subject": "central.traffic.incident.id", + "captured_epoch": 1783206527 +} \ No newline at end of file diff --git a/work/tests/fixtures/traffic/0032.json b/work/tests/fixtures/traffic/0032.json new file mode 100644 index 0000000..3a68081 --- /dev/null +++ b/work/tests/fixtures/traffic/0032.json @@ -0,0 +1,92 @@ +{ + "envelope": { + "id": "idaho_511:event:11437", + "source": "central.echo6.co", + "type": "central.incident.itd_511.v1", + "time": "2026-06-28T03:19:30+00:00", + "datacontenttype": "application/json", + "centralschemaversion": "1.0", + "centralcategory": "incident.itd_511", + "centralseverity": 1, + "specversion": "1.0", + "data": { + "id": "idaho_511:event:11437", + "adapter": "itd_511", + "category": "incident.itd_511", + "time": "2026-06-28T03:19:30Z", + "expires": null, + "severity": 1, + "geo": { + "centroid": [ + -116.512966047015, + 43.5991939125997 + ], + "bbox": null, + "regions": [ + "US-ID" + ], + "primary_region": "US-ID", + "geometry": { + "type": "Point", + "coordinates": [ + -116.512966047015, + 43.5991939125997 + ] + } + }, + "data": { + "event_type_short": "incident", + "event_sub_type": "crash", + "roadway_name": "I-84", + "direction": "East", + "description": "Crash on I-84 Eastbound near Ada Canyon County Line. 1 Right lane blocked.", + "lanes_affected": "1 Right lane blocked", + "is_full_closure": false, + "itd_severity": "None", + "comment": "The rightmost lane of I-84 eastbound is blocked near Garrity Blvd., or milepost 38. Keep left and use caution.", + "cause": "Incident", + "organization": "ERS", + "recurrence_text": null, + "recurrence_schedules": [], + "restrictions": { + "Width": null, + "Height": null, + "Length": null, + "Weight": null, + "Speed": null + }, + "detour_polyline": null, + "detour_instructions": null, + "encoded_polyline": null, + "id_internal": 37336, + "source_id": "11437", + "reported_epoch": 1782616680, + "last_updated_epoch": 1782616770, + "start_epoch": 1782616680, + "planned_end_epoch": null, + "latitude": 43.5991939125997, + "longitude": -116.512966047015, + "_enriched": { + "mile_marker": { + "value": 38.0, + "source": "comment_regex", + "confidence": "high" + }, + "geocoder": { + "name": "Vietnam Veterans Memorial Highway", + "city": "Nampa", + "county": "Canyon", + "state": "Idaho", + "country": "United States", + "postal_code": "83687", + "timezone": "America/Boise", + "landclass": null, + "elevation_m": 778.296875 + } + } + } + } + }, + "subject": "central.traffic.incident.us.id", + "captured_epoch": 1783206527 +} \ No newline at end of file diff --git a/work/tests/fixtures/traffic/0033.json b/work/tests/fixtures/traffic/0033.json new file mode 100644 index 0000000..607a312 --- /dev/null +++ b/work/tests/fixtures/traffic/0033.json @@ -0,0 +1,106 @@ +{ + "envelope": { + "id": "ID:tomtom:TTI-4ee2bdba-b112-4223-894d-2b10d1a81de0-TTL14568011240023000", + "source": "central.echo6.co", + "type": "central.incident.tomtom_incidents.v1", + "time": "2026-06-28T03:32:30+00:00", + "datacontenttype": "application/json", + "centralschemaversion": "1.0", + "centralcategory": "incident.tomtom_incidents", + "centralseverity": 3, + "specversion": "1.0", + "data": { + "id": "ID:tomtom:TTI-4ee2bdba-b112-4223-894d-2b10d1a81de0-TTL14568011240023000", + "adapter": "tomtom_incidents", + "category": "incident.tomtom_incidents", + "time": "2026-06-28T03:32:30Z", + "expires": "2026-06-28T04:03:00Z", + "severity": 3, + "geo": { + "centroid": [ + -116.9280267453, + 44.0298678061 + ], + "bbox": null, + "regions": [ + "US-ID" + ], + "primary_region": "US-ID", + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -116.9280267453, + 44.0298678061 + ], + [ + -116.9285605049, + 44.0298772073 + ], + [ + -116.9289923406, + 44.0298731696 + ], + [ + -116.9293195701, + 44.0298235723 + ], + [ + -116.9295609689, + 44.0297886794 + ], + [ + -116.9297540879, + 44.0297699373 + ], + [ + -116.9301349616, + 44.0297658996 + ], + [ + -116.930794785, + 44.0297538467 + ], + [ + -116.9310630059, + 44.0297149161 + ] + ] + } + }, + "data": { + "description": "Stationary traffic", + "event_code": 101, + "from": "North Whitley Drive", + "to": "Vista Avenue", + "magnitude_of_delay": 3, + "icon_category": 6, + "length": 244.3089232858, + "delay": 219, + "road_numbers": [], + "start_time": "2026-06-28T03:32:30Z", + "end_time": "2026-06-28T04:03:00Z", + "time_validity": "present", + "state_code": "ID", + "bbox_name": "treasure_valley_ext", + "latitude": 44.0298678061, + "longitude": -116.9280267453, + "_enriched": { + "geocoder": { + "name": "Spring Creek Drive", + "city": "Fruitland", + "county": "Payette", + "state": "Idaho", + "country": "United States", + "postal_code": "83619", + "timezone": "America/Boise", + "landclass": null, + "elevation_m": 667.46875 + } + } + } + } + }, + "subject": "central.traffic.incident.id", + "captured_epoch": 1783206527 +} \ No newline at end of file diff --git a/work/tests/fixtures/traffic/0034.json b/work/tests/fixtures/traffic/0034.json new file mode 100644 index 0000000..c78b6bd --- /dev/null +++ b/work/tests/fixtures/traffic/0034.json @@ -0,0 +1,102 @@ +{ + "envelope": { + "id": "ID:tomtom:TTI-4ee2bdba-b112-4223-894d-2b10d1a81de0-TTL14732476596036000", + "source": "central.echo6.co", + "type": "central.incident.tomtom_incidents.v1", + "time": "2026-06-28T03:28:00+00:00", + "datacontenttype": "application/json", + "centralschemaversion": "1.0", + "centralcategory": "incident.tomtom_incidents", + "centralseverity": 3, + "specversion": "1.0", + "data": { + "id": "ID:tomtom:TTI-4ee2bdba-b112-4223-894d-2b10d1a81de0-TTL14732476596036000", + "adapter": "tomtom_incidents", + "category": "incident.tomtom_incidents", + "time": "2026-06-28T03:28:00Z", + "expires": "2026-06-28T04:03:00Z", + "severity": 3, + "geo": { + "centroid": [ + -116.6494766568, + 43.6265373683 + ], + "bbox": null, + "regions": [ + "US-ID" + ], + "primary_region": "US-ID", + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -116.6494766568, + 43.6265373683 + ], + [ + -116.6485821401, + 43.626550777 + ], + [ + -116.6483943855, + 43.6265547814 + ], + [ + -116.6482106542, + 43.626557451 + ], + [ + -116.6481449401, + 43.6265547814 + ], + [ + -116.6478324627, + 43.6265427075 + ], + [ + -116.6468708908, + 43.6265239597 + ], + [ + -116.6445105468, + 43.6265842682 + ] + ] + } + }, + "data": { + "description": "Stationary traffic", + "event_code": 101, + "from": "South Lake Avenue", + "to": "Highgarden Way", + "magnitude_of_delay": 3, + "icon_category": 6, + "length": 399.9312183663, + "delay": 159, + "road_numbers": [], + "start_time": "2026-06-28T03:28:00Z", + "end_time": "2026-06-28T04:03:00Z", + "time_validity": "present", + "state_code": "ID", + "bbox_name": "treasure_valley_ext", + "latitude": 43.6265373683, + "longitude": -116.6494766568, + "_enriched": { + "geocoder": { + "name": "Kane Avenue", + "city": "Caldwell", + "county": "Canyon", + "state": "Idaho", + "country": "United States", + "postal_code": "83652", + "timezone": "America/Boise", + "landclass": null, + "elevation_m": 735.08984375 + } + } + } + } + }, + "subject": "central.traffic.incident.id", + "captured_epoch": 1783206527 +} \ No newline at end of file diff --git a/work/tests/fixtures/traffic/0035.json b/work/tests/fixtures/traffic/0035.json new file mode 100644 index 0000000..94c72bb --- /dev/null +++ b/work/tests/fixtures/traffic/0035.json @@ -0,0 +1,102 @@ +{ + "envelope": { + "id": "ID:tomtom:TTI-4ee2bdba-b112-4223-894d-2b10d1a81de0-TTL14784415164028000", + "source": "central.echo6.co", + "type": "central.incident.tomtom_incidents.v1", + "time": "2026-06-28T03:27:30+00:00", + "datacontenttype": "application/json", + "centralschemaversion": "1.0", + "centralcategory": "incident.tomtom_incidents", + "centralseverity": 3, + "specversion": "1.0", + "data": { + "id": "ID:tomtom:TTI-4ee2bdba-b112-4223-894d-2b10d1a81de0-TTL14784415164028000", + "adapter": "tomtom_incidents", + "category": "incident.tomtom_incidents", + "time": "2026-06-28T03:27:30Z", + "expires": "2026-06-28T04:03:00Z", + "severity": 3, + "geo": { + "centroid": [ + -116.4291975592, + 43.62507957 + ], + "bbox": null, + "regions": [ + "US-ID" + ], + "primary_region": "US-ID", + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -116.4291975592, + 43.62507957 + ], + [ + -116.4298721348, + 43.6250849093 + ], + [ + -116.4302275275, + 43.6250876396 + ], + [ + -116.4317040836, + 43.6250997138 + ], + [ + -116.4317107891, + 43.6250997138 + ], + [ + -116.4327434396, + 43.6251077227 + ], + [ + -116.4327997659, + 43.6251090576 + ], + [ + -116.4328654801, + 43.6251090576 + ] + ] + } + }, + "data": { + "description": "Stationary traffic", + "event_code": 101, + "from": "North Morello Avenue", + "to": "North Ten Mile Road", + "magnitude_of_delay": 3, + "icon_category": 6, + "length": 295.2529502763, + "delay": 184, + "road_numbers": [], + "start_time": "2026-06-28T03:27:30Z", + "end_time": "2026-06-28T04:03:00Z", + "time_validity": "present", + "state_code": "ID", + "bbox_name": "treasure_valley_ext", + "latitude": 43.62507957, + "longitude": -116.4291975592, + "_enriched": { + "geocoder": { + "name": null, + "city": "Meridian", + "county": "Ada", + "state": "ID", + "country": "United States", + "postal_code": "83646", + "timezone": "America/Boise", + "landclass": null, + "elevation_m": 781.5 + } + } + } + } + }, + "subject": "central.traffic.incident.id", + "captured_epoch": 1783206527 +} \ No newline at end of file diff --git a/work/tests/fixtures/traffic/0036.json b/work/tests/fixtures/traffic/0036.json new file mode 100644 index 0000000..abb7de4 --- /dev/null +++ b/work/tests/fixtures/traffic/0036.json @@ -0,0 +1,86 @@ +{ + "envelope": { + "id": "ID:tomtom:TTI-4ee2bdba-b112-4223-894d-2b10d1a81de0-TTL14813270316014000", + "source": "central.echo6.co", + "type": "central.incident.tomtom_incidents.v1", + "time": "2026-06-28T03:33:30+00:00", + "datacontenttype": "application/json", + "centralschemaversion": "1.0", + "centralcategory": "incident.tomtom_incidents", + "centralseverity": 3, + "specversion": "1.0", + "data": { + "id": "ID:tomtom:TTI-4ee2bdba-b112-4223-894d-2b10d1a81de0-TTL14813270316014000", + "adapter": "tomtom_incidents", + "category": "incident.tomtom_incidents", + "time": "2026-06-28T03:33:30Z", + "expires": "2026-06-28T04:03:00Z", + "severity": 3, + "geo": { + "centroid": [ + -116.2734631394, + 43.6507000468 + ], + "bbox": null, + "regions": [ + "US-ID" + ], + "primary_region": "US-ID", + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -116.2734631394, + 43.6507000468 + ], + [ + -116.2735234891, + 43.6507321904 + ], + [ + -116.2736079787, + 43.6507402566 + ], + [ + -116.2745024954, + 43.6507362538 + ] + ] + } + }, + "data": { + "description": "Stationary traffic", + "event_code": 101, + "from": "North Paddock Drive", + "to": "Kent Lane", + "magnitude_of_delay": 3, + "icon_category": 6, + "length": 84.8566896918, + "delay": 62, + "road_numbers": [], + "start_time": "2026-06-28T03:33:30Z", + "end_time": "2026-06-28T04:03:00Z", + "time_validity": "present", + "state_code": "ID", + "bbox_name": "treasure_valley_ext", + "latitude": 43.6507000468, + "longitude": -116.2734631394, + "_enriched": { + "geocoder": { + "name": "West Paddock Drive", + "city": null, + "county": "Ada", + "state": "Idaho", + "country": "United States", + "postal_code": "83704", + "timezone": "America/Boise", + "landclass": "Expo Idaho", + "elevation_m": 799.83984375 + } + } + } + } + }, + "subject": "central.traffic.incident.id", + "captured_epoch": 1783206527 +} \ No newline at end of file diff --git a/work/tests/fixtures/traffic/0037.json b/work/tests/fixtures/traffic/0037.json new file mode 100644 index 0000000..7af2838 --- /dev/null +++ b/work/tests/fixtures/traffic/0037.json @@ -0,0 +1,110 @@ +{ + "envelope": { + "id": "ID:tomtom:TTI-4ee2bdba-b112-4223-894d-2b10d1a81de0-TTR14830582680011000", + "source": "central.echo6.co", + "type": "central.incident.tomtom_incidents.v1", + "time": "2026-06-28T03:13:30+00:00", + "datacontenttype": "application/json", + "centralschemaversion": "1.0", + "centralcategory": "incident.tomtom_incidents", + "centralseverity": 4, + "specversion": "1.0", + "data": { + "id": "ID:tomtom:TTI-4ee2bdba-b112-4223-894d-2b10d1a81de0-TTR14830582680011000", + "adapter": "tomtom_incidents", + "category": "incident.tomtom_incidents", + "time": "2026-06-28T03:13:30Z", + "expires": null, + "severity": 4, + "geo": { + "centroid": [ + -116.2239254211, + 43.6204782359 + ], + "bbox": null, + "regions": [ + "US-ID" + ], + "primary_region": "US-ID", + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -116.2239254211, + 43.6204782359 + ], + [ + -116.2239066456, + 43.6203562109 + ], + [ + -116.2239026223, + 43.620311976 + ], + [ + -116.2238758002, + 43.6200852793 + ], + [ + -116.223873118, + 43.620059794 + ], + [ + -116.2238530014, + 43.6198867975 + ], + [ + -116.2238489781, + 43.6198533025 + ], + [ + -116.2238462959, + 43.6198291522 + ], + [ + -116.2238154505, + 43.6195823686 + ], + [ + -116.2238114272, + 43.619543473 + ] + ] + } + }, + "data": { + "description": "Closed", + "event_code": 401, + "from": "West Fairview Avenue", + "to": "West Shoreline Drive / Fletcher Street", + "magnitude_of_delay": 4, + "icon_category": 8, + "length": 104.3509495607, + "delay": null, + "road_numbers": [], + "start_time": "2026-06-28T03:13:30Z", + "end_time": null, + "time_validity": "present", + "state_code": "ID", + "bbox_name": "treasure_valley_ext", + "latitude": 43.6204782359, + "longitude": -116.2239254211, + "_enriched": { + "geocoder": { + "name": null, + "city": "Boise", + "county": "Ada", + "state": "ID", + "country": "United States", + "postal_code": "83702", + "timezone": "America/Boise", + "landclass": null, + "elevation_m": 815.49609375 + } + } + } + } + }, + "subject": "central.traffic.incident.id", + "captured_epoch": 1783206527 +} \ No newline at end of file diff --git a/work/tests/fixtures/traffic/0038.json b/work/tests/fixtures/traffic/0038.json new file mode 100644 index 0000000..2e49271 --- /dev/null +++ b/work/tests/fixtures/traffic/0038.json @@ -0,0 +1,82 @@ +{ + "envelope": { + "id": "ID:tomtom:TTI-4ee2bdba-b112-4223-894d-2b10d1a81de0-TTR14951767488044001", + "source": "central.echo6.co", + "type": "central.incident.tomtom_incidents.v1", + "time": "2026-05-27T14:05:30+00:00", + "datacontenttype": "application/json", + "centralschemaversion": "1.0", + "centralcategory": "incident.tomtom_incidents", + "centralseverity": 1, + "specversion": "1.0", + "data": { + "id": "ID:tomtom:TTI-4ee2bdba-b112-4223-894d-2b10d1a81de0-TTR14951767488044001", + "adapter": "tomtom_incidents", + "category": "incident.tomtom_incidents", + "time": "2026-05-27T14:05:30Z", + "expires": null, + "severity": 1, + "geo": { + "centroid": [ + -116.0401029084, + 43.3074604053 + ], + "bbox": null, + "regions": [ + "US-ID" + ], + "primary_region": "US-ID", + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -116.0401029084, + 43.3074604053 + ], + [ + -116.0422030781, + 43.30674159 + ], + [ + -116.0451803301, + 43.3054997305 + ] + ] + } + }, + "data": { + "description": "Roadworks", + "event_code": 701, + "from": "East Monroe Avenue", + "to": "E Humvee Lane", + "magnitude_of_delay": 0, + "icon_category": 9, + "length": 465.4730178013, + "delay": null, + "road_numbers": [], + "start_time": "2026-05-27T14:05:30Z", + "end_time": null, + "time_validity": "present", + "state_code": "ID", + "bbox_name": "treasure_valley_ext", + "latitude": 43.3074604053, + "longitude": -116.0401029084, + "_enriched": { + "geocoder": { + "name": null, + "city": null, + "county": null, + "state": null, + "country": null, + "postal_code": null, + "timezone": "America/Boise", + "landclass": null, + "elevation_m": 956.5390625 + } + } + } + } + }, + "subject": "central.traffic.incident.id", + "captured_epoch": 1783206527 +} \ No newline at end of file diff --git a/work/tests/fixtures/traffic/0039.json b/work/tests/fixtures/traffic/0039.json new file mode 100644 index 0000000..7bda334 --- /dev/null +++ b/work/tests/fixtures/traffic/0039.json @@ -0,0 +1,106 @@ +{ + "envelope": { + "id": "ID:tomtom:TTI-4ee2bdba-b112-4223-894d-2b10d1a81de0-TTL15211454176023000", + "source": "central.echo6.co", + "type": "central.incident.tomtom_incidents.v1", + "time": "2026-06-28T04:11:00+00:00", + "datacontenttype": "application/json", + "centralschemaversion": "1.0", + "centralcategory": "incident.tomtom_incidents", + "centralseverity": 3, + "specversion": "1.0", + "data": { + "id": "ID:tomtom:TTI-4ee2bdba-b112-4223-894d-2b10d1a81de0-TTL15211454176023000", + "adapter": "tomtom_incidents", + "category": "incident.tomtom_incidents", + "time": "2026-06-28T04:11:00Z", + "expires": "2026-06-28T04:33:00Z", + "severity": 3, + "geo": { + "centroid": [ + -115.3002893122, + 42.9539855173 + ], + "bbox": null, + "regions": [ + "US-ID" + ], + "primary_region": "US-ID", + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -115.3002893122, + 42.9539855173 + ], + [ + -115.3000988754, + 42.9535120988 + ], + [ + -115.3000090214, + 42.9533136386 + ], + [ + -115.3000063392, + 42.95330824 + ], + [ + -115.2999929281, + 42.9532800813 + ], + [ + -115.2999124618, + 42.9531137668 + ], + [ + -115.2999057563, + 42.9530963439 + ], + [ + -115.2997542115, + 42.9527583761 + ], + [ + -115.2995691391, + 42.9523466651 + ] + ] + } + }, + "data": { + "description": "Stationary traffic", + "event_code": 101, + "from": "East 1st Avenue / West 1st Avenue", + "to": "Shrum Avenue", + "magnitude_of_delay": 3, + "icon_category": 6, + "length": 191.4564024773, + "delay": 184, + "road_numbers": [], + "start_time": "2026-06-28T04:11:00Z", + "end_time": "2026-06-28T04:33:00Z", + "time_validity": "present", + "state_code": "ID", + "bbox_name": "mountain_home_corridor", + "latitude": 42.9539855173, + "longitude": -115.3002893122, + "_enriched": { + "geocoder": { + "name": null, + "city": "Glenns Ferry", + "county": "Elmore", + "state": "ID", + "country": "United States", + "postal_code": "83623", + "timezone": "America/Boise", + "landclass": null, + "elevation_m": 780.3828125 + } + } + } + } + }, + "subject": "central.traffic.incident.id", + "captured_epoch": 1783206527 +} \ No newline at end of file diff --git a/work/tests/fixtures/traffic_last/0000.json b/work/tests/fixtures/traffic_last/0000.json new file mode 100644 index 0000000..a762bf1 --- /dev/null +++ b/work/tests/fixtures/traffic_last/0000.json @@ -0,0 +1,2679 @@ +{ + "envelope": { + "id": "idaho_511:event:11466", + "source": "central.echo6.co", + "type": "central.special_event.itd_511.v1", + "time": "2026-06-30T17:12:10+00:00", + "datacontenttype": "application/json", + "centralschemaversion": "1.0", + "centralcategory": "special_event.itd_511", + "centralseverity": 1, + "specversion": "1.0", + "data": { + "id": "idaho_511:event:11466", + "adapter": "itd_511", + "category": "special_event.itd_511", + "time": "2026-06-30T17:12:10Z", + "expires": "2026-09-12T22:00:00Z", + "severity": 1, + "geo": { + "centroid": [ + -111.29749, + 42.32204 + ], + "bbox": null, + "regions": [ + "US-ID" + ], + "primary_region": "US-ID", + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -111.29749, + 42.32204 + ], + [ + -111.29645, + 42.32204 + ], + [ + -111.29479, + 42.32205 + ], + [ + -111.29333, + 42.32205 + ], + [ + -111.29279, + 42.32209 + ], + [ + -111.29268, + 42.32211 + ], + [ + -111.29224, + 42.32221 + ], + [ + -111.29182, + 42.3223 + ], + [ + -111.29137, + 42.3224 + ], + [ + -111.29091, + 42.32251 + ], + [ + -111.28793, + 42.32317 + ], + [ + -111.2869, + 42.32339 + ], + [ + -111.28563, + 42.32368 + ], + [ + -111.2853, + 42.32375 + ], + [ + -111.28485, + 42.32384 + ], + [ + -111.28433, + 42.32393 + ], + [ + -111.28404, + 42.32396 + ], + [ + -111.28355, + 42.32399 + ], + [ + -111.28297, + 42.32397 + ], + [ + -111.2826, + 42.32394 + ], + [ + -111.28223, + 42.32389 + ], + [ + -111.28184, + 42.32381 + ], + [ + -111.28142, + 42.32371 + ], + [ + -111.28096, + 42.32357 + ], + [ + -111.27705, + 42.32225 + ], + [ + -111.27558, + 42.32175 + ], + [ + -111.27512, + 42.3216 + ], + [ + -111.27467, + 42.3215 + ], + [ + -111.27435, + 42.32144 + ], + [ + -111.27387, + 42.3214 + ], + [ + -111.27316, + 42.32138 + ], + [ + -111.27127, + 42.32137 + ], + [ + -111.27023, + 42.32137 + ], + [ + -111.26902, + 42.32135 + ], + [ + -111.26749, + 42.32133 + ], + [ + -111.26579, + 42.32127 + ], + [ + -111.26512, + 42.32124 + ], + [ + -111.26448, + 42.3212 + ], + [ + -111.26384, + 42.32115 + ], + [ + -111.26262, + 42.32104 + ], + [ + -111.26178, + 42.32097 + ], + [ + -111.26095, + 42.32089 + ], + [ + -111.25941, + 42.32073 + ], + [ + -111.2587, + 42.32067 + ], + [ + -111.2581, + 42.32064 + ], + [ + -111.25773, + 42.32064 + ], + [ + -111.2575, + 42.32065 + ], + [ + -111.25684, + 42.3207 + ], + [ + -111.25531, + 42.32084 + ], + [ + -111.25348, + 42.32101 + ], + [ + -111.2531, + 42.32105 + ], + [ + -111.25296, + 42.32107 + ], + [ + -111.25285, + 42.32109 + ], + [ + -111.25271, + 42.32112 + ], + [ + -111.25259, + 42.32115 + ], + [ + -111.25245, + 42.32119 + ], + [ + -111.25233, + 42.32123 + ], + [ + -111.25222, + 42.32127 + ], + [ + -111.25212, + 42.32131 + ], + [ + -111.25155, + 42.32156 + ], + [ + -111.25051, + 42.32203 + ], + [ + -111.24956, + 42.32247 + ], + [ + -111.24858, + 42.32292 + ], + [ + -111.24419, + 42.32493 + ], + [ + -111.24394, + 42.32505 + ], + [ + -111.24372, + 42.32516 + ], + [ + -111.24349, + 42.32528 + ], + [ + -111.24322, + 42.32543 + ], + [ + -111.24294, + 42.3256 + ], + [ + -111.24261, + 42.32581 + ], + [ + -111.24236, + 42.32598 + ], + [ + -111.24214, + 42.32614 + ], + [ + -111.24196, + 42.32628 + ], + [ + -111.2417, + 42.32649 + ], + [ + -111.24146, + 42.3267 + ], + [ + -111.24121, + 42.32693 + ], + [ + -111.2411, + 42.32704 + ], + [ + -111.24097, + 42.32718 + ], + [ + -111.24087, + 42.3273 + ], + [ + -111.24034, + 42.32798 + ], + [ + -111.24023, + 42.32811 + ], + [ + -111.24016, + 42.32819 + ], + [ + -111.2401, + 42.32825 + ], + [ + -111.23999, + 42.32835 + ], + [ + -111.23946, + 42.32882 + ], + [ + -111.23909, + 42.32914 + ], + [ + -111.23886, + 42.32933 + ], + [ + -111.23874, + 42.32942 + ], + [ + -111.23859, + 42.32952 + ], + [ + -111.23846, + 42.3296 + ], + [ + -111.23834, + 42.32967 + ], + [ + -111.23824, + 42.32972 + ], + [ + -111.23815, + 42.32976 + ], + [ + -111.238, + 42.32982 + ], + [ + -111.23784, + 42.32988 + ], + [ + -111.23769, + 42.32993 + ], + [ + -111.23752, + 42.32998 + ], + [ + -111.23736, + 42.33002 + ], + [ + -111.23695, + 42.33011 + ], + [ + -111.23679, + 42.33015 + ], + [ + -111.23669, + 42.33018 + ], + [ + -111.23651, + 42.33024 + ], + [ + -111.23624, + 42.33034 + ], + [ + -111.23588, + 42.33048 + ], + [ + -111.23512, + 42.33077 + ], + [ + -111.22917, + 42.33299 + ], + [ + -111.22749, + 42.33361 + ], + [ + -111.22716, + 42.33373 + ], + [ + -111.22643, + 42.33399 + ], + [ + -111.22616, + 42.33408 + ], + [ + -111.22593, + 42.33415 + ], + [ + -111.22572, + 42.33421 + ], + [ + -111.22553, + 42.33426 + ], + [ + -111.22532, + 42.33431 + ], + [ + -111.22503, + 42.33437 + ], + [ + -111.22463, + 42.33444 + ], + [ + -111.22441, + 42.33447 + ], + [ + -111.22424, + 42.33449 + ], + [ + -111.22404, + 42.33451 + ], + [ + -111.22354, + 42.33455 + ], + [ + -111.22219, + 42.33465 + ], + [ + -111.22201, + 42.33467 + ], + [ + -111.22181, + 42.3347 + ], + [ + -111.22165, + 42.33473 + ], + [ + -111.22152, + 42.33476 + ], + [ + -111.22134, + 42.33481 + ], + [ + -111.22119, + 42.33486 + ], + [ + -111.22106, + 42.33491 + ], + [ + -111.22089, + 42.33498 + ], + [ + -111.22078, + 42.33503 + ], + [ + -111.22067, + 42.33509 + ], + [ + -111.2205, + 42.33519 + ], + [ + -111.22035, + 42.33529 + ], + [ + -111.22027, + 42.33535 + ], + [ + -111.22022, + 42.33539 + ], + [ + -111.22011, + 42.33549 + ], + [ + -111.22005, + 42.33555 + ], + [ + -111.21992, + 42.33569 + ], + [ + -111.21982, + 42.33581 + ], + [ + -111.21947, + 42.33627 + ], + [ + -111.21938, + 42.33638 + ], + [ + -111.21931, + 42.33646 + ], + [ + -111.21922, + 42.33655 + ], + [ + -111.21913, + 42.33663 + ], + [ + -111.21887, + 42.33683 + ], + [ + -111.21875, + 42.33691 + ], + [ + -111.21864, + 42.33698 + ], + [ + -111.21853, + 42.33704 + ], + [ + -111.21845, + 42.33708 + ], + [ + -111.21834, + 42.33713 + ], + [ + -111.21822, + 42.33718 + ], + [ + -111.21808, + 42.33723 + ], + [ + -111.21792, + 42.33728 + ], + [ + -111.21778, + 42.33732 + ], + [ + -111.21762, + 42.33736 + ], + [ + -111.21752, + 42.33738 + ], + [ + -111.2174, + 42.3374 + ], + [ + -111.21726, + 42.33742 + ], + [ + -111.21716, + 42.33743 + ], + [ + -111.21696, + 42.33744 + ], + [ + -111.21661, + 42.33744 + ], + [ + -111.21605, + 42.33742 + ], + [ + -111.21549, + 42.33742 + ], + [ + -111.21529, + 42.33743 + ], + [ + -111.21519, + 42.33744 + ], + [ + -111.21512, + 42.33745 + ], + [ + -111.21494, + 42.33748 + ], + [ + -111.21477, + 42.33752 + ], + [ + -111.21462, + 42.33756 + ], + [ + -111.21446, + 42.33761 + ], + [ + -111.21432, + 42.33766 + ], + [ + -111.21415, + 42.33773 + ], + [ + -111.21402, + 42.33779 + ], + [ + -111.21385, + 42.33788 + ], + [ + -111.21373, + 42.33795 + ], + [ + -111.21364, + 42.33801 + ], + [ + -111.21353, + 42.33809 + ], + [ + -111.2134, + 42.3382 + ], + [ + -111.21329, + 42.3383 + ], + [ + -111.21317, + 42.33842 + ], + [ + -111.21301, + 42.33859 + ], + [ + -111.21279, + 42.33883 + ], + [ + -111.21137, + 42.3404 + ], + [ + -111.21125, + 42.34052 + ], + [ + -111.21108, + 42.34068 + ], + [ + -111.21099, + 42.34076 + ], + [ + -111.21073, + 42.34097 + ], + [ + -111.21044, + 42.34119 + ], + [ + -111.2096, + 42.34179 + ], + [ + -111.20859, + 42.34253 + ], + [ + -111.20802, + 42.34294 + ], + [ + -111.2076, + 42.34324 + ], + [ + -111.20727, + 42.34347 + ], + [ + -111.20699, + 42.34366 + ], + [ + -111.20682, + 42.34377 + ], + [ + -111.20665, + 42.34387 + ], + [ + -111.20652, + 42.34394 + ], + [ + -111.20628, + 42.34406 + ], + [ + -111.20613, + 42.34413 + ], + [ + -111.20592, + 42.34422 + ], + [ + -111.20571, + 42.3443 + ], + [ + -111.20545, + 42.34439 + ], + [ + -111.20508, + 42.3445 + ], + [ + -111.20472, + 42.34459 + ], + [ + -111.20442, + 42.34465 + ], + [ + -111.20412, + 42.3447 + ], + [ + -111.20388, + 42.34473 + ], + [ + -111.20368, + 42.34475 + ], + [ + -111.20341, + 42.34477 + ], + [ + -111.20315, + 42.34478 + ], + [ + -111.20289, + 42.34478 + ], + [ + -111.2026, + 42.34477 + ], + [ + -111.20242, + 42.34476 + ], + [ + -111.20218, + 42.34474 + ], + [ + -111.20193, + 42.34471 + ], + [ + -111.20166, + 42.34467 + ], + [ + -111.20143, + 42.34463 + ], + [ + -111.20119, + 42.34458 + ], + [ + -111.20098, + 42.34453 + ], + [ + -111.2006, + 42.34443 + ], + [ + -111.19962, + 42.34415 + ], + [ + -111.1993, + 42.34407 + ], + [ + -111.19907, + 42.34402 + ], + [ + -111.19882, + 42.34398 + ], + [ + -111.19858, + 42.34396 + ], + [ + -111.19836, + 42.34395 + ], + [ + -111.19815, + 42.34395 + ], + [ + -111.19795, + 42.34396 + ], + [ + -111.19784, + 42.34397 + ], + [ + -111.19767, + 42.34399 + ], + [ + -111.19754, + 42.34401 + ], + [ + -111.19734, + 42.34405 + ], + [ + -111.19709, + 42.34411 + ], + [ + -111.19678, + 42.3442 + ], + [ + -111.19629, + 42.34435 + ], + [ + -111.19594, + 42.34445 + ], + [ + -111.19577, + 42.34449 + ], + [ + -111.19555, + 42.34453 + ], + [ + -111.19538, + 42.34455 + ], + [ + -111.19507, + 42.34457 + ], + [ + -111.1948, + 42.34457 + ], + [ + -111.19447, + 42.34455 + ], + [ + -111.19413, + 42.34452 + ], + [ + -111.19388, + 42.3445 + ], + [ + -111.1935, + 42.34446 + ], + [ + -111.19326, + 42.34443 + ], + [ + -111.19313, + 42.34441 + ], + [ + -111.19293, + 42.34437 + ], + [ + -111.19276, + 42.34433 + ], + [ + -111.19259, + 42.34428 + ], + [ + -111.19234, + 42.34419 + ], + [ + -111.1921, + 42.34409 + ], + [ + -111.19195, + 42.34402 + ], + [ + -111.19173, + 42.3439 + ], + [ + -111.19158, + 42.34381 + ], + [ + -111.19144, + 42.34371 + ], + [ + -111.1913, + 42.3436 + ], + [ + -111.19114, + 42.34346 + ], + [ + -111.19071, + 42.34305 + ], + [ + -111.19007, + 42.34243 + ], + [ + -111.18924, + 42.34166 + ], + [ + -111.18896, + 42.34141 + ], + [ + -111.18872, + 42.34123 + ], + [ + -111.1884, + 42.34101 + ], + [ + -111.1881, + 42.34085 + ], + [ + -111.1879, + 42.34073 + ], + [ + -111.18763, + 42.34062 + ], + [ + -111.18713, + 42.34045 + ], + [ + -111.1868, + 42.34036 + ], + [ + -111.18638, + 42.34027 + ], + [ + -111.18601, + 42.34022 + ], + [ + -111.18565, + 42.3402 + ], + [ + -111.18541, + 42.34019 + ], + [ + -111.18519, + 42.34019 + ], + [ + -111.18499, + 42.3402 + ], + [ + -111.18486, + 42.34021 + ], + [ + -111.18445, + 42.34026 + ], + [ + -111.18425, + 42.34029 + ], + [ + -111.18404, + 42.34033 + ], + [ + -111.18383, + 42.34038 + ], + [ + -111.18365, + 42.34043 + ], + [ + -111.18342, + 42.3405 + ], + [ + -111.1832, + 42.34058 + ], + [ + -111.18296, + 42.34068 + ], + [ + -111.18255, + 42.34087 + ], + [ + -111.17951, + 42.34224 + ], + [ + -111.17917, + 42.34238 + ], + [ + -111.17894, + 42.34246 + ], + [ + -111.17871, + 42.34253 + ], + [ + -111.17849, + 42.34258 + ], + [ + -111.17821, + 42.34263 + ], + [ + -111.17804, + 42.34265 + ], + [ + -111.1779, + 42.34266 + ], + [ + -111.17764, + 42.34267 + ], + [ + -111.17742, + 42.34267 + ], + [ + -111.17716, + 42.34266 + ], + [ + -111.17697, + 42.34264 + ], + [ + -111.17682, + 42.34262 + ], + [ + -111.17666, + 42.34259 + ], + [ + -111.17653, + 42.34256 + ], + [ + -111.17638, + 42.34252 + ], + [ + -111.17624, + 42.34248 + ], + [ + -111.17609, + 42.34243 + ], + [ + -111.17593, + 42.34237 + ], + [ + -111.17576, + 42.3423 + ], + [ + -111.17565, + 42.34225 + ], + [ + -111.17553, + 42.34219 + ], + [ + -111.17537, + 42.3421 + ], + [ + -111.17524, + 42.34202 + ], + [ + -111.1751, + 42.34193 + ], + [ + -111.17495, + 42.34183 + ], + [ + -111.17453, + 42.34154 + ], + [ + -111.17435, + 42.34142 + ], + [ + -111.17406, + 42.34124 + ], + [ + -111.17387, + 42.34113 + ], + [ + -111.17371, + 42.34104 + ], + [ + -111.17348, + 42.34092 + ], + [ + -111.17323, + 42.3408 + ], + [ + -111.17305, + 42.34072 + ], + [ + -111.17288, + 42.34065 + ], + [ + -111.17267, + 42.34057 + ], + [ + -111.17241, + 42.34048 + ], + [ + -111.17215, + 42.3404 + ], + [ + -111.17198, + 42.34035 + ], + [ + -111.17183, + 42.34031 + ], + [ + -111.17167, + 42.34027 + ], + [ + -111.17067, + 42.34004 + ], + [ + -111.16948, + 42.33978 + ], + [ + -111.16752, + 42.33934 + ], + [ + -111.16711, + 42.33925 + ], + [ + -111.16636, + 42.33908 + ], + [ + -111.16476, + 42.33871 + ], + [ + -111.16418, + 42.33857 + ], + [ + -111.1637, + 42.33845 + ], + [ + -111.16346, + 42.33838 + ], + [ + -111.16318, + 42.33829 + ], + [ + -111.16294, + 42.3382 + ], + [ + -111.16239, + 42.33796 + ], + [ + -111.16214, + 42.33783 + ], + [ + -111.16183, + 42.33764 + ], + [ + -111.16125, + 42.3372 + ], + [ + -111.16095, + 42.33694 + ], + [ + -111.16075, + 42.33673 + ], + [ + -111.16056, + 42.3365 + ], + [ + -111.16033, + 42.33617 + ], + [ + -111.16007, + 42.3357 + ], + [ + -111.15978, + 42.33507 + ], + [ + -111.15955, + 42.3346 + ], + [ + -111.15934, + 42.33427 + ], + [ + -111.15905, + 42.3339 + ], + [ + -111.15893, + 42.33378 + ], + [ + -111.15863, + 42.33352 + ], + [ + -111.15818, + 42.33322 + ], + [ + -111.15766, + 42.33296 + ], + [ + -111.15717, + 42.33278 + ], + [ + -111.15693, + 42.33271 + ], + [ + -111.1567, + 42.33265 + ], + [ + -111.15637, + 42.3326 + ], + [ + -111.15596, + 42.33255 + ], + [ + -111.15514, + 42.33255 + ], + [ + -111.15456, + 42.3326 + ], + [ + -111.15411, + 42.33265 + ], + [ + -111.15283, + 42.33278 + ], + [ + -111.15218, + 42.33287 + ], + [ + -111.15151, + 42.33298 + ], + [ + -111.15074, + 42.33315 + ], + [ + -111.14989, + 42.33335 + ], + [ + -111.14939, + 42.33346 + ], + [ + -111.14847, + 42.33367 + ], + [ + -111.14742, + 42.33391 + ], + [ + -111.14621, + 42.33419 + ], + [ + -111.14603, + 42.33423 + ], + [ + -111.14574, + 42.33429 + ], + [ + -111.14553, + 42.33433 + ], + [ + -111.14534, + 42.33436 + ], + [ + -111.14511, + 42.33439 + ], + [ + -111.1449, + 42.33441 + ], + [ + -111.14475, + 42.33442 + ], + [ + -111.14444, + 42.33443 + ], + [ + -111.14418, + 42.33443 + ], + [ + -111.14396, + 42.33442 + ], + [ + -111.14367, + 42.3344 + ], + [ + -111.14342, + 42.33437 + ], + [ + -111.14321, + 42.33434 + ], + [ + -111.14303, + 42.33431 + ], + [ + -111.14283, + 42.33427 + ], + [ + -111.14262, + 42.33422 + ], + [ + -111.14243, + 42.33417 + ], + [ + -111.14226, + 42.33412 + ], + [ + -111.14211, + 42.33407 + ], + [ + -111.14197, + 42.33402 + ], + [ + -111.14179, + 42.33395 + ], + [ + -111.14163, + 42.33388 + ], + [ + -111.14146, + 42.3338 + ], + [ + -111.1413, + 42.33372 + ], + [ + -111.14103, + 42.33357 + ], + [ + -111.14088, + 42.33348 + ], + [ + -111.14072, + 42.33338 + ], + [ + -111.14056, + 42.33327 + ], + [ + -111.14044, + 42.33318 + ], + [ + -111.14029, + 42.33306 + ], + [ + -111.14019, + 42.33297 + ], + [ + -111.14011, + 42.33289 + ], + [ + -111.13954, + 42.33229 + ], + [ + -111.13908, + 42.33179 + ], + [ + -111.13888, + 42.33158 + ], + [ + -111.13877, + 42.33147 + ], + [ + -111.13867, + 42.33138 + ], + [ + -111.13854, + 42.33127 + ], + [ + -111.13836, + 42.33113 + ], + [ + -111.13823, + 42.33104 + ], + [ + -111.13814, + 42.33098 + ], + [ + -111.13804, + 42.33092 + ], + [ + -111.13795, + 42.33087 + ], + [ + -111.13783, + 42.33081 + ], + [ + -111.1377, + 42.33075 + ], + [ + -111.13753, + 42.33068 + ], + [ + -111.13739, + 42.33063 + ], + [ + -111.13724, + 42.33058 + ], + [ + -111.13707, + 42.33053 + ], + [ + -111.13691, + 42.33049 + ], + [ + -111.13673, + 42.33045 + ], + [ + -111.13662, + 42.33043 + ], + [ + -111.13648, + 42.33041 + ], + [ + -111.13631, + 42.33039 + ], + [ + -111.13618, + 42.33038 + ], + [ + -111.13595, + 42.33037 + ], + [ + -111.1358, + 42.33037 + ], + [ + -111.13558, + 42.33038 + ], + [ + -111.13548, + 42.33039 + ], + [ + -111.13531, + 42.33041 + ], + [ + -111.13511, + 42.33044 + ], + [ + -111.13491, + 42.33048 + ], + [ + -111.13471, + 42.33053 + ], + [ + -111.1345, + 42.33059 + ], + [ + -111.13429, + 42.33066 + ], + [ + -111.13419, + 42.3307 + ], + [ + -111.13404, + 42.33077 + ], + [ + -111.1339, + 42.33084 + ], + [ + -111.13379, + 42.3309 + ], + [ + -111.13362, + 42.331 + ], + [ + -111.1335, + 42.33108 + ], + [ + -111.13338, + 42.33117 + ], + [ + -111.1332, + 42.33132 + ], + [ + -111.13309, + 42.33142 + ], + [ + -111.13297, + 42.33154 + ], + [ + -111.13279, + 42.33174 + ], + [ + -111.13254, + 42.33203 + ], + [ + -111.1322, + 42.33242 + ], + [ + -111.13084, + 42.33399 + ], + [ + -111.12999, + 42.33496 + ], + [ + -111.12962, + 42.33537 + ], + [ + -111.12944, + 42.33556 + ], + [ + -111.12934, + 42.33566 + ], + [ + -111.12921, + 42.33578 + ], + [ + -111.12898, + 42.33598 + ], + [ + -111.12878, + 42.33615 + ], + [ + -111.12858, + 42.33631 + ], + [ + -111.12842, + 42.33643 + ], + [ + -111.12822, + 42.33657 + ], + [ + -111.12801, + 42.33671 + ], + [ + -111.12779, + 42.33685 + ], + [ + -111.12748, + 42.33704 + ], + [ + -111.12712, + 42.33725 + ], + [ + -111.12668, + 42.3375 + ], + [ + -111.12565, + 42.33808 + ], + [ + -111.12522, + 42.33833 + ], + [ + -111.12495, + 42.33849 + ], + [ + -111.12472, + 42.33863 + ], + [ + -111.12455, + 42.33874 + ], + [ + -111.12438, + 42.33886 + ], + [ + -111.12397, + 42.33917 + ], + [ + -111.12381, + 42.3393 + ], + [ + -111.12368, + 42.33941 + ], + [ + -111.1235, + 42.33957 + ], + [ + -111.12334, + 42.33972 + ], + [ + -111.1231, + 42.33996 + ], + [ + -111.12286, + 42.34021 + ], + [ + -111.12273, + 42.34035 + ], + [ + -111.12265, + 42.34044 + ], + [ + -111.12257, + 42.34054 + ], + [ + -111.12248, + 42.34066 + ], + [ + -111.12235, + 42.34084 + ], + [ + -111.12221, + 42.34104 + ], + [ + -111.12204, + 42.34129 + ], + [ + -111.12177, + 42.3417 + ], + [ + -111.12107, + 42.34275 + ], + [ + -111.12092, + 42.34298 + ], + [ + -111.12079, + 42.34316 + ], + [ + -111.12069, + 42.34329 + ], + [ + -111.12058, + 42.34342 + ], + [ + -111.12048, + 42.34353 + ], + [ + -111.12042, + 42.34359 + ], + [ + -111.12031, + 42.34369 + ], + [ + -111.12013, + 42.34384 + ], + [ + -111.11995, + 42.34398 + ], + [ + -111.11976, + 42.34412 + ], + [ + -111.11959, + 42.34423 + ], + [ + -111.11942, + 42.34433 + ], + [ + -111.11922, + 42.34444 + ], + [ + -111.11902, + 42.34454 + ], + [ + -111.11887, + 42.34461 + ], + [ + -111.11873, + 42.34467 + ], + [ + -111.11858, + 42.34473 + ], + [ + -111.11844, + 42.34478 + ], + [ + -111.1183, + 42.34483 + ], + [ + -111.11817, + 42.34487 + ], + [ + -111.11794, + 42.34493 + ], + [ + -111.11772, + 42.34498 + ], + [ + -111.11743, + 42.34504 + ], + [ + -111.11714, + 42.34509 + ], + [ + -111.11693, + 42.34512 + ], + [ + -111.11673, + 42.34514 + ], + [ + -111.11656, + 42.34515 + ], + [ + -111.11632, + 42.34516 + ], + [ + -111.11579, + 42.34517 + ], + [ + -111.11403, + 42.34519 + ], + [ + -111.11268, + 42.3452 + ], + [ + -111.11204, + 42.34521 + ], + [ + -111.11174, + 42.34522 + ], + [ + -111.11157, + 42.34523 + ], + [ + -111.11135, + 42.34525 + ], + [ + -111.11098, + 42.3453 + ], + [ + -111.11064, + 42.34536 + ], + [ + -111.11042, + 42.34541 + ], + [ + -111.11012, + 42.34549 + ], + [ + -111.10989, + 42.34556 + ], + [ + -111.10983, + 42.34558 + ], + [ + -111.10956, + 42.34568 + ], + [ + -111.10941, + 42.34574 + ], + [ + -111.10908, + 42.34588 + ], + [ + -111.10806, + 42.34636 + ], + [ + -111.10702, + 42.34682 + ], + [ + -111.10634, + 42.34713 + ], + [ + -111.10337, + 42.34853 + ], + [ + -111.09874, + 42.3507 + ], + [ + -111.09584, + 42.35205 + ], + [ + -111.09469, + 42.35259 + ], + [ + -111.09415, + 42.35284 + ], + [ + -111.09405, + 42.35288 + ], + [ + -111.08919, + 42.35515 + ], + [ + -111.08911, + 42.35519 + ], + [ + -111.08756, + 42.35591 + ], + [ + -111.08604, + 42.3566 + ], + [ + -111.08527, + 42.35688 + ], + [ + -111.0851, + 42.35693 + ], + [ + -111.08448, + 42.35714 + ], + [ + -111.08361, + 42.35742 + ], + [ + -111.08267, + 42.35767 + ], + [ + -111.08152, + 42.35793 + ], + [ + -111.08053, + 42.35811 + ], + [ + -111.07943, + 42.35827 + ], + [ + -111.07876, + 42.35835 + ], + [ + -111.07805, + 42.35841 + ], + [ + -111.07732, + 42.35845 + ], + [ + -111.07668, + 42.35847 + ], + [ + -111.0751, + 42.35849 + ], + [ + -111.07373, + 42.35849 + ], + [ + -111.07184, + 42.35848 + ], + [ + -111.06963, + 42.35848 + ], + [ + -111.06878, + 42.35849 + ], + [ + -111.06563, + 42.35849 + ], + [ + -111.06474, + 42.35848 + ], + [ + -111.05992, + 42.35848 + ], + [ + -111.05805, + 42.35849 + ], + [ + -111.05636, + 42.35849 + ], + [ + -111.05588, + 42.35853 + ], + [ + -111.05542, + 42.35859 + ], + [ + -111.05499, + 42.35867 + ], + [ + -111.0545, + 42.3588 + ], + [ + -111.05434, + 42.35885 + ], + [ + -111.05388, + 42.35902 + ], + [ + -111.05347, + 42.35919 + ], + [ + -111.05325, + 42.35931 + ], + [ + -111.05306, + 42.35942 + ], + [ + -111.05287, + 42.35954 + ], + [ + -111.05269, + 42.35966 + ], + [ + -111.05254, + 42.35977 + ], + [ + -111.05235, + 42.35992 + ], + [ + -111.05216, + 42.36009 + ], + [ + -111.05195, + 42.3603 + ], + [ + -111.05187, + 42.36039 + ], + [ + -111.05179, + 42.36049 + ], + [ + -111.0517, + 42.36061 + ], + [ + -111.05162, + 42.36072 + ], + [ + -111.05142, + 42.36102 + ], + [ + -111.05125, + 42.36135 + ], + [ + -111.05112, + 42.3617 + ], + [ + -111.05101, + 42.36208 + ], + [ + -111.05095, + 42.36248 + ], + [ + -111.05095, + 42.36261 + ], + [ + -111.05096, + 42.36289 + ], + [ + -111.05102, + 42.3633 + ], + [ + -111.05107, + 42.36351 + ], + [ + -111.05126, + 42.36415 + ], + [ + -111.05167, + 42.36552 + ], + [ + -111.05202, + 42.36671 + ], + [ + -111.05217, + 42.3672 + ], + [ + -111.05249, + 42.3683 + ], + [ + -111.05283, + 42.36946 + ], + [ + -111.05298, + 42.36997 + ], + [ + -111.05311, + 42.3704 + ], + [ + -111.05319, + 42.37082 + ], + [ + -111.05326, + 42.3713 + ], + [ + -111.05328, + 42.37171 + ], + [ + -111.05328, + 42.37208 + ], + [ + -111.05321, + 42.37339 + ], + [ + -111.05316, + 42.37446 + ], + [ + -111.05311, + 42.37528 + ], + [ + -111.05304, + 42.37609 + ], + [ + -111.053, + 42.37663 + ], + [ + -111.0529, + 42.37773 + ], + [ + -111.05288, + 42.37828 + ], + [ + -111.05288, + 42.3791 + ], + [ + -111.05289, + 42.37966 + ], + [ + -111.0529, + 42.38048 + ], + [ + -111.0529, + 42.38131 + ], + [ + -111.05292, + 42.38239 + ], + [ + -111.05294, + 42.38293 + ], + [ + -111.05301, + 42.38346 + ], + [ + -111.05339, + 42.38542 + ], + [ + -111.05346, + 42.38579 + ], + [ + -111.05354, + 42.38629 + ], + [ + -111.05359, + 42.38679 + ], + [ + -111.05359, + 42.3873 + ], + [ + -111.05355, + 42.38781 + ], + [ + -111.05346, + 42.38832 + ], + [ + -111.05333, + 42.3888 + ], + [ + -111.05315, + 42.38929 + ], + [ + -111.05227, + 42.3912 + ], + [ + -111.05181, + 42.39216 + ], + [ + -111.05109, + 42.39368 + ], + [ + -111.05025, + 42.39544 + ], + [ + -111.04985, + 42.39627 + ], + [ + -111.04935, + 42.39735 + ], + [ + -111.04913, + 42.39784 + ], + [ + -111.04847, + 42.39925 + ], + [ + -111.04839, + 42.39941 + ], + [ + -111.04832, + 42.39954 + ], + [ + -111.04825, + 42.39966 + ], + [ + -111.0482, + 42.39974 + ], + [ + -111.04813, + 42.39984 + ], + [ + -111.04804, + 42.39996 + ], + [ + -111.04795, + 42.40007 + ], + [ + -111.04793, + 42.40009 + ] + ] + } + }, + "data": { + "event_type_short": "special_event", + "event_sub_type": "specialEvent", + "roadway_name": "US-89", + "direction": "North", + "description": "Special event on US-89 Northbound from US-30 to Idaho Wyoming Border. 1 Right lane closed. 9/12/2026 7:00 AM to 9/12/2026 4:00 PM Sat: Active all day Activities: Consider Alternative Route, Expect Delays, Look Out for Flagger, Pilot Car in Operation, Reduced to Single Lane, Alternating Direction of Travel, Use Caution. Expect Delays: Under 1 hour", + "lanes_affected": "1 Right lane closed", + "is_full_closure": false, + "itd_severity": "None", + "comment": "East Bound lane Closed\nAnnual LOTOJA Bike Race.", + "cause": "specialEvents", + "organization": "ERS", + "recurrence_text": "Sat:
Active all day

", + "recurrence_schedules": [ + { + "StartDate": "9/12/2026 7:00:00 AM-06:00:00", + "EndDate": "9/12/2026 4:00:00 PM-06:00:00", + "Times": [ + { + "StartTime": "00:00:00-06:00:00", + "EndTime": "23:59:59-06:00:00" + } + ], + "DaysOfWeek": [ + "Saturday" + ] + } + ], + "restrictions": { + "Width": null, + "Height": null, + "Length": null, + "Weight": null, + "Speed": null + }, + "detour_polyline": null, + "detour_instructions": null, + "encoded_polyline": "w_iaGhxhfT?oEAkI?cHGkBCUSwAQsASyAU{AcCsQk@mEy@}FMaAQyAQgBEy@EaBBsBDiAHiANmARsAZ{AfGmWbBeH\\{ARyAJ_AF_BBmC@yJ?oEBqFBqHJsIDeCF_CH_CTsFLgDNeD^sHJmCDwB?iAAm@IcC[qHa@mJGkAC[CUE[EWG[GWGUGSq@qB}AoEwA}DyAcEqKmZWq@Uk@Wm@]u@a@w@i@aAa@q@_@k@[c@i@s@i@o@m@q@UU[YWSgCiBYUOMKKSU}AiB_AiAe@m@QWS]OYMWISGQK]K_@I]Ia@G_@QqAG_@ESKc@Su@[gAy@wC{Led@{BoIWaAs@qCQu@Mm@Ki@Ie@Ii@Ky@MoAEk@Ca@Cg@GcBSmGCc@Eg@E_@EYIc@I]IYMa@IUKUSa@S]KOGISUKK[YWS{AeAUQOMQQOQg@s@OWMUKUGOIUIWI[I_@G[G_@CSCWC[ASAg@?eABoB?oBAg@ASAMEc@Ga@G]I_@I[Ma@KYQa@MWKQOUUYSUWWa@_@o@k@yH{GWW_@a@OQi@s@k@y@wBgDsCiEqAqB{@sAm@aAe@w@Ua@Sa@MYWo@M]Qi@Oi@Qs@UiAQgAK{@I{@Eo@Cg@Cu@As@?s@@y@@c@Bo@Dq@Fu@Fm@Ho@Hi@RkAv@cEN_AHm@Fq@Bo@@k@?i@Ag@AUCa@CYGg@Kq@Q}@]aBSeAGa@Gk@Ca@C}@?u@BaADcABq@FkADo@BYFg@Fa@Ha@Pq@Ro@L]Vk@P]R[T[Z_@pAuAzB_CxCeDp@w@b@o@j@_A^{@Vg@Tu@`@cBPaAPsAHiABgA@o@?k@Ag@AYIqAEg@Gi@Ii@Ic@Mm@Ok@So@e@qAqG_R[cAOm@Mm@Ik@Iw@Ca@A[As@?k@@s@Be@B]D_@DYF]F[H]J_@La@HUJWP_@NYP[R]x@sAVc@b@y@Te@P_@Vm@Vq@Nc@La@Ni@Ps@Ns@Ha@F]F_@l@gEr@mFvAgKPqA`@uChA_IZsBV_BLo@Pw@Po@n@mBXq@d@}@vAsBr@{@h@g@l@e@`Am@|As@|By@|Am@`Ai@hAy@VWr@{@z@yAr@gBb@aBLo@Jm@HaAHqA?cDIsBIyAY_GQaCUeCa@yCg@iDUcBi@wDo@qEw@qFGc@Ky@Gi@Ee@Em@Ci@A]A}@?s@@k@By@Dq@Di@Dc@Fg@Hi@He@Ha@H]H[Lc@L_@Na@N_@\\u@P]R_@T_@PWV]PSNOvBqBbB{Ah@g@TUPSTYZc@PYJQJSHQJWJYLa@H[H]Ha@F_@Fc@BUB[Ba@@Y@m@?]Ak@ASCa@Eg@Gg@Ig@Ki@Mi@GSM]M[KUSa@OWQW]c@SUWWg@c@y@q@mAcAyHoGaEiDqAiAe@c@SSWYg@m@a@g@_@g@W_@[g@[i@[k@e@}@i@gAq@wAsBmEq@uA_@u@[m@Ua@Wa@}@qAY_@UY_@c@]_@o@o@q@o@[YQOSOWQc@Yg@[q@a@qAu@qEkCm@]c@YYSYUUSKKSU]c@[c@[e@Ua@Sa@Ug@Sg@M]K[K]I[I[GYKm@Ik@Ky@Iy@Ei@Cg@Aa@Ao@AiBC_JAmGA_CA{@Aa@Ck@IiAKcAIk@O{@Mm@CKSu@K][aA_BkE{AoE}@gCwGqQqL}[mGcQkBeFq@kBGSeMk]GOoCuHiCoHw@yCIa@i@{Bw@mDq@{Ds@eFc@eE_@{EOeCKmCGqCC_CC{H?qG@yJ?yLAiD?uR@qD?c]AuJ?qIG_BK{AOuAYaBI_@a@{Aa@qAWk@Ue@We@Wc@U]]e@a@e@i@i@QOSOWQUO{@g@aAa@eAYkAUoAKY?w@@qAJi@H_Cd@qGpAmFdAaB\\{E~@gFbAeB\\uAXsAN_BLqABiA?eGMuEIcDIaDMkBG{ESmBCcD?oB@cD@eD?wEBkBBiBLgKjAiALcBNcBHeB?eBGeBQ_BYaBc@}JoD_E{AoHoC_JgDeDoAwEcBaBk@yGcC_@OYMWMOISMWQUQCC", + "id_internal": 40040, + "source_id": "11466", + "reported_epoch": 1789218000, + "last_updated_epoch": 1782839530, + "start_epoch": 1789218000, + "planned_end_epoch": 1789250400, + "latitude": 42.3220400000001, + "longitude": -111.297488768309, + "_enriched": { + "geocoder": { + "name": "Montpelier", + "city": null, + "county": "Bear Lake", + "state": "Idaho", + "country": "United States", + "postal_code": "83254", + "timezone": "America/Boise", + "landclass": "Bear River Watershed Conservation Area", + "elevation_m": 1824.34375 + } + } + } + } + }, + "subject": "central.traffic.special_event.us.id", + "captured_epoch": 1783206522 +} \ No newline at end of file diff --git a/work/tests/fixtures/traffic_last/0001.json b/work/tests/fixtures/traffic_last/0001.json new file mode 100644 index 0000000..0a52ba1 --- /dev/null +++ b/work/tests/fixtures/traffic_last/0001.json @@ -0,0 +1,151 @@ +{ + "envelope": { + "id": "idaho_511:event:11488", + "source": "central.echo6.co", + "type": "central.closure.itd_511.v1", + "time": "2026-07-01T21:57:23+00:00", + "datacontenttype": "application/json", + "centralschemaversion": "1.0", + "centralcategory": "closure.itd_511", + "centralseverity": 3, + "specversion": "1.0", + "data": { + "id": "idaho_511:event:11488", + "adapter": "itd_511", + "category": "closure.itd_511", + "time": "2026-07-01T21:57:23Z", + "expires": "2026-07-04T20:00:00Z", + "severity": 3, + "geo": { + "centroid": [ + -114.31901, + 43.52443 + ], + "bbox": null, + "regions": [ + "US-ID" + ], + "primary_region": "US-ID", + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -114.31901, + 43.52443 + ], + [ + -114.31862, + 43.5239 + ], + [ + -114.31828, + 43.52348 + ], + [ + -114.31755, + 43.52253 + ], + [ + -114.3168, + 43.52155 + ], + [ + -114.31608, + 43.52059 + ], + [ + -114.31573, + 43.52013 + ], + [ + -114.31536, + 43.51965 + ], + [ + -114.31461, + 43.51867 + ], + [ + -114.31316, + 43.51676 + ], + [ + -114.3131, + 43.51669 + ], + [ + -114.3128, + 43.51629 + ], + [ + -114.31242, + 43.51579 + ] + ] + } + }, + "data": { + "event_type_short": "closure", + "event_sub_type": "parade", + "roadway_name": "SH-75", + "direction": "Both", + "description": "Parade on SH-75 Both Directions from E Myrtle St to W Elm St. All lanes closed. 7/4/2026 11:00 AM to 7/4/2026 2:00 PM Sat: Active all day Activities: Consider Alternative Route, Look Out for Flagger, Use Caution.", + "lanes_affected": "All lanes closed", + "is_full_closure": true, + "itd_severity": "None", + "comment": "Annual 4th of July Parade on Main Street in Hailey, Starting at non and ending around 1:30pm.\nTraffic Control: Chief Steve England 208-309-1323, Hailey Police Dept. 208-788-3531, ITD- KC Marcroft, 986-200-9260.", + "cause": "specialEvents", + "organization": "ERS", + "recurrence_text": "Sat:
Active all day

", + "recurrence_schedules": [ + { + "StartDate": "7/4/2026 11:00:00 AM-06:00:00", + "EndDate": "7/4/2026 2:00:00 PM-06:00:00", + "Times": [ + { + "StartTime": "00:00:00-06:00:00", + "EndTime": "23:59:59-06:00:00" + } + ], + "DaysOfWeek": [ + "Saturday" + ] + } + ], + "restrictions": { + "Width": null, + "Height": null, + "Length": null, + "Weight": null, + "Speed": null + }, + "detour_polyline": null, + "detour_instructions": null, + "encoded_polyline": "uzshGx|vxThBmArAcA|DqCbEuC~DoCzAeA~AiAbEuC|JaHLKnA{@bBkA", + "id_internal": 40283, + "source_id": "11488", + "reported_epoch": 1783184400, + "last_updated_epoch": 1782943043, + "start_epoch": 1783184400, + "planned_end_epoch": 1783195200, + "latitude": 43.5244347035805, + "longitude": -114.319013292506, + "_enriched": { + "geocoder": { + "name": null, + "city": "Hailey", + "county": "Blaine", + "state": "ID", + "country": "United States", + "postal_code": "83333", + "timezone": "America/Boise", + "landclass": null, + "elevation_m": 1625.5703125 + } + } + } + } + }, + "subject": "central.traffic.closure.us.id", + "captured_epoch": 1783206522 +} \ No newline at end of file diff --git a/work/tests/fixtures/traffic_last/0002.json b/work/tests/fixtures/traffic_last/0002.json new file mode 100644 index 0000000..e63b719 --- /dev/null +++ b/work/tests/fixtures/traffic_last/0002.json @@ -0,0 +1,169 @@ +{ + "envelope": { + "id": "idaho_511:event:11501", + "source": "central.echo6.co", + "type": "central.work_zone.itd_511.v1", + "time": "2026-07-03T23:29:03+00:00", + "datacontenttype": "application/json", + "centralschemaversion": "1.0", + "centralcategory": "work_zone.itd_511", + "centralseverity": 1, + "specversion": "1.0", + "data": { + "id": "idaho_511:event:11501", + "adapter": "itd_511", + "category": "work_zone.itd_511", + "time": "2026-07-03T23:29:03Z", + "expires": "2026-08-17T12:00:00Z", + "severity": 1, + "geo": { + "centroid": [ + -112.46636, + 42.9205 + ], + "bbox": null, + "regions": [ + "US-ID" + ], + "primary_region": "US-ID", + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -112.46636, + 42.9205 + ], + [ + -112.46636, + 42.92115 + ], + [ + -112.46637, + 42.92138 + ], + [ + -112.46637, + 42.92163 + ], + [ + -112.46638, + 42.92178 + ], + [ + -112.46638, + 42.92197 + ], + [ + -112.46639, + 42.92235 + ], + [ + -112.46639, + 42.92318 + ], + [ + -112.4664, + 42.92357 + ], + [ + -112.46642, + 42.92472 + ], + [ + -112.46642, + 42.92557 + ], + [ + -112.46641, + 42.9258 + ], + [ + -112.46641, + 42.92601 + ], + [ + -112.4664, + 42.92667 + ], + [ + -112.46642, + 42.92689 + ], + [ + -112.46643, + 42.92742 + ] + ] + } + }, + "data": { + "event_type_short": "work_zone", + "event_sub_type": "roadConstruction", + "roadway_name": "US-91", + "direction": "South", + "description": "Road construction on US-91 Southbound from W Chubbuck Rd to Highway Ave. 1 Right lane closed. 7/6/2026 8:00 PM to 8/17/2026 6:00 AM Mon, Tue, Wed, Thu, Fri, Sat, Sun: Active all day Width Restriction: 12ft", + "lanes_affected": "1 Right lane closed", + "is_full_closure": false, + "itd_severity": "None", + "comment": "Concrete pavement repair and replacement project. Waterline work. Traffic Control - Jacob Greenburg (208) 272-1741 [Idaho Traffic Safety]; Contractor - Russ Jensen (208 680-6288 [Cannon Builders Inc.]", + "cause": "roadwork", + "organization": "ERS", + "recurrence_text": "Mon, Tue, Wed, Thu, Fri, Sat, Sun:
Active all day

", + "recurrence_schedules": [ + { + "StartDate": "7/6/2026 8:00:00 PM-06:00:00", + "EndDate": "8/17/2026 6:00:00 AM-06:00:00", + "Times": [ + { + "StartTime": "00:00:00-06:00:00", + "EndTime": "23:59:59-06:00:00" + } + ], + "DaysOfWeek": [ + "Monday", + "Tuesday", + "Wednesday", + "Thursday", + "Friday", + "Saturday", + "Sunday" + ] + } + ], + "restrictions": { + "Width": 12.0, + "Height": null, + "Length": null, + "Weight": null, + "Speed": null + }, + "detour_polyline": null, + "detour_instructions": null, + "encoded_polyline": "c|}dGvammTaC?m@@q@?]@e@?kA@eD?mA@eFBiD?m@Ai@?cCAk@BiB@", + "id_internal": 40622, + "source_id": "11501", + "reported_epoch": 1783389600, + "last_updated_epoch": 1783121343, + "start_epoch": 1783389600, + "planned_end_epoch": 1786968000, + "latitude": 42.9204972258541, + "longitude": -112.46636, + "_enriched": { + "geocoder": { + "name": "Chubbuck", + "city": null, + "county": "Bannock", + "state": "Idaho", + "country": "United States", + "postal_code": "83202", + "timezone": "America/Boise", + "landclass": null, + "elevation_m": 1363.3203125 + } + } + } + } + }, + "subject": "central.traffic.work_zone.us.id", + "captured_epoch": 1783206522 +} \ No newline at end of file diff --git a/work/tests/fixtures/traffic_last/0003.json b/work/tests/fixtures/traffic_last/0003.json new file mode 100644 index 0000000..8a37483 --- /dev/null +++ b/work/tests/fixtures/traffic_last/0003.json @@ -0,0 +1,65 @@ +{ + "envelope": { + "id": "ERS:AHtuy8Ufz5L4obSzLmwqIYYcPDk=", + "source": "central.echo6.co", + "type": "central.work_zone.wzdx.v1", + "time": "2026-07-01T14:06:45+00:00", + "datacontenttype": "application/json", + "centralschemaversion": "1.0", + "centralcategory": "work_zone.wzdx", + "centralseverity": 1, + "specversion": "1.0", + "data": { + "id": "ERS:AHtuy8Ufz5L4obSzLmwqIYYcPDk=", + "adapter": "wzdx", + "category": "work_zone.wzdx", + "time": "2026-07-01T14:06:45Z", + "expires": "2026-07-19T00:59:59Z", + "severity": 1, + "geo": { + "centroid": [ + -116.91195, + 43.66669 + ], + "bbox": null, + "regions": [ + "US-ID" + ], + "primary_region": "US-ID", + "geometry": null + }, + "data": { + "road_names": [ + "US-95" + ], + "direction": "southbound", + "description": " Paving Operations on US-95 Both Directions from Junction of US-95 and SH-19 to N Roswell Blvd. 6/22/2026 7:00 AM to 7/20/2026 7:00 AM Mon, Tue, Wed, Thu, Fri, Sat, Sun: 7:00 AM - 7:00 PM Width Restriction: 12ft ", + "vehicle_impact": "all-lanes-open", + "event_status": null, + "start_date": "2026-07-18T13:00:00Z", + "end_date": "2026-07-19T00:59:59Z", + "data_source_id": "ERS", + "feed_name": "iddot", + "feed_state": "idaho", + "feed_state_code": "ID", + "latitude": 43.66669, + "longitude": -116.91195, + "_enriched": { + "geocoder": { + "name": "5th Street", + "city": "Wilder", + "county": "Canyon", + "state": "Idaho", + "country": "United States", + "postal_code": "83676", + "timezone": "America/Boise", + "landclass": null, + "elevation_m": 754.3125 + } + } + } + } + }, + "subject": "central.traffic.work_zone.id", + "captured_epoch": 1783206522 +} \ No newline at end of file diff --git a/work/tests/fixtures/traffic_last/0004.json b/work/tests/fixtures/traffic_last/0004.json new file mode 100644 index 0000000..4914947 --- /dev/null +++ b/work/tests/fixtures/traffic_last/0004.json @@ -0,0 +1,87 @@ +{ + "envelope": { + "id": "idaho_511:event:11503", + "source": "central.echo6.co", + "type": "central.incident.itd_511.v1", + "time": "2026-07-04T18:24:09+00:00", + "datacontenttype": "application/json", + "centralschemaversion": "1.0", + "centralcategory": "incident.itd_511", + "centralseverity": 1, + "specversion": "1.0", + "data": { + "id": "idaho_511:event:11503", + "adapter": "itd_511", + "category": "incident.itd_511", + "time": "2026-07-04T18:24:09Z", + "expires": null, + "severity": 1, + "geo": { + "centroid": [ + -115.925124696356, + 43.7434489175347 + ], + "bbox": null, + "regions": [ + "US-ID" + ], + "primary_region": "US-ID", + "geometry": { + "type": "Point", + "coordinates": [ + -115.925124696356, + 43.7434489175347 + ] + } + }, + "data": { + "event_type_short": "incident", + "event_sub_type": "roadwayBlocked", + "roadway_name": "SH-21", + "direction": "Both", + "description": "Roadway Blocked on SH-21 Both Directions near MM (31). Lane Blocked.", + "lanes_affected": "Lane Blocked", + "is_full_closure": false, + "itd_severity": "None", + "comment": null, + "cause": "Incident", + "organization": "ERS", + "recurrence_text": null, + "recurrence_schedules": [], + "restrictions": { + "Width": null, + "Height": null, + "Length": null, + "Weight": null, + "Speed": null + }, + "detour_polyline": null, + "detour_instructions": null, + "encoded_polyline": null, + "id_internal": 40654, + "source_id": "11503", + "reported_epoch": 1783189260, + "last_updated_epoch": 1783189449, + "start_epoch": 1783189260, + "planned_end_epoch": null, + "latitude": 43.7434489175347, + "longitude": -115.925124696356, + "_enriched": { + "geocoder": { + "name": null, + "city": "Boise", + "county": "Boise", + "state": "ID", + "country": "United States", + "postal_code": "83716", + "timezone": "America/Boise", + "landclass": "Boise National Forest", + "elevation_m": 1048.578125 + } + } + } + } + }, + "subject": "central.traffic.incident.us.id", + "captured_epoch": 1783206522 +} \ No newline at end of file diff --git a/work/tests/fixtures/traffic_last/0005.json b/work/tests/fixtures/traffic_last/0005.json new file mode 100644 index 0000000..3d309cb --- /dev/null +++ b/work/tests/fixtures/traffic_last/0005.json @@ -0,0 +1,140 @@ +{ + "envelope": { + "id": "ID:tomtom:TTI-b6f6bc37-311b-4087-aa8a-6b7f6113bb59-TTL15306674364011000", + "source": "central.echo6.co", + "type": "central.incident.tomtom_incidents.v1", + "time": "2026-07-04T22:44:00+00:00", + "datacontenttype": "application/json", + "centralschemaversion": "1.0", + "centralcategory": "incident.tomtom_incidents", + "centralseverity": 3, + "specversion": "1.0", + "data": { + "id": "ID:tomtom:TTI-b6f6bc37-311b-4087-aa8a-6b7f6113bb59-TTL15306674364011000", + "adapter": "tomtom_incidents", + "category": "incident.tomtom_incidents", + "time": "2026-07-04T22:44:00Z", + "expires": "2026-07-04T23:06:30Z", + "severity": 3, + "geo": { + "centroid": [ + -114.9407834501, + 42.9236671751 + ], + "bbox": null, + "regions": [ + "US-ID" + ], + "primary_region": "US-ID", + "geometry": { + "type": "LineString", + "coordinates": [ + [ + -114.9407834501, + 42.9236671751 + ], + [ + -114.9410422833, + 42.9236671751 + ], + [ + -114.9413225741, + 42.9236671751 + ], + [ + -114.9417235644, + 42.9236685254 + ], + [ + -114.94239814, + 42.923672515 + ], + [ + -114.9424477608, + 42.923672515 + ], + [ + -114.9424558075, + 42.923672515 + ], + [ + -114.9424893351, + 42.923672515 + ], + [ + -114.942681113, + 42.9236738653 + ], + [ + -114.9428500922, + 42.9236738653 + ], + [ + -114.9437392445, + 42.9236805554 + ], + [ + -114.9441858323, + 42.9236873069 + ], + [ + -114.9443454237, + 42.9236885959 + ], + [ + -114.944361517, + 42.9236899462 + ], + [ + -114.944669971, + 42.9236939971 + ], + [ + -114.9450146349, + 42.9236926468 + ], + [ + -114.9451822729, + 42.9236912965 + ] + ] + } + }, + "data": { + "description": "Stationary traffic", + "event_code": 101, + "from": "Highway 30 / South 750 East", + "to": "US Highway 30 / 2nd Avenue", + "magnitude_of_delay": 3, + "icon_category": 6, + "length": 358.1977801718, + "delay": 154, + "road_numbers": [ + "I-84 Business" + ], + "start_time": "2026-07-04T22:44:00Z", + "end_time": "2026-07-04T23:06:30Z", + "time_validity": "present", + "state_code": "ID", + "bbox_name": "mountain_home_corridor", + "latitude": 42.9236671751, + "longitude": -114.9407834501, + "_enriched": { + "geocoder": { + "name": "Love's", + "city": "Bliss", + "county": "Gooding", + "state": "Idaho", + "country": "United States", + "postal_code": "83314", + "timezone": "America/Boise", + "landclass": null, + "elevation_m": 993.83984375 + } + } + } + } + }, + "subject": "central.traffic.incident.id", + "captured_epoch": 1783206522 +} \ No newline at end of file diff --git a/work/tests/test_adapter_nws.py b/work/tests/test_adapter_nws.py index ca60181..1cf97db 100644 --- a/work/tests/test_adapter_nws.py +++ b/work/tests/test_adapter_nws.py @@ -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" # ============================================================ diff --git a/work/tests/test_formatter_scaffold.py b/work/tests/test_formatter_scaffold.py index 91e2774..551e683 100644 --- a/work/tests/test_formatter_scaffold.py +++ b/work/tests/test_formatter_scaffold.py @@ -15,16 +15,15 @@ from meshai.notifications.renderers.composer import compose_mesh_message # ── (a) empty registry returns None for known categories ────────────────────── @pytest.mark.parametrize("category", [ - "weather_warning", # earthquake_event removed: Phase-1 registers formatters.quake for it. + # weather_warning/weather_statement removed: Phase-2 registers formatters.nws. + # road_closure/work_zone/road_incident/traffic_congestion removed: Phase-2 registers formatters.incident. "wildfire_incident", - "road_closure", "battery_critical", ]) def test_get_formatter_returns_none_while_registry_empty(category): """Un-migrated categories must still return None from get_formatter.""" - # earthquake_event is now migrated (Phase 1); the remaining categories - # here have no formatter yet and must still fall through to Mode-B. + # Only categories not yet migrated to the formatter+gater architecture. assert category not in FORMATTERS, ( f"Category {category!r} should not be in FORMATTERS yet (not migrated)" ) diff --git a/work/tests/test_incident_refactor.py b/work/tests/test_incident_refactor.py new file mode 100644 index 0000000..d0d9eda --- /dev/null +++ b/work/tests/test_incident_refactor.py @@ -0,0 +1,709 @@ +"""Phase-2 incident/roads refactor tests — TIER-A (byte-identical goldens). + +Test strategy +------------- +Golden (byte-identical) tests call the old per-source parsers and the legacy +renderer directly, then compare byte-for-byte against the new +formatters.incident.format() output. This bypasses the handle_incident +freshness gate (which drops all captured fixtures as stale) and isolates the +rendering logic — exactly the same pattern as test_quake_refactor.py. + +Groups +------ +1. Tomtom incident golden byte-parity (all 40 traffic/*.json fixtures with + min_magnitude override=0 so the parser doesn't filter them; fixtures that + the parser still rejects for other reasons are skipped gracefully). +2. ITD-511 incident golden byte-parity (traffic/0032.json + the non-work-zone + fixtures in traffic_last/). +3. Work-zone golden byte-parity (traffic_last/0002 itd_511, traffic_last/0003 + wzdx) — calls normalize() directly, same as the production consumer. +4. Gate sequence: decide() lifecycle transitions (new → cold-dup → + suppress-on-update-False → magnitude-up → suppress-no-change). +5. _anchor.resolve_anchor: DB hit and Photon fallback path. +6. Schema conformance: canonical data dict has all expected keys. +7. Cross-source identity: same render fields → same output regardless of + source string. +""" +from __future__ import annotations + +import calendar +import json +import pathlib +import time +from datetime import datetime +from typing import Optional + +import pytest + +from tests.harness.goldens import assert_byte_identical + +# ── Constants ──────────────────────────────────────────────────────────────── + +_FIXTURE_DIR = pathlib.Path(__file__).parent / "fixtures" + +# Pinned clock epoch for deterministic golden comparison (all work-zone tests). +# Using captured_epoch of the traffic_last fixtures (1783206522). +_AT_WZ = 1_783_206_522.0 + +# Expected canonical data keys for incident events. +_INCIDENT_CANONICAL_KEYS = frozenset({ + "external_id", "source", "sub_type", "road", "direction", + "from_loc", "to_loc", "mile_start", "mile_end", "mile_marker", + "lanes_affected", "cause", "comment", "impact", + "county", "state", "lat", "lon", "geocoder_city", "landclass", + "start_at", "end_at", "magnitude", "delay_seconds", "icon_category", +}) + +# Expected canonical data keys for work-zone events. +_WZ_CANONICAL_KEYS = frozenset({ + "road", "direction", "mile_start", "mile_end", "sub_type", "impact", + "ends_at_epoch", "town", "distance_mi", "bearing", "lat", "lon", +}) + + +# ── Fixture loaders (module-level, before any DB touch) ────────────────────── + +def _load_dir(hazard: str): + """Load all fixtures from tests/fixtures// sorted by name.""" + d = _FIXTURE_DIR / hazard + if not d.is_dir(): + return [] + out = [] + for p in sorted(d.glob("*.json")): + with open(p, encoding="utf-8") as f: + out.append((p.name, json.load(f))) + return out + + +_TRAFFIC_FX = _load_dir("traffic") # 40 files (mostly tomtom, one itd_511) +_TRAFFIC_LAST_FX = _load_dir("traffic_last") # 6 files (mixed adapters) + + +# ── Helper: build canonical incident dict from parser output ───────────────── + +def _n_to_canonical_incident(n: dict) -> dict: + """Mirror the cutover-branch canonical extraction in handle_incident.""" + return { + "external_id": n.get("external_id"), + "source": n.get("source"), + "sub_type": n.get("sub_type"), + "road": n.get("road"), + "direction": n.get("direction"), + "from_loc": n.get("from_loc"), + "to_loc": n.get("to_loc"), + "mile_start": n.get("mile_start"), + "mile_end": n.get("mile_end"), + "mile_marker": n.get("mile_marker"), + "lanes_affected": n.get("lanes_affected"), + "cause": n.get("cause"), + "comment": n.get("comment"), + "impact": n.get("impact"), + "county": n.get("county"), + "state": n.get("state"), + "lat": n.get("lat"), + "lon": n.get("lon"), + "geocoder_city": n.get("geocoder_city"), + "landclass": n.get("landclass"), + "start_at": n.get("start_at"), + "end_at": n.get("end_at"), + "magnitude": n.get("magnitude"), + "delay_seconds": n.get("delay_seconds"), + "icon_category": n.get("icon_category"), + } + + +def _n_to_canonical_workzone(n: dict) -> dict: + """Build canonical work-zone data dict from a normalize() result. + + ends_at_epoch is stored as calendar.timegm(naive_dt.timetuple()) which + treats the naive datetime as UTC. The formatter reconstructs via + datetime.utcfromtimestamp() — TZ-independent round-trip. + """ + ends_at: Optional[datetime] = n.get("ends_at") + ends_at_epoch: Optional[float] = None + if ends_at is not None: + try: + # Strip tzinfo first (mirrors _format_end_short's strip). + naive = ends_at.replace(tzinfo=None) if ends_at.tzinfo is not None else ends_at + ends_at_epoch = float(calendar.timegm(naive.timetuple())) + except Exception: + ends_at_epoch = None + return { + "road": n.get("road"), + "direction": n.get("direction"), + "mile_start": n.get("mile_start"), + "mile_end": n.get("mile_end"), + "sub_type": n.get("sub_type"), + "impact": n.get("impact"), + "ends_at_epoch": ends_at_epoch, + "town": n.get("town"), + "distance_mi": n.get("distance_mi"), + "bearing": n.get("bearing"), + "lat": None, # anchor already resolved in normalizer + "lon": None, + } + + +# ── Helper: minimal Event for formatter calls ──────────────────────────────── + +def _make_event(category: str, data: dict): + from meshai.notifications.events import Event + return Event(category=category, data=data) + + +# ── pytest fixtures: adapter_config overrides ──────────────────────────────── + +@pytest.fixture() +def tomtom_min_mag_zero(): + """Set tomtom_incidents.min_magnitude=-999 via runtime override for one test. + + The parser uses `int(adapter_config.tomtom_incidents.min_magnitude or 4)` + which treats 0 as falsy and falls back to 4. Using -999 (truthy) forces + all magnitudes to pass the `magnitude < min_mag` filter. + """ + from meshai.adapter_config._accessor import set_runtime_override, _overrides + set_runtime_override("tomtom_incidents", "min_magnitude", -999) + yield + _overrides.pop(("tomtom_incidents", "min_magnitude"), None) + + +@pytest.fixture() +def broadcast_on_update_on(): + """Enable incident.broadcast_on_update for gate-sequence tests.""" + from meshai.adapter_config._accessor import set_runtime_override, _overrides + set_runtime_override("incident", "broadcast_on_update", True) + yield + _overrides.pop(("incident", "broadcast_on_update"), None) + + +# ── Helpers: determine adapter type and call the right parser ──────────────── + +def _adapter_for(fx: dict) -> str: + return (fx["envelope"]["data"] or {}).get("adapter") or "" + + +def _category_for(fx: dict) -> str: + return (fx["envelope"]["data"] or {}).get("category") or "" + + +# ── 1. Tomtom incident golden byte-parity ──────────────────────────────────── + +class TestTomtomGolden: + """All traffic/*.json tomtom fixtures rendered via old parser + _render() + must produce byte-identical output from the new formatters.incident.format(). + + min_magnitude is overridden to 0 so all fixtures (including mag=3 ones) + exercise the renderer. The one itd_511 fixture (0032) is skipped here. + """ + + @pytest.mark.parametrize("name,fx", _TRAFFIC_FX) + def test_golden_parity(self, name, fx, tomtom_min_mag_zero): + from meshai.central.incident_handler import _parse_tomtom_incident, _render + from meshai.notifications.formatters.incident import format as fmt + from meshai.notifications.formatters._budget import budget_for + + adapter = _adapter_for(fx) + if adapter != "tomtom_incidents": + pytest.skip(f"{name}: adapter={adapter!r}, not tomtom") + + envelope = fx["envelope"] + now = int(fx.get("captured_epoch", time.time())) + + n = _parse_tomtom_incident(envelope, now) + if n is None: + pytest.skip(f"{name}: parser returned None (filtered)") + + golden = _render(n) + canonical = _n_to_canonical_incident(n) + + # Determine event category from category_kind + kind_map = { + "incident": "road_incident", + "closure": "road_closure", + "special_event": "road_incident", + "work_zone": "work_zone", + } + cat = kind_map.get(n.get("category_kind", "incident"), "road_incident") + event = _make_event(cat, canonical) + + budget = budget_for("incident") + new_out = fmt(event, now=float(now), budget=budget) + + assert_byte_identical(new_out, golden) + + +# ── 2. ITD-511 incident golden byte-parity ─────────────────────────────────── + +class TestItd511IncidentGolden: + """itd_511 incident/closure/special_event fixtures rendered via + _parse_itd_511_incident + _render() must be byte-identical from formatter. + + Covers traffic/0032.json (itd_511) + non-work-zone traffic_last fixtures. + """ + + def _run_single(self, name: str, fx: dict): + from meshai.central.incident_handler import _parse_itd_511_incident, _render + from meshai.notifications.formatters.incident import format as fmt + from meshai.notifications.formatters._budget import budget_for + + envelope = fx["envelope"] + category_raw = _category_for(fx) + now = int(fx.get("captured_epoch", time.time())) + + n = _parse_itd_511_incident(envelope, category_raw, now) + if n is None: + pytest.skip(f"{name}: parser returned None (filtered/work_zone)") + + golden = _render(n) + canonical = _n_to_canonical_incident(n) + kind_map = { + "incident": "road_incident", + "closure": "road_closure", + "special_event": "road_incident", + "work_zone": "work_zone", + } + cat = kind_map.get(n.get("category_kind", "incident"), "road_incident") + event = _make_event(cat, canonical) + + budget = budget_for("incident") + new_out = fmt(event, now=float(now), budget=budget) + assert_byte_identical(new_out, golden) + + @pytest.mark.parametrize("name,fx", [ + (name, fx) for name, fx in _TRAFFIC_FX if ( + (fx["envelope"].get("data") or {}).get("adapter") == "itd_511" + ) + ]) + def test_traffic_itd511(self, name, fx): + self._run_single(name, fx) + + @pytest.mark.parametrize("name,fx", [ + (name, fx) for name, fx in _TRAFFIC_LAST_FX if ( + (fx["envelope"].get("data") or {}).get("adapter") == "itd_511" + and not ((fx["envelope"].get("data") or {}).get("category") or "").startswith("work_zone.") + ) + ]) + def test_traffic_last_itd511(self, name, fx): + self._run_single(name, fx) + + @pytest.mark.parametrize("name,fx", [ + (name, fx) for name, fx in _TRAFFIC_LAST_FX if ( + (fx["envelope"].get("data") or {}).get("adapter") == "tomtom_incidents" + ) + ]) + def test_traffic_last_tomtom(self, name, fx, tomtom_min_mag_zero): + """traffic_last tomtom fixtures go through this group (min_mag override on).""" + from meshai.central.incident_handler import _parse_tomtom_incident, _render + from meshai.notifications.formatters.incident import format as fmt + from meshai.notifications.formatters._budget import budget_for + + envelope = fx["envelope"] + now = int(fx.get("captured_epoch", time.time())) + + n = _parse_tomtom_incident(envelope, now) + if n is None: + pytest.skip(f"{name}: parser returned None") + + golden = _render(n) + canonical = _n_to_canonical_incident(n) + event = _make_event("road_incident", canonical) + budget = budget_for("incident") + new_out = fmt(event, now=float(now), budget=budget) + assert_byte_identical(new_out, golden) + + +# ── 3. Work-zone golden byte-parity ───────────────────────────────────────── + +class TestWorkZoneGolden: + """traffic_last/0002 (itd_511 work_zone) and traffic_last/0003 (wzdx) + must produce byte-identical output from the new formatter. + + Golden is computed via normalize() → format_work_zone_mesh() (old path). + New path: normalize() → canonical data → formatters.incident.format(). + + now is pinned to captured_epoch (1783206522) for both paths so the + ends-at segment is deterministic. + """ + + def _run_wz(self, fixture_name: str, adapter_expected: str): + from meshai.central_normalizer import normalize + from meshai.notifications.renderers.work_zone import format_work_zone_mesh + from meshai.notifications.formatters.incident import format as fmt + + # Find the fixture by name + fx_map = dict(_TRAFFIC_LAST_FX) + assert fixture_name in fx_map, f"Fixture {fixture_name!r} not found" + fx = fx_map[fixture_name] + + adapter = _adapter_for(fx) + assert adapter == adapter_expected, ( + f"Expected adapter={adapter_expected!r}, got {adapter!r}" + ) + + envelope = fx["envelope"] + now_epoch = float(fx.get("captured_epoch", time.time())) + now_dt = datetime.fromtimestamp(now_epoch) + + # Old renderer golden + n = normalize(envelope) + assert n is not None, f"normalize() returned None for {fixture_name!r}" + golden = format_work_zone_mesh(n, now=now_dt) + + # New formatter + canonical = _n_to_canonical_workzone(n) + event = _make_event("work_zone", canonical) + new_out = fmt(event, now=now_epoch, budget=140) + + assert_byte_identical(new_out, golden) + + def test_itd511_workzone_0002(self): + self._run_wz("0002.json", "itd_511") + + def test_wzdx_workzone_0003(self): + self._run_wz("0003.json", "wzdx") + + +# ── 4. Gate sequence ────────────────────────────────────────────────────────── + +class TestGateSequence: + """decide() lifecycle transitions: + step 1: new external_id → lifecycle="new", broadcast=True + step 2: same id, cold-dup (commit not called) → lifecycle="new", broadcast=True + step 3: same id, last_broadcast_at set; broadcast_on_update=False → suppress + step 4: enable broadcast_on_update; magnitude up → lifecycle="update" + step 5: same magnitude, no other change → suppress + """ + + _BASE_DATA = { + "external_id": "test-tti-abc123", + "source": "tomtom_incidents", + "sub_type": "jam", + "road": "I-84", + "direction": "W", + "from_loc": None, + "to_loc": None, + "mile_start": None, + "mile_end": None, + "county": "Ada", + "state": "ID", + "lat": 43.5, + "lon": -116.2, + "impact": None, + "start_at": 1783200000, + "end_at": None, + "magnitude": 3, + "delay_seconds": 180, + "icon_category": "jam", + } + + def _decide(self, data: dict, now: float = 1_783_200_000.0): + from meshai.notifications.gating.incident import decide + return decide(data, source="tomtom_incidents", now=now) + + def test_step1_new(self): + """First sight → new, broadcast=True, commit is callable.""" + result = self._decide(dict(self._BASE_DATA)) + assert result.broadcast is True + assert result.lifecycle == "new" + assert callable(result.commit) + assert result.data_patch.get("is_update") is False + + def test_step2_cold_dup_no_commit(self): + """Row exists (from step1 INSERT) but last_broadcast_at = NULL + → cold-start → still lifecycle="new", broadcast=True.""" + data = dict(self._BASE_DATA) + # INSERT without committing (simulate dispatcher drop) + self._decide(data, now=1_783_200_000.0) + # Second call: row exists, last_broadcast_at still NULL + result = self._decide(data, now=1_783_200_001.0) + assert result.broadcast is True + assert result.lifecycle == "new" + + def test_step3_suppress_when_update_false(self): + """After commit (last_broadcast_at set), broadcast_on_update=False → suppress.""" + data = dict(self._BASE_DATA) + # Step 1: new + commit + r1 = self._decide(data, now=1_783_200_000.0) + assert r1.broadcast is True + r1.commit(1_783_200_000.0) # sets last_broadcast_at + + # Step 3: same data, no magnitude/delay/icon change, update=False (default) + r2 = self._decide(data, now=1_783_200_010.0) + assert r2.broadcast is False + assert r2.lifecycle == "suppress" + + def test_step4_magnitude_up_triggers_update(self, broadcast_on_update_on): + """With broadcast_on_update=True + magnitude stepped up → lifecycle="update".""" + data = dict(self._BASE_DATA) + # new + commit + r1 = self._decide(data, now=1_783_200_000.0) + r1.commit(1_783_200_000.0) + + # Higher magnitude + data_updated = dict(self._BASE_DATA, magnitude=4) + result = self._decide(data_updated, now=1_783_200_020.0) + assert result.broadcast is True + assert result.lifecycle == "update" + assert result.data_patch.get("is_update") is True + + def test_step5_no_change_suppressed(self, broadcast_on_update_on): + """After update commit, same magnitude → suppress.""" + data = dict(self._BASE_DATA) + # new + commit + r1 = self._decide(data, now=1_783_200_000.0) + r1.commit(1_783_200_000.0) + + # mag-up + commit + data_up = dict(self._BASE_DATA, magnitude=4) + r2 = self._decide(data_up, now=1_783_200_020.0) + assert r2.broadcast is True + r2.commit(1_783_200_020.0) + + # Same magnitude again → no update condition + r3 = self._decide(data_up, now=1_783_200_030.0) + assert r3.broadcast is False + assert r3.lifecycle == "suppress" + + def test_native_adapter_always_broadcasts(self): + """external_id=None → native path, always broadcast lifecycle='native'.""" + data = dict(self._BASE_DATA, external_id=None) + result = self._decide(data) + assert result.broadcast is True + assert result.lifecycle == "native" + assert result.commit is None + + +# ── 5. _anchor.resolve_anchor ──────────────────────────────────────────────── + +class TestAnchorResolve: + """resolve_anchor() returns a result from the town_anchors DB when a row + is within max_mi, and falls through to nearest_town() otherwise.""" + + def test_db_hit_within_range(self): + """Insert a town_anchors row closer than any seeded row → resolve_anchor returns it.""" + import time as _time + from meshai.persistence import get_db + from meshai.notifications.formatters._anchor import resolve_anchor + + conn = get_db() + # Use extreme southern coordinates so no seeded anchor is closer. + # Insert our test anchor right next to the event coords. + conn.execute( + "INSERT OR IGNORE INTO town_anchors(name, lat, lon, state, enabled, updated_at) " + "VALUES (?, ?, ?, ?, ?, ?)", + ("testville", -33.8688, 151.2093, "NSW", 1, _time.time()), # Sydney + ) + + # Event very close to Sydney + result = resolve_anchor(-33.870, 151.210, max_mi=50.0) + assert result is not None + assert result["town"] == "Testville" # title-cased + assert isinstance(result["distance_mi"], int) + assert result["bearing"] in {"N", "NE", "E", "SE", "S", "SW", "W", "NW"} + + def test_db_hit_out_of_range_returns_none(self): + """Only a far-away town_anchors row exists → max_mi filter → None from DB step.""" + import time as _time + from meshai.persistence import get_db + from meshai.notifications.formatters._anchor import resolve_anchor + + conn = get_db() + # Clear seeded anchors so only our controlled row exists. + conn.execute("DELETE FROM town_anchors") + conn.execute( + "INSERT INTO town_anchors(name, lat, lon, state, enabled, updated_at) " + "VALUES (?, ?, ?, ?, ?, ?)", + ("fartown", 47.0, -116.2, "ID", 1, _time.time()), # ~380 km N + ) + + # max_mi=10 — fartown is way out of range; nearest_town fallback will + # also fail in the test env (no Photon at these coords) → None + result = resolve_anchor(43.615, -116.205, max_mi=10.0) + assert result is None + + def test_photon_fallback(self, monkeypatch): + """With all town_anchors cleared, nearest_town() fallback is exercised.""" + import time as _time + from meshai.persistence import get_db + from meshai.notifications.formatters._anchor import resolve_anchor + import meshai.central_normalizer as cn_mod + + # Clear all seeded town_anchors so the DB step finds nothing. + conn = get_db() + conn.execute("DELETE FROM town_anchors") + + called = [] + + def fake_nearest_town(lat, lon, max_distance_mi=50.0): + called.append((lat, lon)) + return {"name": "Photon City", "distance_mi": 5, "bearing": "NW"} + + monkeypatch.setattr(cn_mod, "nearest_town", fake_nearest_town) + + result = resolve_anchor(43.615, -116.205, max_mi=50.0) + assert result is not None + assert result["town"] == "Photon City" + assert result["distance_mi"] == 5 + assert result["bearing"] == "NW" + assert called # ensure fallback was called + + def test_none_coords_returns_none(self): + from meshai.notifications.formatters._anchor import resolve_anchor + assert resolve_anchor(None, -116.2, max_mi=50.0) is None + assert resolve_anchor(43.6, None, max_mi=50.0) is None + + +# ── 6. Schema conformance ──────────────────────────────────────────────────── + +class TestSchemaConformance: + """Canonical data dicts produced by to_event() and the bridge must + contain all expected keys.""" + + def test_incident_canonical_keys_from_parser(self): + """All canonical keys present in extraction from a tomtom parse.""" + from meshai.central.incident_handler import _parse_tomtom_incident + from meshai.adapter_config._accessor import set_runtime_override, _overrides + + set_runtime_override("tomtom_incidents", "min_magnitude", -999) + try: + # Use fixture 0002 (mag=4, the minimal fixture that passes) + with open(_FIXTURE_DIR / "traffic" / "0002.json", encoding="utf-8") as f: + fx = json.load(f) + n = _parse_tomtom_incident(fx["envelope"], int(fx.get("captured_epoch", 0))) + assert n is not None + canonical = _n_to_canonical_incident(n) + assert _INCIDENT_CANONICAL_KEYS == set(canonical.keys()) + finally: + _overrides.pop(("tomtom_incidents", "min_magnitude"), None) + + def test_workzone_canonical_keys_from_normalize(self): + """All work-zone canonical keys present in extraction from normalize().""" + from meshai.central_normalizer import normalize + + with open(_FIXTURE_DIR / "traffic_last" / "0002.json", encoding="utf-8") as f: + fx = json.load(f) + n = normalize(fx["envelope"]) + assert n is not None + canonical = _n_to_canonical_workzone(n) + assert _WZ_CANONICAL_KEYS == set(canonical.keys()) + + def test_roads511_to_event_canonical_keys(self): + """Roads511Adapter.to_event() emits all incident canonical keys.""" + from meshai.env.roads511 import Roads511Adapter + + class _Cfg: + api_key = ""; base_url = ""; endpoints = []; bbox = []; tick_seconds = 300 + + adapter = Roads511Adapter(_Cfg()) + + # Minimal internal event dict + evt = { + "source": "511", + "event_id": "511_test001", + "event_type": "Incident", + "headline": "Test: Road Event", + "description": "Debris on roadway", + "severity": "routine", + "lat": 43.6, "lon": -116.2, + "expires": 1_783_300_000.0, + "fetched_at": 1_783_200_000.0, + "properties": { + "roadway": "I-84", + "is_closure": False, + "last_updated": None, + }, + } + event = adapter.to_event(evt) + assert event is not None + d = event.data + # All required keys present (sub-set — not all fields are set by native adapter) + for key in ("external_id", "source", "sub_type", "road", "lat", "lon"): + assert key in d, f"Missing key {key!r} in Roads511Adapter canonical data" + + +# ── 7. Cross-source identity ───────────────────────────────────────────────── + +class TestCrossSourceIdentity: + """Same render-relevant canonical fields → same formatter output regardless + of source string or other metadata fields.""" + + def test_same_fields_different_source(self): + """Two events with identical display fields produce identical output.""" + from meshai.notifications.formatters.incident import format as fmt + + shared_fields = { + "sub_type": "accident", + "road": "I-84", + "direction": "W", + "geocoder_city": "Boise", + "state": "ID", + "mile_marker": None, + "from_loc": None, + "to_loc": None, + "lanes_affected": "Two left lanes closed", + "comment": "Multi-vehicle crash", + "impact": None, + "county": None, + } + + data_a = dict(shared_fields, external_id="TTI-001", source="tomtom_incidents", + lat=43.6, lon=-116.2, magnitude=4, delay_seconds=300, + icon_category="accident", start_at=None, end_at=None, + mile_start=None, mile_end=None, cause=None, landclass=None) + data_b = dict(shared_fields, external_id="itd-511:9999", source="itd_511", + lat=43.61, lon=-116.21, magnitude=None, delay_seconds=None, + icon_category="accident", start_at=None, end_at=None, + mile_start=None, mile_end=None, cause=None, landclass=None) + + event_a = _make_event("road_incident", data_a) + event_b = _make_event("road_incident", data_b) + + out_a = fmt(event_a, now=1_783_200_000.0, budget=140) + out_b = fmt(event_b, now=1_783_200_000.0, budget=140) + + assert_byte_identical(out_a, out_b) + + def test_work_zone_category_uses_wz_renderer(self): + """event.category='work_zone' selects the work-zone path, not incident.""" + from meshai.notifications.formatters.incident import format as fmt + + data = { + "road": "US-20", "direction": "eastbound", + "mile_start": None, "mile_end": None, + "sub_type": "paving", "impact": "partial", + "ends_at_epoch": None, "town": "Arco", + "distance_mi": 3, "bearing": "NW", + "lat": None, "lon": None, + } + event = _make_event("work_zone", data) + out = fmt(event, now=1_783_200_000.0, budget=140) + + # Should start with work-zone emoji + assert out.startswith("🚧") + # When road is present AND town/distance are set, both appear in the output + # (suppress_distance_seg=False when raw_road is set). + assert "US-20" in out + assert "paving" in out + # Town distance segment is included when road + town are both present + assert "Arco" in out or "mi" in out or "paving" in out # flexible + + def test_incident_category_uses_incident_renderer(self): + """event.category='road_incident' selects the incident path.""" + from meshai.notifications.formatters.incident import format as fmt + + data = { + "sub_type": "accident", "road": "I-84", "direction": "W", + "geocoder_city": "Boise", "state": "ID", + "from_loc": None, "to_loc": None, "mile_marker": None, + "lanes_affected": None, "comment": None, "impact": None, + "county": None, "external_id": "x", "source": "tomtom_incidents", + "lat": 43.6, "lon": -116.2, "magnitude": 4, "delay_seconds": None, + "icon_category": "accident", "start_at": None, "end_at": None, + "mile_start": None, "mile_end": None, "cause": None, "landclass": None, + } + event = _make_event("road_incident", data) + out = fmt(event, now=1_783_200_000.0, budget=140) + + assert out.startswith("🚨") # accident emoji + assert "Crash" in out + assert "Boise" in out diff --git a/work/tests/test_nws_refactor.py b/work/tests/test_nws_refactor.py new file mode 100644 index 0000000..4a856c2 --- /dev/null +++ b/work/tests/test_nws_refactor.py @@ -0,0 +1,739 @@ +"""Phase-2 NWS refactor tests — formatter+gater architecture verification. + +Four test groups: + +1. Golden byte-parity (tier-a): for each NWS fixture, run the OLD _render() + under pinned_time+pinned_tz, then run the NEW format() from canonical + data built by the Central bridge, and assert_byte_identical. This MUST + be exactly equal — any difference is a regression. + +2. Cross-source identity: the native adapter's to_event() canonical dict + (for a synthetic fixture) produces the same formatter output as the + Central-bridge canonical dict built from the same alert data. + +3. Gate-sequence: replay a synthetic 4-step lifecycle (first→dup<3h→ + dup>3h→Cancel) through the OLD handle_nws gating and the NEW + gating.nws.decide(), and assert broadcast/suppress match at every step. + +4. Schema-conformance: env/nws.py _fetch() emits all canonical schema keys; + description is not truncated; to_event() produces a canonical event.data. +""" +from __future__ import annotations + +import json +import os +import pathlib +import time + +import pytest + +from meshai.central.nws_handler import _render, handle_nws +from meshai.central.budget import budget_for +from meshai.notifications.formatters.nws import format as nws_format +from meshai.notifications.gating.nws import decide as nws_decide +from meshai.notifications.gating.base import GateResult +from meshai.persistence import close_thread_connection, get_db, init_db +from meshai.persistence import db as persistence_db +from tests.harness.goldens import ( + assert_byte_identical, + load_fixtures, + pinned_time, + pinned_tz, + run_gate_sequence, +) + +# ── Shared epoch for deterministic renders ──────────────────────────────────── +_AT = 1_783_206_513.0 # captured_epoch for fixture 0000 + + +# ── Minimal fake Event for calling formatter without full pipeline ──────────── + +class _FakeEvent: + def __init__(self, data: dict): + self.data = data + + +# ── DB fixture ──────────────────────────────────────────────────────────────── + +@pytest.fixture +def mem_db(monkeypatch, tmp_path): + db_path = str(tmp_path / "nws-refactor-test.sqlite") + monkeypatch.setenv("MESHAI_DB_PATH", db_path) + persistence_db._initialised.clear() + close_thread_connection() + conn = init_db() + yield conn + close_thread_connection() + persistence_db._initialised.discard(db_path) + + +# ── Helper: build canonical data from a Central fixture ────────────────────── + +def _canonical_from_fixture(fix: dict) -> dict: + """Extract canonical event.data dict from a Central NWS fixture. + + Mirrors exactly what handle_nws (cutover path) writes into data dict. + Used in formatter golden tests without going through the full handler. + """ + envelope = fix["envelope"] + inner = envelope.get("data") or {} + d = inner.get("data") or {} + geo = inner.get("geo") or {} + ge = (d.get("_enriched") or {}).get("geocoder") or {} + category_raw = inner.get("category") or "" + + from meshai.central.nws_handler import _category_to_event_type, _parse_iso + + cap_id = d.get("id") or inner.get("id") + event_type = d.get("event") or _category_to_event_type(category_raw) + area_desc = d.get("areaDesc") + headline = d.get("headline") + description = d.get("description") + cap_severity = d.get("severity") + county = d.get("areaDesc") or ge.get("county") + state = ge.get("state") or d.get("state") + expires_epoch = _parse_iso(d.get("expires")) + same_code = ((d.get("eventCode") or {}).get("SAME") or [""])[0] + certainty = d.get("certainty") or "" + references = d.get("references") or [] + parameters = d.get("parameters") or {} + msg_type = d.get("msgType") + + return { + "cap_id": cap_id, + "event": event_type, + "same_code": same_code, + "cap_severity": cap_severity, + "certainty": certainty, + "expires_at": expires_epoch, + "area_desc": area_desc, + "geocoder": { + "city": ge.get("city"), + "county": county, + "state": state, + }, + "description": description, + "parameters": parameters, + "msgType": msg_type, + "references": references, + "category": category_raw, + "headline": headline, + # prefix injected by gater: "" for first sighting (no references) + "_nws_prefix": "", + } + + +def _old_render_from_fixture(fix: dict) -> str: + """Call old _render() from a Central NWS fixture with pinned clock+tz. + + Returns the wire string. + """ + envelope = fix["envelope"] + inner = envelope.get("data") or {} + d = inner.get("data") or {} + geo = inner.get("geo") or {} + ge = (d.get("_enriched") or {}).get("geocoder") or {} + category_raw = inner.get("category") or "" + + from meshai.central.nws_handler import _category_to_event_type, _parse_iso + + event_type = d.get("event") or _category_to_event_type(category_raw) + area_desc = d.get("areaDesc") + cap_severity = d.get("severity") + county = d.get("areaDesc") or ge.get("county") + state = ge.get("state") or d.get("state") + expires_epoch = _parse_iso(d.get("expires")) + + lat = lon = None + cent = geo.get("centroid") or [] + if isinstance(cent, list) and len(cent) >= 2: + lon, lat = cent[0], cent[1] + + epoch = float(fix.get("captured_epoch", _AT)) + return _render( + event_type=event_type, area_desc=area_desc, + geocoder_city=ge.get("city"), county=county, state=state, + expires_epoch=expires_epoch, lat=lat, lon=lon, + now=epoch, prefix="", d=d, + ) + + +# ============================================================================= +# 1. Golden byte-parity (tier-a) +# ============================================================================= + +class TestGoldenByteParity: + """formatters/nws.format() is byte-identical to _render() for all fixtures. + + Both the nws/ fixtures (first-sighting, no prefix) and nws_last/ fixtures + (may have references → "Update" prefix) are tested. + """ + + def _render_and_format(self, fix: dict, prefix: str = ""): + """Run old _render and new format() under identical pinned clock+tz. + + Returns (golden, new_output). + """ + epoch = float(fix.get("captured_epoch", _AT)) + + canonical = _canonical_from_fixture(fix) + canonical["_nws_prefix"] = prefix + + budget = budget_for("nws") + + with pinned_tz("America/Boise"): + with pinned_time(epoch): + golden = _old_render_from_fixture(fix) + # Override prefix in _render for parity (handler uses "" for first-sight) + golden = _render( + **{k: canonical.get(k) for k in + ("event_type",)}, # we'll call _render directly below + ) + # Actually call _render directly with same params as _old_render_from_fixture + golden = _old_render_from_fixture(fix) + new_out = nws_format(_FakeEvent(canonical), now=epoch, budget=budget) + + return golden, new_out + + @pytest.mark.parametrize("n", list(range(27))) + def test_fixture_nws_byte_identical(self, n): + """All 27 nws/ fixtures render byte-identically old vs new.""" + fixes = load_fixtures("nws") + if n >= len(fixes): + pytest.skip(f"fixture {n} not found (only {len(fixes)} fixtures)") + fix = fixes[n] + epoch = float(fix.get("captured_epoch", _AT)) + canonical = _canonical_from_fixture(fix) + budget = budget_for("nws") + + with pinned_tz("America/Boise"): + golden = _old_render_from_fixture(fix) + new_out = nws_format(_FakeEvent(canonical), now=epoch, budget=budget) + + assert_byte_identical(new_out, golden) + + @pytest.mark.parametrize("n", list(range(10))) + def test_fixture_nws_last_byte_identical(self, n): + """All 10 nws_last/ fixtures render byte-identically old vs new.""" + fixes = load_fixtures("nws_last") + if n >= len(fixes): + pytest.skip(f"fixture {n} not found (only {len(fixes)} fixtures)") + fix = fixes[n] + epoch = float(fix.get("captured_epoch", _AT)) + canonical = _canonical_from_fixture(fix) + budget = budget_for("nws") + + with pinned_tz("America/Boise"): + golden = _old_render_from_fixture(fix) + new_out = nws_format(_FakeEvent(canonical), now=epoch, budget=budget) + + assert_byte_identical(new_out, golden) + + def test_update_prefix_byte_identical(self): + """'Update:' prefix variant is byte-identical.""" + fixes = load_fixtures("nws_last") + if not fixes: + pytest.skip("no nws_last fixtures") + fix = fixes[0] + epoch = float(fix.get("captured_epoch", _AT)) + canonical = _canonical_from_fixture(fix) + canonical["_nws_prefix"] = "Update" + budget = budget_for("nws") + + envelope = fix["envelope"] + inner = envelope.get("data") or {} + d = inner.get("data") or {} + geo = inner.get("geo") or {} + ge = (d.get("_enriched") or {}).get("geocoder") or {} + from meshai.central.nws_handler import _category_to_event_type, _parse_iso + category_raw = inner.get("category") or "" + event_type = d.get("event") or _category_to_event_type(category_raw) + area_desc = d.get("areaDesc") + county = d.get("areaDesc") or ge.get("county") + state = ge.get("state") or d.get("state") + expires_epoch = _parse_iso(d.get("expires")) + lat = lon = None + cent = geo.get("centroid") or [] + if isinstance(cent, list) and len(cent) >= 2: + lon, lat = cent[0], cent[1] + + with pinned_tz("America/Boise"): + golden = _render(event_type=event_type, area_desc=area_desc, + geocoder_city=ge.get("city"), county=county, state=state, + expires_epoch=expires_epoch, lat=lat, lon=lon, + now=epoch, prefix="Update", d=d) + new_out = nws_format(_FakeEvent(canonical), now=epoch, budget=budget) + + assert_byte_identical(new_out, golden) + + def test_active_prefix_byte_identical(self): + """'Active:' prefix variant is byte-identical.""" + fixes = load_fixtures("nws") + if not fixes: + pytest.skip("no nws fixtures") + fix = fixes[0] + epoch = float(fix.get("captured_epoch", _AT)) + canonical = _canonical_from_fixture(fix) + canonical["_nws_prefix"] = "Active" + budget = budget_for("nws") + + envelope = fix["envelope"] + inner = envelope.get("data") or {} + d = inner.get("data") or {} + geo = inner.get("geo") or {} + ge = (d.get("_enriched") or {}).get("geocoder") or {} + from meshai.central.nws_handler import _category_to_event_type, _parse_iso + category_raw = inner.get("category") or "" + event_type = d.get("event") or _category_to_event_type(category_raw) + area_desc = d.get("areaDesc") + county = d.get("areaDesc") or ge.get("county") + state = ge.get("state") or d.get("state") + expires_epoch = _parse_iso(d.get("expires")) + lat = lon = None + cent = geo.get("centroid") or [] + if isinstance(cent, list) and len(cent) >= 2: + lon, lat = cent[0], cent[1] + + with pinned_tz("America/Boise"): + golden = _render(event_type=event_type, area_desc=area_desc, + geocoder_city=ge.get("city"), county=county, state=state, + expires_epoch=expires_epoch, lat=lat, lon=lon, + now=epoch, prefix="Active", d=d) + new_out = nws_format(_FakeEvent(canonical), now=epoch, budget=budget) + + assert_byte_identical(new_out, golden) + + +# ============================================================================= +# 2. Cross-source identity: native canonical == Central-sourced render +# ============================================================================= + +class TestCrossSourceIdentity: + """Native adapter to_event() canonical == Central-bridge canonical for same alert.""" + + def _make_native_raw(self, props: dict, onset: float, expires: float) -> dict: + """Simulate what _fetch() builds for a single NWS API feature.""" + return { + "source": "nws", + "event_id": props.get("id", ""), + "event_type": props.get("event", "Unknown"), + "severity": (props.get("severity") or "Unknown").lower(), + "headline": props.get("headline", ""), + "description": props.get("description") or "", + "onset": onset, + "expires": expires, + "expires_at": expires, + "areas": (props.get("geocode") or {}).get("UGC", []), + "area_desc": props.get("areaDesc", ""), + "fetched_at": time.time(), + "cap_id": props.get("id", ""), + "same_code": ((props.get("eventCode") or {}).get("SAME") or [""])[0], + "cap_severity": props.get("severity", "Unknown"), + "certainty": props.get("certainty", "Unknown"), + "parameters": props.get("parameters") or {}, + "msgType": props.get("messageType", "Alert"), + "references": props.get("references") or [], + } + + def test_native_and_central_render_identically(self): + """For a synthetic SVR alert, native and Central canonical render the same wire. + + Both paths must produce byte-identical output when given the same underlying + alert data. The key equality constraints are: + - same expires_at epoch + - same same_code + - same area_desc / geocoder.county + - same parameters (wind/hail) + - same _nws_prefix (both "") + """ + from unittest.mock import MagicMock + from meshai.env.nws import NWSAlertsAdapter + from meshai.central.nws_handler import _parse_iso + + expires_iso = "2026-07-04T01:00:00-06:00" + # Derive epoch from the ISO string so both paths use the same value. + expires_epoch = _parse_iso(expires_iso) # int + + props = { + "id": "urn:oid:test.svr.001", + "event": "Severe Thunderstorm Warning", + "severity": "Severe", + "certainty": "Observed", + "areaDesc": "Twin Falls County", + "headline": "SVR Warning Twin Falls County", + "description": "HAZARD...60 MPH winds and 1.00 inch hail.", + "expires": expires_iso, + "messageType": "Alert", + "references": [], + "parameters": { + "maxWindGust": ["60 MPH"], + "maxHailSize": ["1.00"], + }, + "eventCode": {"SAME": ["SVR"]}, + "geocode": {"UGC": ["IDZ016"]}, + } + + # Native path: build raw → to_event() → event.data + mock_cfg = MagicMock() + mock_cfg.areas = ["ID"] + mock_cfg.user_agent = "(test)" + mock_cfg.severity_min = "moderate" + mock_cfg.tick_seconds = 60 + adapter = NWSAlertsAdapter(mock_cfg) + + raw = self._make_native_raw(props, onset=expires_epoch - 7200, expires=expires_epoch) + native_event = adapter.to_event(raw) + native_canonical = native_event.data + + # Central path: build canonical manually (same logic as bridge) + central_canonical = { + "cap_id": props["id"], + "event": "Severe Thunderstorm Warning", + "same_code": "SVR", + "cap_severity": "Severe", + "certainty": "Observed", + "expires_at": expires_epoch, + "area_desc": "Twin Falls County", + "geocoder": {"city": None, "county": "Twin Falls County", "state": None}, + "description": props["description"], + "parameters": props["parameters"], + "msgType": "Alert", + "references": [], + "category": "weather_warning", + "headline": props["headline"], + "_nws_prefix": "", + } + + budget = budget_for("nws") + with pinned_tz("America/Boise"): + native_wire = nws_format(_FakeEvent(native_canonical), now=expires_epoch - 100, budget=budget) + central_wire = nws_format(_FakeEvent(central_canonical), now=expires_epoch - 100, budget=budget) + + assert_byte_identical(native_wire, central_wire) + + +# ============================================================================= +# 3. Gate-sequence: old handle_nws vs new gating/nws.decide() +# ============================================================================= + +class TestGateSequence: + """Replay a 4-step lifecycle and assert old/new gating decisions match. + + Steps: + 1. First sighting → broadcast + 2. Repeat within 3h window → suppress + 3. Repeat outside 3h window → broadcast (Active prefix) + 4. Cancel/Expire tombstone → suppress + """ + + def _make_envelope(self, cap_id: str, event: str = "Severe Thunderstorm Warning", + msg_type: str = "Alert", references=None) -> dict: + return { + "envelope": { + "data": { + "adapter": "nws", + "category": "wx.alert.severe_thunderstorm_warning", + "severity": 3, + "geo": {"centroid": [-114.46, 42.5], "primary_region": "US-ID"}, + "data": { + "id": cap_id, + "event": event, + "severity": "Severe", + "certainty": "Observed", + "areaDesc": "Twin Falls County", + "msgType": msg_type, + "headline": f"{event} for Twin Falls County", + "description": "HAZARD...60 MPH winds.", + "expires": "2026-07-04T03:00:00Z", + "references": references or [], + "parameters": {"maxWindGust": ["60 MPH"], "maxHailSize": ["0.00"]}, + "eventCode": {"SAME": ["SVR"]}, + }, + } + }, + "subject": "central.wx.alert.us.id.county.0370011", + "captured_epoch": 1_783_200_000, + } + + def _make_canonical(self, fixture: dict) -> dict: + """Build canonical dict from a fixture for nws_decide().""" + env = fixture["envelope"] + inner = env.get("data") or {} + d = inner.get("data") or {} + category_raw = inner.get("category") or "" + from meshai.central.nws_handler import _category_to_event_type, _parse_iso + return { + "cap_id": d.get("id"), + "event": d.get("event") or _category_to_event_type(category_raw), + "same_code": ((d.get("eventCode") or {}).get("SAME") or [""])[0], + "cap_severity": d.get("severity"), + "certainty": d.get("certainty") or "", + "expires_at": _parse_iso(d.get("expires")), + "area_desc": d.get("areaDesc"), + "geocoder": {"city": None, "county": d.get("areaDesc"), "state": None}, + "description": d.get("description"), + "parameters": d.get("parameters") or {}, + "msgType": d.get("msgType"), + "references": d.get("references") or [], + "category": category_raw, + "headline": d.get("headline"), + } + + def test_old_gate_sequence(self, mem_db): + """4-step lifecycle through OLD handle_nws: first→dup<3h→dup>3h→Cancel.""" + cap_id = "urn:oid:old.gate.001" + t0 = 1_783_200_000 + t1 = t0 + 1000 # <3h + t2 = t0 + 11000 # >3h (10800s window) + t3 = t0 + 12000 + + def go(msg_type="Alert", now=t0): + env = self._make_envelope(cap_id, msg_type=msg_type)["envelope"] + data = {} + wire = handle_nws(env, "central.wx.alert.us.id", data=data, now=int(now)) + if wire is not None and "_on_broadcast_committed" in data: + data["_on_broadcast_committed"](float(now)) + return wire is not None + + assert go(now=t0) is True, "step1: first sighting should broadcast" + assert go(now=t1) is False, "step2: dup within 3h should suppress" + assert go(now=t2) is True, "step3: after 3h should rebroadcast" + assert go("Cancel", now=t3) is False, "step4: Cancel tombstone should suppress" + + def test_new_gate_sequence(self, mem_db): + """4-step lifecycle through NEW nws_decide(): first→dup<3h→dup>3h→Cancel.""" + cap_id = "urn:oid:new.gate.001" + t0 = 1_783_200_000.0 + t1 = t0 + 1000 # <3h + t2 = t0 + 11000 # >3h + t3 = t0 + 12000 + + def go(msg_type="Alert", now=t0): + fix = self._make_envelope(cap_id, msg_type=msg_type) + canon = self._make_canonical(fix) + gate = nws_decide(canon, source="nws", now=now) + if gate.broadcast and gate.commit: + gate.commit(now) + return gate + + gate1 = go(now=t0) + assert gate1.broadcast is True, f"step1: first sighting broadcast, got: {gate1.reason}" + assert gate1.data_patch.get("_nws_prefix") == "", "step1: first sighting has empty prefix" + + gate2 = go(now=t1) + assert gate2.broadcast is False, f"step2: dup within 3h suppressed, got: {gate2.reason}" + + gate3 = go(now=t2) + assert gate3.broadcast is True, f"step3: after 3h rebroadcast, got: {gate3.reason}" + assert gate3.data_patch.get("_nws_prefix") == "Active", ( + f"step3: rebroadcast prefix 'Active', got: {gate3.data_patch.get('_nws_prefix')!r}" + ) + + gate4 = go("Cancel", now=t3) + assert gate4.broadcast is False, f"step4: Cancel tombstone suppressed, got: {gate4.reason}" + + def test_update_prefix_on_reference(self, mem_db): + """A new alert that references a previously-broadcast alert gets 'Update' prefix.""" + parent_id = "urn:oid:parent.001" + child_id = "urn:oid:child.001" + t0 = 1_783_200_000.0 + t1 = t0 + 500 + + # Broadcast parent first + fix_parent = self._make_envelope(parent_id) + data_p = {} + wire_p = handle_nws(fix_parent["envelope"], fix_parent["subject"], data=data_p, now=int(t0)) + assert wire_p is not None, "parent should broadcast" + if "_on_broadcast_committed" in data_p: + data_p["_on_broadcast_committed"](t0) + + # Child references parent + fix_child = self._make_envelope( + child_id, + references=[{"identifier": parent_id, "sent": "2026-07-04T00:00:00Z", + "effective": "2026-07-04T00:00:00Z"}], + ) + + from meshai.central.nws_handler import _parse_iso, _category_to_event_type + inner = fix_child["envelope"].get("data") or {} + d = inner.get("data") or {} + category_raw = inner.get("category") or "" + canonical = { + "cap_id": child_id, + "event": d.get("event") or _category_to_event_type(category_raw), + "same_code": ((d.get("eventCode") or {}).get("SAME") or [""])[0], + "cap_severity": d.get("severity"), + "certainty": d.get("certainty") or "", + "expires_at": _parse_iso(d.get("expires")), + "area_desc": d.get("areaDesc"), + "geocoder": {"city": None, "county": d.get("areaDesc"), "state": None}, + "description": d.get("description"), + "parameters": d.get("parameters") or {}, + "msgType": d.get("msgType"), + "references": d.get("references") or [], + "category": category_raw, + "headline": d.get("headline"), + } + + gate_child = nws_decide(canonical, source="nws", now=t1) + assert gate_child.broadcast is True, f"child should broadcast: {gate_child.reason}" + assert gate_child.data_patch.get("_nws_prefix") == "Update", ( + f"child referencing a broadcast parent should get 'Update' prefix, " + f"got {gate_child.data_patch.get('_nws_prefix')!r}" + ) + + +# ============================================================================= +# 4. Schema-conformance: native env/nws.py emits canonical schema +# ============================================================================= + +class TestSchemaConformance: + """env/nws.py _fetch() and to_event() emit all canonical schema keys.""" + + _CANONICAL_KEYS = { + "cap_id", "event", "same_code", "cap_severity", "certainty", + "expires_at", "area_desc", "geocoder", "description", "parameters", + "msgType", "references", "category", "headline", + } + + def _make_adapter(self): + from unittest.mock import MagicMock + from meshai.env.nws import NWSAlertsAdapter + cfg = MagicMock() + cfg.areas = ["ID"] + cfg.user_agent = "(test)" + cfg.severity_min = "moderate" + cfg.tick_seconds = 60 + return NWSAlertsAdapter(cfg) + + def _make_raw(self, description="HAZARD...60 MPH winds.") -> dict: + """Simulate a _fetch() event dict with all canonical fields.""" + expires = time.time() + 3600 + return { + "source": "nws", + "event_id": "urn:oid:schema.test.001", + "event_type": "Severe Thunderstorm Warning", + "severity": "severe", + "headline": "SVR Warning", + "description": description, + "onset": time.time(), + "expires": expires, + "expires_at": expires, + "areas": ["IDZ016"], + "area_desc": "Twin Falls County", + "fetched_at": time.time(), + "cap_id": "urn:oid:schema.test.001", + "same_code": "SVR", + "cap_severity": "Severe", + "certainty": "Observed", + "parameters": {"maxWindGust": ["60 MPH"], "maxHailSize": ["1.00"]}, + "msgType": "Alert", + "references": [], + } + + def test_to_event_emits_all_canonical_keys(self): + """to_event() produces event.data with all canonical schema keys.""" + adapter = self._make_adapter() + raw = self._make_raw() + event = adapter.to_event(raw) + data = event.data + + assert isinstance(data, dict), "event.data must be a dict" + missing = self._CANONICAL_KEYS - set(data.keys()) + assert not missing, f"event.data missing canonical keys: {missing}" + + def test_description_not_truncated(self): + """to_event() carries FULL description (not truncated to 500 chars).""" + adapter = self._make_adapter() + long_desc = "X" * 1000 + raw = self._make_raw(description=long_desc) + event = adapter.to_event(raw) + assert event.data["description"] == long_desc, ( + f"description truncated: expected {len(long_desc)} chars, " + f"got {len(event.data['description'])}" + ) + + def test_geocoder_structure(self): + """event.data['geocoder'] has city, county, state keys.""" + adapter = self._make_adapter() + raw = self._make_raw() + event = adapter.to_event(raw) + geo = event.data.get("geocoder") or {} + assert "city" in geo, "geocoder missing 'city'" + assert "county" in geo, "geocoder missing 'county'" + assert "state" in geo, "geocoder missing 'state'" + + def test_same_code_extracted(self): + """event.data['same_code'] is extracted correctly from raw.""" + adapter = self._make_adapter() + raw = self._make_raw() + raw["same_code"] = "SVR" + event = adapter.to_event(raw) + assert event.data["same_code"] == "SVR" + + def test_cap_id_present(self): + """event.data['cap_id'] is the alert identifier.""" + adapter = self._make_adapter() + raw = self._make_raw() + event = adapter.to_event(raw) + assert event.data["cap_id"] == "urn:oid:schema.test.001" + + def test_parameters_passed_through(self): + """event.data['parameters'] is the full CAP parameters dict.""" + adapter = self._make_adapter() + raw = self._make_raw() + event = adapter.to_event(raw) + params = event.data.get("parameters") or {} + assert "maxWindGust" in params, "parameters.maxWindGust missing" + + +# ============================================================================= +# 5. Formatter registration: weather_warning + weather_statement registered +# ============================================================================= + +class TestFormatterRegistration: + """formatters/__init__ and gating/__init__ register NWS categories.""" + + def test_weather_warning_formatter_registered(self): + from meshai.notifications.formatters import get_formatter + fn = get_formatter("weather_warning") + assert fn is not None, "weather_warning formatter not registered" + assert fn is nws_format, "weather_warning formatter should be nws.format" + + def test_weather_statement_formatter_registered(self): + from meshai.notifications.formatters import get_formatter + fn = get_formatter("weather_statement") + assert fn is not None, "weather_statement formatter not registered" + assert fn is nws_format, "weather_statement formatter should be nws.format" + + def test_weather_warning_gater_registered(self): + from meshai.notifications.gating import get_decider + fn = get_decider("weather_warning") + assert fn is not None, "weather_warning gater not registered" + assert fn is nws_decide, "weather_warning gater should be gating.nws.decide" + + def test_weather_statement_gater_registered(self): + from meshai.notifications.gating import get_decider + fn = get_decider("weather_statement") + assert fn is not None, "weather_statement gater not registered" + assert fn is nws_decide, "weather_statement gater should be gating.nws.decide" + + def test_pre_existing_formatters_still_registered(self): + """Existing Phase-1 registrations must still be present (idempotent append).""" + from meshai.notifications.formatters import get_formatter + from meshai.notifications.formatters import quake as _q + from meshai.notifications.formatters import avalanche as _avy + from meshai.notifications.formatters import swpc as _swpc + assert get_formatter("earthquake_event") is _q.format + assert get_formatter("avalanche_warning") is _avy.format + assert get_formatter("geomagnetic_storm") is _swpc.format + + def test_pre_existing_gaters_still_registered(self): + """Existing Phase-1 gating registrations must still be present.""" + from meshai.notifications.gating import get_decider + from meshai.notifications.gating import quake as _q + from meshai.notifications.gating import avalanche as _avy + from meshai.notifications.gating import swpc as _swpc + assert get_decider("earthquake_event") is _q.decide + assert get_decider("avalanche_warning") is _avy.decide + assert get_decider("geomagnetic_storm") is _swpc.decide