mirror of
https://github.com/zvx-echo6/meshai.git
synced 2026-08-26 17:31:34 +00:00
fire: remove fire digest feature and drop out-of-coverage fires at ingest (#107)
Two fire-scope cleanups: 1. Remove the fire digest feature entirely -- scheduler (notifications/scheduled/fire_digest.py), pipeline wiring, the fires.digest_* adapter_config key registrations, and the Fire Digest dashboard UI (ScheduledBroadcasts / Environment / Reference / AdapterConfig / ActivityLog). The unrelated generic per-rule notification digest is kept. Orphaned fires.digest_* config rows and the fire_digest_broadcasts table are left as inert data (v16 migration untouched). 2. Add a coverage-scope gate at fire ingest: _ingest_fires now skips any fire whose coordinates fall outside all configured coverage areas (same areas_from_config + classify_geom_areas membership the dispatch-level CoverageFilter uses), so out-of-coverage fires are never stored, tracked, alerted, reminded, or re-ingested. Fails open when coverage is disabled, has no areas, or excludes the fires adapter. Co-authored-by: Matt Johnson <mj@k7zvx.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
af826319c8
commit
b0b0697bac
16 changed files with 189 additions and 960 deletions
|
|
@ -110,16 +110,27 @@ def _raw_fire(*, name="MORA", irwin=_IRWIN, acres=2410, contained=10,
|
|||
}
|
||||
|
||||
|
||||
def _make_store():
|
||||
def _make_store(coverage_areas=None, coverage_excluded=None):
|
||||
bus = EventBus()
|
||||
captured: list = []
|
||||
bus.subscribe(lambda e: captured.append(e))
|
||||
store = EnvironmentalStore(EnvironmentalConfig(), event_bus=bus)
|
||||
store = EnvironmentalStore(
|
||||
EnvironmentalConfig(), event_bus=bus,
|
||||
coverage_areas=coverage_areas,
|
||||
coverage_excluded=coverage_excluded,
|
||||
)
|
||||
adapter = _FakeFires()
|
||||
store._adapters["nifc"] = adapter
|
||||
return store, adapter, captured
|
||||
|
||||
|
||||
# A coverage area covering SW Idaho (the default _raw_fire lat/lon 44.0,-115.0
|
||||
# sits inside this box; a fire near 0,0 or on the US east coast is outside).
|
||||
_SW_IDAHO_AREA = {
|
||||
"name": "sw-id", "west": -117.0, "south": 42.0, "east": -114.0, "north": 45.0,
|
||||
}
|
||||
|
||||
|
||||
def _seed_row(conn, *, acres, contained, last_bcast_at):
|
||||
"""Manually insert an already-broadcast fires row (skips cold-start)."""
|
||||
conn.execute(
|
||||
|
|
@ -309,3 +320,80 @@ def test_to_event_stamps_canonical_data(env):
|
|||
assert ev.data["contained_pct"] == 30
|
||||
assert ev.data["declared_at_epoch"] == _NOW
|
||||
assert ev.data["lat"] == 44.0 and ev.data["state"] == "US-ID"
|
||||
|
||||
|
||||
# ── 7. Coverage-scope ingest gate ────────────────────────────────────────────
|
||||
# Fires OUTSIDE every configured coverage area are NEVER stored (so they are
|
||||
# never tracked / alerted / reminded / re-ingested). The gate mirrors the
|
||||
# dispatch-level CoverageFilter's set-union membership and applies to BOTH the
|
||||
# cold-start seed and the live INSERT/UPDATE path. Fail-OPEN when coverage is
|
||||
# disabled or has no areas (unchanged current behaviour).
|
||||
|
||||
|
||||
def _fires_row(conn, irwin):
|
||||
return conn.execute(
|
||||
"SELECT irwin_id FROM fires WHERE irwin_id=?", (irwin,)).fetchone()
|
||||
|
||||
|
||||
def test_coverage_gate_inside_area_is_stored(env):
|
||||
"""A fire INSIDE a coverage box is stored (cold-start seed row present)."""
|
||||
conn, _clk = env
|
||||
store, adapter, captured = _make_store(coverage_areas=[_SW_IDAHO_AREA])
|
||||
# Default coords 44.0,-115.0 are inside _SW_IDAHO_AREA.
|
||||
adapter.set_batch([_raw_fire(irwin="IRWIN-IN-1", lat=44.0, lon=-115.0)])
|
||||
store._ingest("nifc", adapter)
|
||||
|
||||
assert captured == [], "cold-start seed must broadcast nothing"
|
||||
assert _fires_row(conn, "IRWIN-IN-1") is not None, \
|
||||
"in-area fire must be stored"
|
||||
|
||||
|
||||
def test_coverage_gate_outside_area_is_not_stored(env):
|
||||
"""A fire OUTSIDE all coverage boxes is NOT stored (no row) with coverage
|
||||
enabled + areas defined — neither the cold-start seed nor a live upsert."""
|
||||
conn, _clk = env
|
||||
store, adapter, captured = _make_store(coverage_areas=[_SW_IDAHO_AREA])
|
||||
# 0,0 (Gulf of Guinea) is far outside the SW-Idaho box.
|
||||
adapter.set_batch([_raw_fire(irwin="IRWIN-OUT-1", lat=0.0, lon=0.0)])
|
||||
store._ingest("nifc", adapter)
|
||||
|
||||
assert captured == [], "out-of-area fire must broadcast nothing"
|
||||
assert _fires_row(conn, "IRWIN-OUT-1") is None, \
|
||||
"out-of-area fire must NOT be stored (cold-start seed dropped)"
|
||||
|
||||
# A later (already-seeded) poll must ALSO refuse to INSERT the out-of-area
|
||||
# fire via the live path — it must never latch a row.
|
||||
store._fires_seeded = True
|
||||
store._ingest("nifc", adapter)
|
||||
assert _fires_row(conn, "IRWIN-OUT-1") is None, \
|
||||
"out-of-area fire must NOT be stored on the live upsert path either"
|
||||
|
||||
|
||||
def test_coverage_gate_fail_open_when_no_areas(env):
|
||||
"""Coverage DISABLED / no areas -> an out-of-box fire IS stored (fail-open,
|
||||
unchanged behaviour)."""
|
||||
conn, _clk = env
|
||||
store, adapter, captured = _make_store() # no coverage areas
|
||||
assert store._fire_coverage_areas == [], \
|
||||
"no coverage areas -> gate is a no-op list"
|
||||
adapter.set_batch([_raw_fire(irwin="IRWIN-OPEN-1", lat=0.0, lon=0.0)])
|
||||
store._ingest("nifc", adapter)
|
||||
|
||||
assert _fires_row(conn, "IRWIN-OPEN-1") is not None, \
|
||||
"with no coverage areas, every fire is stored (fail-open)"
|
||||
|
||||
|
||||
def test_coverage_gate_excluded_adapter_fails_open(env):
|
||||
"""When 'fires' is on the coverage opt-out list, the gate is disabled even
|
||||
with areas configured — mirrors the _coverage_for fetch-scope escape hatch;
|
||||
an out-of-box fire IS stored."""
|
||||
conn, _clk = env
|
||||
store, adapter, captured = _make_store(
|
||||
coverage_areas=[_SW_IDAHO_AREA], coverage_excluded=["fires"])
|
||||
assert store._fire_coverage_areas == [], \
|
||||
"excluded 'fires' adapter -> no gate built"
|
||||
adapter.set_batch([_raw_fire(irwin="IRWIN-EXCL-1", lat=0.0, lon=0.0)])
|
||||
store._ingest("nifc", adapter)
|
||||
|
||||
assert _fires_row(conn, "IRWIN-EXCL-1") is not None, \
|
||||
"excluded adapter falls back to no coverage gating (fire stored)"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue