diff --git a/sql/migrations/034_widen_monitoring_area_default_to_full_idaho.sql b/sql/migrations/034_widen_monitoring_area_default_to_full_idaho.sql new file mode 100644 index 0000000..e377772 --- /dev/null +++ b/sql/migrations/034_widen_monitoring_area_default_to_full_idaho.sql @@ -0,0 +1,23 @@ +-- Migration 034: widen monitoring-area default to cover all of Idaho (v0.10.9) +-- +-- v0.10.2's migration 030 set monitor_north=44.5 as the default. Idaho actually +-- extends to 49.0N (Canadian border), so the original default cut off the entire +-- panhandle -- Coeur d'Alene, Lewiston, Sandpoint, Moscow, and the McCall region. +-- Tonight's investigation (v0.10.9 ticket) caught the impact at scale: the +-- supervisor's publish-time bbox filter was dropping ~56 itd_511 events per +-- 60-second poll (~5,376/day), the entire northern half of Idaho's roadwork, +-- closures, and incidents. The NWS adapter was also losing Idaho UGC zone +-- alerts north of 44.5 for the same reason (v0.10.7 followup-ticket (b)). +-- +-- This migration does two things: +-- 1. ALTER COLUMN ... SET DEFAULT 49.0 -- so fresh installs get the full +-- Idaho extent baked in. +-- 2. UPDATE ... WHERE monitor_north = 44.5 -- so existing deployments still +-- at the v0.10.2 dev value get bumped. The WHERE 44.5 guard preserves +-- any operator who deliberately narrowed (e.g. set north=44.0 for a +-- regional test) -- those rows are untouched. +-- +-- Idempotent: running again is a no-op on both statements once row is at 49.0. + +ALTER TABLE config.system ALTER COLUMN monitor_north SET DEFAULT 49.0; +UPDATE config.system SET monitor_north = 49.0 WHERE monitor_north = 44.5; diff --git a/src/central/gui/routes.py b/src/central/gui/routes.py index ba01af8..3773eb0 100644 --- a/src/central/gui/routes.py +++ b/src/central/gui/routes.py @@ -2349,7 +2349,7 @@ async def enrichment_update(request: Request) -> Response: # --- Monitoring area (system-level archive bbox filter) -------------------- -_DEFAULT_MONITOR = {"north": 44.5, "south": 41.8, "east": -111.0, "west": -117.5} +_DEFAULT_MONITOR = {"north": 49.0, "south": 41.8, "east": -111.0, "west": -117.5} async def _read_monitoring_area(conn) -> dict[str, Any]: diff --git a/tests/test_monitoring_area.py b/tests/test_monitoring_area.py index 1ec91db..abbfae8 100644 --- a/tests/test_monitoring_area.py +++ b/tests/test_monitoring_area.py @@ -140,3 +140,68 @@ class TestLoadMonitoringArea: "monitor_east": -111.0, "monitor_west": -117.5, }) assert await load_monitoring_area(conn) is None + + +class TestDefaultMonitoringAreaCoversIdaho: + """v0.10.9 regression guard for migration 034 + the routes._DEFAULT_MONITOR fallback. + + v0.10.2 shipped with the default monitor_north = 44.5, which only covered the + southern third of Idaho -- the supervisor's publish-time bbox filter was + silently dropping ~56 itd_511 events per 60-second poll (the entire northern + panhandle: Coeur d'Alene, Lewiston, Sandpoint, Moscow, McCall). NWS Idaho + UGC-zone alerts in the panhandle were similarly dropped (v0.10.7 followup + ticket (b)). Idaho actually extends to 49.0N. Migration 034 widens the + default to cover the full state. + + If anything reverts the default to a value that excludes any Idaho city, + this test fails loudly. + """ + + # (city, lat, lon) -- spread across Idaho to catch any narrowing on any side. + IDAHO_SENTINEL_CITIES = [ + ("Coeur d'Alene (panhandle, north)", 47.6777, -116.7805), + ("Sandpoint (panhandle, far north)", 48.2766, -116.5534), + ("Lewiston (north-central)", 46.4165, -117.0177), + ("Moscow (panhandle, west)", 46.7324, -116.9999), + ("McCall (central-north)", 44.9111, -116.0998), + ("Boise (south-west)", 43.6150, -116.2023), + ("Idaho Falls (south-east)", 43.4917, -112.0339), + ("Twin Falls (south-central)", 42.5629, -114.4609), + ("Pocatello (south-east)", 42.8713, -112.4455), + ] + + def test_routes_default_monitor_covers_full_idaho_extent(self): + """routes._DEFAULT_MONITOR must contain every Idaho sentinel city.""" + from central.gui.routes import _DEFAULT_MONITOR + default = MonitoringArea( + north=_DEFAULT_MONITOR["north"], + south=_DEFAULT_MONITOR["south"], + east=_DEFAULT_MONITOR["east"], + west=_DEFAULT_MONITOR["west"], + ) + for label, lat, lon in self.IDAHO_SENTINEL_CITIES: + assert classify_geom(_pt(lon, lat), default) == "in-bounds", ( + f"_DEFAULT_MONITOR rejected {label} at ({lat}, {lon}) -- " + f"check that monitor_north is at least 49.0" + ) + + def test_routes_default_monitor_bounds_match_idaho_extent(self): + """Belt-and-suspenders: assert the exact corner values rather than + just inferring from the city checks. v0.10.9 set the default to + (49.0, 41.8, -111.0, -117.5) -- the bounding box of Idaho.""" + from central.gui.routes import _DEFAULT_MONITOR + assert _DEFAULT_MONITOR["north"] >= 49.0 + assert _DEFAULT_MONITOR["south"] <= 41.8 + assert _DEFAULT_MONITOR["east"] >= -111.0 + assert _DEFAULT_MONITOR["west"] <= -117.5 + + def test_v0_10_2_narrow_default_would_reject_panhandle(self): + """Anti-regression: the OLD v0.10.2 north=44.5 default would have + rejected the panhandle. This isn't testing current behavior -- it's + documenting what we fixed, and ensuring nothing reintroduces a + narrow north bound silently.""" + narrow = MonitoringArea(north=44.5, south=41.8, east=-111.0, west=-117.5) + panhandle_cities = [c for c in self.IDAHO_SENTINEL_CITIES if c[1] > 44.5] + assert len(panhandle_cities) >= 4, "expected at least 4 north-of-44.5 sentinels" + for label, lat, lon in panhandle_cities: + assert classify_geom(_pt(lon, lat), narrow) == "out-of-bounds", label