v0.14.0: multi-bbox monitoring areas (config.monitoring_areas set-union filter)

Generalize the single system monitoring bbox into a named list of areas with
set-union semantics: an event is kept by the archive (and at publish time) if
its geometry intersects ANY configured area. No-geometry events are always
kept; an empty list keeps everything (the pre-feature default).

- Migration 042: new config.monitoring_areas table (name UNIQUE, north/south/
  east/west, CHECK ordering); seeds 'default' from config.system.monitor_* so
  current bounds (N=49.0 S=41.8 E=-111.0 W=-117.5) are preserved. Old columns
  left in place for v0.14.0 (drop in v0.14.1).
- monitoring_area.py: classify_geom_areas + load_monitoring_areas; single-area
  classify_geom / load_monitoring_area kept as back-compat shims.
- archive + supervisor consumers load and apply the area list (set-union);
  _monitoring_area kept as a back-compat property over the internal list.
- GUI /monitoring-area: list+create+update+delete (server-rendered forms),
  shared Leaflet map with per-name golden-angle colored rectangles, add/edit
  via leaflet.draw corner handles, empty-state banner, auto-fit to all areas.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
malice 2026-06-11 12:58:08 -06:00
commit 71e1e94fe1
13 changed files with 792 additions and 237 deletions

View file

@ -0,0 +1,48 @@
-- Migration 042: generalize the single system bbox into a named multi-area list (v0.14.0)
--
-- Until now the system monitoring area was a single rectangle stored as four
-- columns on the config.system singleton (monitor_north/south/east/west, added
-- by migration 030, default widened to full Idaho by 034). Matt needs to watch
-- several non-contiguous regions (Treasure Valley, Magic Valley, Mountain Home,
-- ...) without inflating one box to cover everything between them. This migration
-- introduces config.monitoring_areas: a list of named bboxes with set-union
-- semantics -- an event is kept by the archive (and at publish time) if its
-- geometry intersects ANY area in the list. An empty list keeps everything,
-- matching the pre-feature "no area configured" default.
--
-- The existing single bbox is preserved verbatim as the row named 'default',
-- carrying Matt's current production bounds (N=49.0 S=41.8 E=-111.0 W=-117.5).
--
-- The old config.system.monitor_* columns are intentionally LEFT IN PLACE for
-- v0.14.0 (consumers stop reading them; the GUI tile_url/attribution still live
-- on config.system). They are dropped in a follow-up (v0.14.1) once the new
-- table is proven on prod. Idempotent per docs/migrations.md.
CREATE TABLE IF NOT EXISTS config.monitoring_areas (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL UNIQUE,
north DOUBLE PRECISION NOT NULL,
south DOUBLE PRECISION NOT NULL,
east DOUBLE PRECISION NOT NULL,
west DOUBLE PRECISION NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT now(),
updated_at TIMESTAMP NOT NULL DEFAULT now(),
CONSTRAINT monitoring_areas_lat_order CHECK (north > south),
CONSTRAINT monitoring_areas_lon_order CHECK (east > west)
);
CREATE INDEX IF NOT EXISTS monitoring_areas_name_idx ON config.monitoring_areas(name);
-- Seed the 'default' area from the existing single bbox so current bounds are
-- preserved on upgrade. Skipped cleanly if the row already exists (re-run) or if
-- any monitor_* column is NULL (no usable bbox -> nothing to migrate, list stays
-- empty = keep everything).
INSERT INTO config.monitoring_areas (name, north, south, east, west)
SELECT 'default', monitor_north, monitor_south, monitor_east, monitor_west
FROM config.system
WHERE id = true
AND monitor_north IS NOT NULL
AND monitor_south IS NOT NULL
AND monitor_east IS NOT NULL
AND monitor_west IS NOT NULL
ON CONFLICT (name) DO NOTHING;