mirror of
https://github.com/zvx-echo6/meshai.git
synced 2026-06-11 01:14:45 +02:00
feat(v0.7-fire-tracker-3): spotting detection -- pixels beyond perimeter trigger immediate broadcast
Phase 3 of FIRMS+WFIGS fusion. v15.sql adds perimeter_geojson to fire_passes + last_spotting_broadcast_at to fires. FIRMS handler computes convex hull of each pass on pass-boundary close; attributed pixels >= 1.5 mi (configurable) from previous-pass perimeter emit wildfire_spotting broadcast. Cooldown 1h between spotting broadcasts per fire so rapid embers do not spam. wildfire_spotting category at immediate severity -- spotting is the highest-actionable fire signal (spread beyond perimeter). All thresholds GUI-editable. Phase 4 (LLM summaries + on-demand queries) deferred. Schema (v15.sql): - fire_passes gains perimeter_geojson TEXT (nullable; populated by _close_prev_perimeter at boundary). GeoJSON Polygon, single outer ring in (lon, lat) order per RFC 7946, closed (first == last). - fires gains last_spotting_broadcast_at REAL (per-fire cooldown latch). Index (irwin_id, last_spotting_broadcast_at) for the cooldown probe. adapter_config (defaults.py REGISTRY): - fires.spotting_distance_threshold_mi = 1.5 (float). Matches design doc Phase 3 spec; design doc open question #6 lists this as TBD pending real spotting observation data. - fires.spotting_cooldown_seconds = 3600 (int, 1h). Suppresses rapid-ember spam from a single satellite pass. ALERT_CATEGORIES (notifications/categories.py): - wildfire_spotting: immediate / fire. Highest fire severity -- spotting represents fire spread BEYOND the existing perimeter, the most actionable detection signal. FIRMS handler (central/firms_handler.py): - _handle_pass_boundary now closes the prior pass's perimeter (convex hull of fire_pixels via Andrew's monotone chain) on the first boundary; subsequent in-pass pixels reuse the stored hull. - _check_spotting runs for every attributed pixel: looks up the most recent CLOSED pass (perimeter_geojson NOT NULL AND pass_id != current), point-in-polygon test, vertex-distance approximation per design doc Q (sparse pixels make edge projection overkill at VIIRS 375 m resolution), per-fire cooldown gate. - Priority order: spotting (immediate) > growth (priority) > cluster (priority) > halt (routine). Spotting preempts growth at the same pixel because immediate > priority. - Helpers: _convex_hull (Andrew's monotone chain), _hull_to_geojson (RFC 7946 Polygon), _point_in_polygon (ray casting), _close_prev_perimeter, _check_spotting, _prev_has_perimeter. Wire string: - wildfire_spotting: "🔥 Possible spotting <dist:.1f> mi <dir> of <incident_name> perimeter" -- direction is 8-way bearing from the previous pass's centroid to the spotting pixel. Tests (tests/test_fire_tracker_phase3.py, 11 cases all green): - Pass close stamps perimeter_geojson as a closed Polygon (6 hex vertices -> 7-entry closed ring). - Pixel 2 mi NE of perimeter fires spotting with distance in the 1.0..2.5 mi band (vertex-distance approximation) and direction NE. - Pixel inside perimeter -> NO spotting wire. - Second spotting candidate within 1h cooldown -> suppressed. - Past-cooldown spotting fires again. - Convex hull / point-in-polygon / GeoJSON round-trip helper tests. - adapter_config seed for both new fires.* keys. - wildfire_spotting category registered with immediate severity. - 49 tests green across phase1/phase2/phase3/or-arch/include-roundtrip. Live verification on CT108 after rebuild: - v15 migration applied (schema_meta=15, no Traceback in 3 min). - Container healthy. Synthetic 25-pixel probe (PROBE-V07P3-*, cleaned up after): - Pass A: 20 pixels in a ~0.3 mi circle. Perimeter stored on boundary. - Pass B: 5 pixels at distances 0.5/1.0/2.0/5.0/7.0 mi from center. Observed wires: "🔥 Possible spotting 1.7 mi NE of Probe Spotting Fire perimeter" "🔥 Possible spotting 4.7 mi NW of Probe Spotting Fire perimeter" (Plus a Phase 2 growth wire on the first pass B pixel -- documented side effect: single-pixel pass B centroid shows 0.5 mi drift from pass A.) - 7.0 mi E pixel: outside 5 mi spread, no broadcast (cluster check found no co-located unattributed pixels). Cleanup confirmed. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
f5c566c6c0
commit
31e543ca04
6 changed files with 634 additions and 4 deletions
|
|
@ -277,7 +277,7 @@ REGISTRY: dict[tuple[str, str], dict[str, Any]] = {
|
|||
},
|
||||
|
||||
# =================================================================
|
||||
# FIRES -- 4 settings (Phase 1 radius + Phase 2 growth/halt thresholds)
|
||||
# FIRES -- 6 settings (Phase 1 radius + Phase 2 growth/halt + Phase 3 spotting)
|
||||
# =================================================================
|
||||
# Per-fire spread radius override lives in fires.spread_radius_mi;
|
||||
# the value below is the fallback. v0.7-fire-1 shipped 5 mi based on
|
||||
|
|
@ -319,6 +319,28 @@ REGISTRY: dict[tuple[str, str], dict[str, Any]] = {
|
|||
"type": "int",
|
||||
"description": "Minimum elapsed seconds since the most recent attributed pixel before wildfire_halted can fire.",
|
||||
},
|
||||
# v0.7-fire-3 -- spotting detection.
|
||||
# spotting_distance_threshold_mi: an attributed pixel this far or
|
||||
# more from the previous-pass perimeter (convex hull, vertex-
|
||||
# distance approximation) fires wildfire_spotting. 1.5 mi matches
|
||||
# the design doc Phase 3 spec ("Hotspot >=1.5 mi from perimeter").
|
||||
# Treat as an initial-guess default -- the design doc lists this
|
||||
# as an open question pending real spotting-fire observation data.
|
||||
("fires", "spotting_distance_threshold_mi"): {
|
||||
"default": 1.5,
|
||||
"type": "float",
|
||||
"description": "Distance (miles) from previous-pass perimeter that fires wildfire_spotting. Tune from observed spotting events; design doc open question #6 marks this as TBD.",
|
||||
},
|
||||
# spotting_cooldown_seconds: per-fire latch so a burst of pixels
|
||||
# in the same general spotting area doesn't spam the mesh. 1h is
|
||||
# short enough that real follow-on spotting (different ember,
|
||||
# different sector) re-fires, long enough that a single satellite
|
||||
# pass with N nearby ember hits broadcasts at most once.
|
||||
("fires", "spotting_cooldown_seconds"): {
|
||||
"default": 3600,
|
||||
"type": "int",
|
||||
"description": "Minimum seconds between consecutive wildfire_spotting broadcasts for the same fire; suppresses rapid-ember spam.",
|
||||
},
|
||||
|
||||
# =================================================================
|
||||
# FIRMS -- 7 settings (storage floors + dedup + 3 v0.7 cluster knobs)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue