mirror of
https://github.com/zvx-echo6/meshai.git
synced 2026-08-26 17:31:34 +00:00
7 commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
67176d66d5 |
chore(central-ripout 2d-ii): remove dead fire scraps (NOT the 'rewrite' — there wasn't one) (#166)
* chore(fire-ripout 2dii-a): remove dead wildfire_growth cutover path + handle_firms
wildfire_growth events are always fully precomposed (env/firms.py sets
_meshai_precomposed=True + title=<wire from _render()>) and the category is
not in cutover.NATIVE_ALWAYS_DECIDE, so the registered fire formatter could
never be reached live via compose_mesh_message for this category -- the
precomposed-title bypass always won first. Removes the dead
is_cutover("wildfire_growth") NEW-PATH branch in
fire_fusion._handle_pass_boundary (kept the live legacy leg that calls
_render unconditionally) and the now-unreachable wildfire_growth formatter
registration in notifications/formatters/__init__.py.
Also removes handle_firms, the dead Central NATS-envelope entrypoint (zero
live production callers -- Central's consumer that drove it is gone), plus
its envelope-specific filtering helpers (_confidence_passes, _in_bbox,
_coerce_severity, _log_event, FIRMS_CONFIDENCE_FLOOR/FRP_FLOOR/BBOX_OPTIONAL)
that had no live callers left. The shared, LIVE fusion core
(_ingest_pixel_core, attribution, clustering, growth/spotting/halt) and
_parse_acq_epoch are unaffected -- still the engine behind the native
ingest_hotspot_pixel entrypoint.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore(fire-ripout 2dii-b): remove dead handle_wfigs entrypoint
handle_wfigs (the dead Central NATS-envelope entrypoint) had zero live
production callers -- Central's consumer that drove it is gone. The LIVE
WFIGS path is env/fires.py (native adapter) -> env/store.py::_emit_event,
forced onto gating.fire.decide + the shared fire formatter via
cutover.NATIVE_ALWAYS_DECIDE, independent of this dead handler.
Removes handle_wfigs and its private-only helpers (_coerce_severity,
_log_event, _log_event_returning_id) that had no callers left. _render
remains -- it is called directly by env.fire_fusion._handle_pass_boundary on
the FIRMS wildfire_growth path, and is used as a byte-identity oracle by
tests against the shared, live fire formatter. _build_canonical,
_attach_commit_handles, _fire_too_old_to_announce, _now, _cleanup_stale_fires
and WFIGS_BROADCAST_COOLDOWN_S are kept -- each has its own direct test
coverage independent of handle_wfigs.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* test(fire-ripout 2dii-c): rewire fire tests off the deleted dead entrypoints
handle_firms and handle_wfigs (both removed in the prior two commits) were
used throughout this test suite purely as convenient DRIVERS for live logic
(pixel attribution/clustering/growth/spotting/halt fusion; the WFIGS
New/Update/cooldown/closed decider). Per-file disposition:
Rewired to drive the LIVE native entrypoint directly (no behavior change --
same underlying _ingest_pixel_core / gating.fire.decide engines):
- test_fire_tracker_phase1/2/3.py: handle_firms -> ingest_hotspot_pixel
- test_firms_refactor.py: handle_firms driver helpers -> ingest_hotspot_pixel;
dropped the dead wildfire_growth cutover test + the fully-redundant
TestNotCutoverLegacyVerbatim class (already covered by
test_firms_native_fusion.py's TestIngestGrowth/Spotting/Halt against the
live entrypoint); wildfire_growth formatter registration test now
asserts None (matches source change).
- test_wfigs_handler.py: handle_wfigs -> _render (the live WFIGS renderer)
called directly, for the anchor-priority + missing-acres cases that
exercise shared/live code (_location_anchor).
- test_fire_refactor.py: handle_wfigs -> gating.fire.decide() driven
directly, with state written the same unconditional shape the live
native path uses; TestGateSequenceParity trimmed to focus on the
tombstone/closed lifecycle step (not covered by
test_fire_native_growth.py's native-adapter New/Update/cooldown coverage).
- test_tombstone_broadcast.py, test_fire_age_gate.py: handle_wfigs ->
gating.fire.decide() driven directly; same assertions, off the dead path.
Deleted as pure dead-entrypoint contract testing with no live equivalent:
- test_firms_native_fusion.py::TestCentralPathUnchanged (2 tests) --
existed only to guard handle_firms's own envelope parsing.
- test_firms_handler.py: confidence/FRP/bbox filtering, missing-coords,
non-firms-adapter guard, event_log accounting (all handle_firms-specific;
the native adapter filters upstream in a different code path). Kept +
rewired: the shared _ingest_pixel_core dedup behavior and
_parse_acq_epoch's int/short acq_time parsing (both genuinely live/shared).
- test_wfigs_handler.py: envelope field-extraction (acres-fallback chain,
IA-placeholder-as-name), tombstone/perimeter subject -> event_log,
New/Update/cooldown decision + audit-row wiring (all redundant with
tests/test_fire_native_growth.py's coverage of gating.fire.decide()
through the real native adapter).
- test_tombstone_broadcast.py::test_commit_callback_flips_handled --
asserted handle_wfigs's own event_log-row flip on commit, a Central-only
concept the native path never used.
- test_tail_followups.py::test_wfigs_tombstone_stamps_column -- asserted
handle_wfigs's own inline tombstoned_at UPDATE; no live producer emits
_kind=wfigs_tombstone today, so nothing in the live system stamps it.
Both live renderers stay covered: fire_format (wildfire_declared/incident,
via test_fire_refactor.py + test_fire_native_growth.py, untouched) and
_render (wildfire_growth via FIRMS, via test_fire_tracker_phase2.py +
test_firms_native_fusion.py's TestIngestGrowth, both driving the live
ingest_hotspot_pixel entrypoint).
Collection: 2010 -> 1974 tests (net -36: dead-only tests deleted, live
behavior rewired 1:1 or consolidated onto already-existing native-path
coverage). Full suite: 1974 passed, 0 failed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Matt Johnson <mj@k7zvx.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
|||
|
de91751bbc |
chore(central-ripout 2d-i): relocate the fire engine + renderer out of central/ (#164)
Moves the last two live files out of the retired-Central folder. central/ is now EMPTY and deleted entirely (incl. __init__.py). - fire-fusion engine (ingest_hotspot_pixel + the growth/cluster/spotting engine) → env/fire_fusion.py, next to its sole consumer env/firms.py. - wildfire text renderer (_render + its live helpers) → env/fire_render.py. THE COUPLING RESOLVED: firms_handler._handle_pass_boundary had a LAZY import inside a function body — `from meshai.central.wfigs_handler import _render` — sitting on the live FIRMS fire-growth path. It is now a normal top-of-file import (`from meshai.env.fire_render import _render`), visible and greppable. PURE MOVE — no behavior change: - The parity oracle (test_fire_refactor.py) PASSES UNCHANGED (only its import paths updated) — proving the fire wire output is byte-for-byte identical before and after. Fire alerts say exactly what they said. - handle_firms / handle_wfigs (dead entrypoints, only caller was the deleted consumer.py) were KEPT and moved rather than dropped — the wording-cleanup PR removes them deliberately. "When unsure, keep." - fire_render.py's geo-helper imports still point at central_normalizer — that file's split is a SEPARATE PR; carried the imports along, did not touch it. Consumers + test import paths rewired. Full suite: 2059 passed, 0 failed. Co-authored-by: Matt Johnson <mj@k7zvx.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
|||
|
5dd8266abe |
fix(firms): repair the FIRMS fire-fusion Event contract (issues #117-#119) (#120)
Three independent bugs kept firms_handler's growth/spotting/halt/cluster fusion decisions from reaching a correct mesh Event: - #117: consumer._normalize() computed `category` from the raw Central category BEFORE the per-adapter handler ran and never re-read data["category"] afterward, so every firms_handler category stamp was a silent no-op. Now re-read post-dispatch, validated against the known category registry (unrecognized overrides are logged and ignored). - #118: consumer.py only ever honors data["_severity_override"], but firms_handler's halt/spotting/cluster sites stamped the plain data["severity"] key instead (only growth used the right key). Switched all three sites to `_severity_override` for one consistent contract. This is severity plumbing only -- it does not change which events fire. - #119: FirePacer's gate only matched source in ("fires","wfigs") at severity=="priority", so FIRMS fusion broadcasts (source="firms", growth/spotting at "immediate") never reached the pacer. Broadened the gate to cover "firms" + {"priority","immediate"}, and gave FirePacer head-of-line insertion so an "immediate" event is never stuck behind already-queued "priority" events. Still unbounded/never-drops. Cluster detection is left exactly as main ships it: live, always on, no toggle (PR #73's curated new-fire cluster broadcasts with cold-start silent-seeding). Only its severity-override key changes, under #118. Updated existing tests that asserted the old (buggy) data["severity"] contract, and added tests/test_firms_fusion_event_contract.py covering all three fixes end-to-end through consumer._normalize()/_handle() and FirePacer directly. Co-authored-by: Matt Johnson <mj@k7zvx.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
|||
|
d479ca537a |
feat(firms): curated new-fire cluster broadcasts (no per-pixel, no cold-start dump) (#73)
* feat(firms): curated new-fire cluster broadcasts (no per-pixel, no cold-start dump) Enable the built _maybe_emit_cluster path (was dead-coded) so FIRMS broadcasts curated hotspot clusters as possible new fires — clustered, deduped via cluster_broadcast_at, attributed against known WFIGS fires first (so MORA's hotspots don't false-cluster). Give FIRMS a default Idaho bbox so it fetches when coverage is off (coverage bbox still overrides). First-fetch silent-seed prevents a cold-start dump of the day's existing hotspots. Raw pixels stay store-only. Coverage geometry gate filters cluster broadcasts to the region. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(firms): first-fetch silent-seed suppresses fusion wires too (no cold-start) Extend the FIRMS cold-start seed to suppress growth/spotting/halt fusion broadcasts on the first fetch, not just clusters — enabling FIRMS must emit zero broadcasts on the initial hotspot sweep. Persistence, attribution, and dedup baselines still run during seed; only later new activity broadcasts. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Matt Johnson <mj@k7zvx.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
|||
|
7ec0883b44 |
fix(env): leak-proof received-delta — durable persistent baseline + non-empty seed guard (#44)
Two live backlog-broadcast leaks traced to the in-memory first-poll seed: (1) incremental-fetch adapters (wzdx: registry tick [0 events] then feeds tick [many]) got marked _seeded on the EMPTY first tick, so the real batch next tick all looked "new" and broadcast; (2) in-memory seed lost on restart. Fix — durable baseline + guard: - _seed_from_persistent() at store init: pre-load already-received item keys from the persistent hazard tables into self._seen, so nothing ever received can re-broadcast (immune to fetch staging + restart). Only sources whose native emit key PROVABLY equals a persistent key are durably seeded: wzdx (traffic_events.external_id) + usgs_quake (quake_events.event_id). Resilient (per-table try/except; missing table -> skip). - _seen_key() now namespaces by evt["source"] (matches persistent tables), via shared _key_ext/_key_eid helpers used by both seed and live emit so they can't drift. - non-empty-seed guard: _ingest marks only sources that carried >=1 event this poll as _seeded -> an empty first tick can never seed-then-leak. This is the root-cause fix; covers all adapters (roads511/traffic fetch atomically per tick, so the guard fully protects them). - storage untouched (self._events populated for every event); Central path/deciders untouched. Live-DB verified: seed pre-loads 784 wzdx + 8 quake keys -> a live wzdx poll of 784 known zones broadcasts 0. +6 tests (incremental staging, restart, persistent-preseed, fresh-DB fallback); suite at 10-failure baseline. Co-authored-by: Matt Johnson <mj@k7zvx.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
|||
|
0c76a806b8 |
feat(env): native adapters broadcast only newly-RECEIVED items (no backlog) (#43)
Replace the native path's "scan accumulated state + suppress what we've already broadcast" model with "broadcast only what newly arrived from the API this poll." Storage is unchanged (self._events + firms_pixels etc. are populated for EVERY received item, so the LLM/get_active backlog is intact); only the BROADCAST decision changes. - env/store.py: per-adapter in-memory seen-set (_seen) + _seeded. First data-bearing poll for an adapter seeds keys and emits NOTHING (that batch is pre-existing backlog); later polls emit only keys not seen before. Restart => empty sets => next poll re-seeds silently. Structurally impossible to broadcast backlog on cold start / restart / re-enable. Key = external_id -> event_id -> content hash, namespaced per adapter. self._events[key]=evt still runs unconditionally (storage preserved). - Fixes the ~175 (roads511) / ~782 (wzdx) cold-start bursts AND the latent quake/nws version (they only looked safe because Central pre-populated their broadcast tables). - env/satpass.py: broadcast on AOS IMMINENCE (now < aos <= now+lead, broadcast_lead_seconds default 3600), future-only; window_hours still governs prediction depth. Strict norad_ids post-filter + fixed _parse_norad_ids char-iteration bug (cause of GOES/METEOR leak). - Central path + broadcast-state tables untouched (native-only gate). 13 new tests; full suite at 10-failure baseline (1697 passed). Co-authored-by: Matt Johnson <mj@k7zvx.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
|||
|
0a75930ade |
feat(firms): source-agnostic fire-fusion — native FIRMS feeds growth/spotting/halt (#41)
Closes the last standalone gap. Extract ingest_hotspot_pixel(pixel, *, now) from firms_handler so the FIRMS attribution/fusion engine (firms_pixels -> _attribute_or_cluster -> fire_pixels/fire_passes/centroid -> growth/spotting/ halt) is source-agnostic. Both the Central NATS path and native env/firms.py drive one identical engine. - shared _ingest_pixel_core(conn, ...) called by both ingest_hotspot_pixel and handle_firms; Central path byte-identical (its tests pass unchanged) - env/firms.py _fetch() feeds each fetched pixel into ingest_hotspot_pixel; DB-level dedup makes re-fetched pixels no-ops (no double count); to_event() returns None for raw hotspots, precomposed Event for fusion outputs (wildfire_growth/spotting/halted via the Phase-3c formatters/gating) - raw hotspots / new_ignition / cluster NEVER broadcast (cluster stays dead) FLIP NOTE: keep wildfire_growth/spotting/halted OUT of cutover — native emits precomposed with gating done inside the engine; cutover would re-run the _kind-keyed decider on data lacking _kind and suppress. 10 new tests; Central firms/fire-tracker suites unchanged; full suite 10 failed/1682 passed (baseline 10, +10 new). Co-authored-by: Matt Johnson <mj@k7zvx.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |