fix(coverage): stop out-of-region NWS alerts (LA leak) — tighten state select + zone scoping

Two defects let an LA Heat Advisory broadcast under a southern-Idaho box:
(1) states_for_bbox included states that merely clipped the box corner
(CA/NV/UT/WY) → area=CA fetched California alerts. Now require >=0.25deg
overlap in BOTH dimensions via _significant_overlap (also applied to
avalanche_centers_for_bbox to prevent analogous over-inclusion there).
(2) _in_coverage kept any zone-only alert (no polygon centroid). Now scope
zone-only alerts by their UGC state prefix and fail closed when no zone
info is available — an unlocatable alert must not broadcast under an
active coverage box.
Result: only alerts genuinely within the coverage box broadcast.
Includes the LA-repro regression test (CAZ041 zone-only → False).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Matt Johnson 2026-07-06 18:00:04 +00:00
commit 44344ad6ba
5 changed files with 269 additions and 18 deletions

View file

@ -56,6 +56,21 @@ def point_in_bbox(lat: float, lon: float, bbox) -> bool:
return south <= lat <= north and west <= lon <= east
def _significant_overlap(a: list, b: list, min_deg: float = 0.25) -> bool:
"""True iff boxes a,b [W,S,E,N] overlap by at least min_deg in BOTH lon and lat.
A mere corner or edge clip (tiny overlap in one dimension) does not count
as meaningful coverage. Default of 0.25 degrees is intentionally
conservative: it prevents states that barely nick the coverage box (e.g.
CA clipping 0.04 deg in latitude, or WY clipping 0.12 deg in longitude)
from polluting the NWS area query, while still capturing genuinely adjacent
states like OR that overlap by ~0.5 deg or more in every dimension.
"""
ow = min(a[2], b[2]) - max(a[0], b[0]) # overlap width (lon)
oh = min(a[3], b[3]) - max(a[1], b[1]) # overlap height (lat)
return ow >= min_deg and oh >= min_deg
def bbox_intersects(a, b) -> bool:
"""Return True iff two [W, S, E, N] bounding boxes overlap (share any area).
@ -203,14 +218,17 @@ US_STATE_BBOXES: dict[str, list[float]] = {
def states_for_bbox(bbox) -> list[str]:
"""Return sorted state codes whose bbox intersects the given coverage bbox.
"""Return sorted state codes whose bbox overlaps the given coverage bbox.
Uses bbox_intersects; a state whose bounding rectangle overlaps the coverage
rectangle is included. Deduped and sorted alphabetically.
Uses _significant_overlap (>= 0.25 deg in both lon and lat) rather than
a bare intersection test. This prevents states that merely clip a corner
of the coverage box from being included in NWS area queries the root
cause of the CA/LA-alert-under-Idaho-box defect. Deduped and sorted
alphabetically.
"""
result = []
for code, state_bbox in US_STATE_BBOXES.items():
if bbox_intersects(bbox, state_bbox):
if _significant_overlap(bbox, state_bbox):
result.append(code)
return sorted(set(result))
@ -258,10 +276,14 @@ AVALANCHE_CENTER_BBOXES: dict[str, list[float]] = {
def avalanche_centers_for_bbox(bbox) -> list[str]:
"""Return sorted center IDs whose area intersects the given coverage bbox."""
"""Return sorted center IDs whose area overlaps the given coverage bbox.
Uses _significant_overlap (same conservative threshold as states_for_bbox)
to avoid including avalanche centers that merely clip the coverage box corner.
"""
result = []
for center_id, center_bbox in AVALANCHE_CENTER_BBOXES.items():
if bbox_intersects(bbox, center_bbox):
if _significant_overlap(bbox, center_bbox):
result.append(center_id)
return sorted(set(result))

View file

@ -177,18 +177,32 @@ class NWSAlertsAdapter:
Rules:
- If no coverage bbox is set, always keep.
- If the event has a numeric lat/lon centroid, drop it when outside the box.
- If the event has no centroid (lat/lon absent or None), keep it the
state area= filter already scopes it; we can't box-filter without coords.
- Polygon alerts (lat/lon centroid present): authoritative geographic
check drop when the centroid lies outside the coverage box.
- Zone-only alerts (no polygon / no centroid): scope by UGC zone STATE.
Keep if at least one affected UGC zone belongs to a state the coverage
box covers (self._areas). Fail CLOSED when no zone info is available
an unlocatable alert must NOT broadcast under an active coverage box.
"""
if not self._coverage_bbox:
return True
lat = event.get("lat")
lon = event.get("lon")
if lat is None or lon is None:
return True # no geometry — pass through
from meshai.coverage import point_in_bbox
return point_in_bbox(lat, lon, self._coverage_bbox)
# Polygon alerts: authoritative geographic check on the centroid.
if lat is not None and lon is not None:
from meshai.coverage import point_in_bbox
return point_in_bbox(lat, lon, self._coverage_bbox)
# Zone-only alerts (no polygon/centroid): scope by the UGC zone STATE.
# Keep only if at least one affected zone is in a state the box covers.
# Fail CLOSED if no zone info — an unlocatable alert must not broadcast.
ugcs = event.get("areas") or []
if not ugcs:
return False
area_states = {a.upper() for a in (self._areas or [])}
for ugc in ugcs:
if isinstance(ugc, str) and len(ugc) >= 2 and ugc[:2].upper() in area_states:
return True
return False
def _fetch(self) -> bool:
"""Fetch alerts from NWS API.

View file

@ -286,3 +286,95 @@ def test_map_nws_severity_moderate_to_routine(adapter):
def test_map_nws_severity_minor_to_routine(adapter):
"""Minor NWS severity maps to routine."""
assert adapter._map_nws_severity("minor") == "routine"
# ============================================================
# _in_coverage TESTS — coverage-bbox filtering (LA leak fix)
# ============================================================
# Southern-Idaho coverage box used in all _in_coverage tests.
_IDAHO_COVERAGE_BBOX = [-116.993, 41.959, -110.984, 44.095]
@pytest.fixture
def adapter_with_coverage(mock_config):
"""NWSAlertsAdapter with a southern-Idaho coverage dict (areas=ID,OR)."""
coverage = {
"areas": ["ID", "OR"],
"bbox": _IDAHO_COVERAGE_BBOX,
}
return NWSAlertsAdapter(mock_config, coverage=coverage)
def test_in_coverage_no_bbox_always_true(mock_config):
"""Without a coverage bbox every event passes through."""
adapter_no_cov = NWSAlertsAdapter(mock_config)
event = {"lat": 34.05, "lon": -118.24, "areas": ["CAZ041"]}
assert adapter_no_cov._in_coverage(event) is True
def test_in_coverage_la_repro_zone_only_dropped(adapter_with_coverage):
"""LA-leak regression: a CAZ041 zone-only alert must be DROPPED.
This is the exact scenario that caused the bug: a Heat Advisory for
Los Angeles carried no polygon (zone-based), so the old code returned True
unconditionally for any event lacking lat/lon. The fixed code checks the
UGC state prefix instead and rejects CA when coverage is ID/OR-only.
"""
event = {"lat": None, "lon": None, "areas": ["CAZ041", "CAZ042"]}
assert adapter_with_coverage._in_coverage(event) is False, (
"CAZ* (California) zone-only alert must be DROPPED under Idaho coverage"
)
def test_in_coverage_idaho_zone_only_kept(adapter_with_coverage):
"""An Idaho zone-only alert (IDZ016) must be kept."""
event = {"lat": None, "lon": None, "areas": ["IDZ016"]}
assert adapter_with_coverage._in_coverage(event) is True
def test_in_coverage_oregon_zone_only_kept(adapter_with_coverage):
"""An Oregon zone-only alert (ORZ601) must be kept (OR is in coverage)."""
event = {"lat": None, "lon": None, "areas": ["ORZ601"]}
assert adapter_with_coverage._in_coverage(event) is True
def test_in_coverage_polygon_centroid_inside_kept(adapter_with_coverage):
"""A polygon alert whose centroid is inside the Idaho bbox is kept."""
# Twin Falls, ID — clearly inside the coverage box
event = {"lat": 42.56, "lon": -114.46, "areas": ["IDZ016"]}
assert adapter_with_coverage._in_coverage(event) is True
def test_in_coverage_polygon_centroid_outside_dropped(adapter_with_coverage):
"""A polygon alert whose centroid is outside the bbox is dropped.
Los Angeles centroid (34.05, -118.24) is far south-west of the Idaho box.
"""
event = {"lat": 34.05, "lon": -118.24, "areas": ["CAZ041"]}
assert adapter_with_coverage._in_coverage(event) is False
def test_in_coverage_no_areas_no_centroid_dropped(adapter_with_coverage):
"""Fail closed: an event with no UGC zones and no centroid must be DROPPED."""
event = {"areas": [], "lat": None, "lon": None}
assert adapter_with_coverage._in_coverage(event) is False
def test_in_coverage_missing_areas_key_dropped(adapter_with_coverage):
"""Fail closed: event with no 'areas' key and no centroid is dropped."""
event = {}
assert adapter_with_coverage._in_coverage(event) is False
def test_in_coverage_multi_zone_one_match_kept(adapter_with_coverage):
"""If any UGC zone is in-state the event is kept (at least one match)."""
# Mix of out-of-state and in-state zones (e.g. a boundary advisory)
event = {"lat": None, "lon": None, "areas": ["NVZ001", "IDZ016", "UTZ001"]}
assert adapter_with_coverage._in_coverage(event) is True
def test_in_coverage_ugc_case_insensitive(adapter_with_coverage):
"""UGC state prefix matching must be case-insensitive."""
event = {"lat": None, "lon": None, "areas": ["idz016"]}
assert adapter_with_coverage._in_coverage(event) is True

View file

@ -11,6 +11,7 @@ import pytest
from meshai.coverage import (
AVALANCHE_CENTER_BBOXES,
US_STATE_BBOXES,
_significant_overlap,
arcgis_envelope,
avalanche_centers_for_bbox,
bbox_intersects,
@ -539,3 +540,104 @@ def test_high_precision_grid_points_rounded():
for i, (plat, plon) in enumerate(result["points"]):
_assert_max_6dp(plat, f"traffic.points[{i}].lat")
_assert_max_6dp(plon, f"traffic.points[{i}].lon")
# ===========================================================================
# _significant_overlap
# ===========================================================================
# Southern-Idaho coverage box from the LA-leak bug report (high-precision form
# as produced by a Leaflet map click — used in several tests below).
IDAHO_BUG_BOX = [
-116.99340820312501,
41.95949009892467,
-110.98388671875001,
44.09547572946637,
]
def test_significant_overlap_solid_overlap_true():
"""Two boxes that genuinely overlap return True."""
a = [-116.5, 42.0, -112.0, 44.0]
b = [-117.3, 41.9, -111.0, 49.1] # ID bbox — fully contains a
assert _significant_overlap(a, b) is True
def test_significant_overlap_corner_clip_false():
"""A corner clip smaller than min_deg in one dimension returns False."""
# CA bbox north=42.0, coverage box south=41.959 → lat overlap ~0.04 deg
ca_bbox = [-124.5, 32.5, -114.1, 42.0]
assert _significant_overlap(IDAHO_BUG_BOX, ca_bbox) is False
def test_significant_overlap_lon_edge_clip_false():
"""A lon-only edge clip smaller than min_deg returns False."""
# WY bbox west=-111.1, coverage box east=-110.984 → lon overlap ~0.12 deg
wy_bbox = [-111.1, 40.9, -104.0, 45.1]
assert _significant_overlap(IDAHO_BUG_BOX, wy_bbox) is False
def test_significant_overlap_both_small_false():
"""Both dimensions below min_deg → False."""
a = [-116.0, 42.0, -115.9, 42.09] # tiny box
b = [-115.95, 42.05, -115.85, 42.12]
# overlap_lon = -115.9 - (-115.95) = 0.05 < 0.25 → False
assert _significant_overlap(a, b) is False
def test_significant_overlap_custom_min_deg():
"""Caller can supply a tighter or looser min_deg."""
a = [-116.5, 42.0, -112.0, 44.0]
# NV bbox lat overlap ~0.14 deg — excluded at 0.25 but included at 0.10
nv_bbox = [-120.0, 35.0, -114.0, 42.1]
assert _significant_overlap(a, nv_bbox, min_deg=0.10) is True
assert _significant_overlap(a, nv_bbox, min_deg=0.25) is False
def test_significant_overlap_symmetric():
"""Order of arguments should not matter."""
a = [-116.5, 42.0, -112.0, 44.0]
b = [-117.3, 41.9, -111.0, 49.1]
assert _significant_overlap(a, b) == _significant_overlap(b, a)
# ===========================================================================
# states_for_bbox — LA leak regression + significant-overlap behavior
# ===========================================================================
def test_la_bug_box_yields_id_and_or_only():
"""Regression: the southern-Idaho box that caused the LA-alert leak must
return only ID and OR not CA, NV, UT, or WY."""
states = states_for_bbox(IDAHO_BUG_BOX)
assert states == ["ID", "OR"], (
f"Expected ['ID', 'OR'], got {states}"
"CA/NV/UT/WY must be excluded (corner-clip, not meaningful overlap)"
)
def test_la_bug_box_ca_not_present():
"""CA must NOT appear — its inclusion caused real LA alerts to broadcast."""
assert "CA" not in states_for_bbox(IDAHO_BUG_BOX)
def test_la_bug_box_nv_ut_wy_not_present():
"""NV, UT, WY must NOT appear — they clip only a thin sliver of the box."""
states = states_for_bbox(IDAHO_BUG_BOX)
for dropped in ("NV", "UT", "WY"):
assert dropped not in states, f"{dropped} unexpectedly in {states}"
def test_la_bug_box_id_and_or_present():
"""ID and OR must appear — they have genuine significant overlap."""
states = states_for_bbox(IDAHO_BUG_BOX)
assert "ID" in states
assert "OR" in states
def test_genuine_two_state_box_returns_both():
"""A box that genuinely straddles ID and OR with >0.25 deg overlap returns both."""
# ID_OR_BOX is [-118.0, 43.5, -115.0, 46.0] — overlaps both by >1 degree
states = states_for_bbox(ID_OR_BOX)
assert "ID" in states
assert "OR" in states

View file

@ -186,14 +186,35 @@ def test_nws_in_coverage_outside_bbox_dropped():
assert adapter._in_coverage(event) is False
def test_nws_in_coverage_no_coords_kept():
"""Alert with no lat/lon is always kept (can't filter without coords)."""
def test_nws_in_coverage_zone_only_no_areas_dropped():
"""Zone-only alert with no UGC areas and no centroid is dropped (fail closed).
The old behavior (always keep when no lat/lon) was the root cause of the
LA-alert-under-Idaho-box defect. After the fix, zone-only alerts are
scoped by their UGC state prefix; if there are no UGC codes at all the
event cannot be located and must be dropped.
"""
adapter = _make_nws_with_bbox(IDAHO_BOX)
event = {} # no lat/lon keys at all
event = {} # no lat/lon keys, no areas
assert adapter._in_coverage(event) is False
event_none = {"lat": None, "lon": None, "areas": []} # explicit empty areas
assert adapter._in_coverage(event_none) is False
def test_nws_in_coverage_zone_only_in_state_kept():
"""Zone-only alert with an in-state UGC code is kept."""
adapter = _make_nws_with_bbox(IDAHO_BOX)
event = {"lat": None, "lon": None, "areas": ["IDZ016"]}
assert adapter._in_coverage(event) is True
event_none = {"lat": None, "lon": None}
assert adapter._in_coverage(event_none) is True
def test_nws_in_coverage_zone_only_out_of_state_dropped():
"""Zone-only alert whose UGC zones are all out-of-state is dropped."""
adapter = _make_nws_with_bbox(IDAHO_BOX)
# CA zones — coverage is areas=["ID"]
event = {"lat": None, "lon": None, "areas": ["CAZ041", "CAZ042"]}
assert adapter._in_coverage(event) is False
def test_nws_in_coverage_no_bbox_always_passes():