Rewrite the satellite-pass wire to a single clean line: short ham names
(ISS/AO-27/AO-91), numeric max elevation (max 77°) instead of a bucket word,
collapsed compass sweeps (no E→E→E), and friendly observer names — dropping
the meaningless synthetic coverage_center parenthetical (and no longer seeding
that observer when explicit observers are configured). Absolute local time
kept for the 12h-advance heads-up.
Co-authored-by: Matt Johnson <mj@k7zvx.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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>
meshai can now predict + broadcast satellite passes locally without Central.
Data plumbing (4c-1):
- env/tle_fetch.py: keyless Celestrak GP fetcher (GROUP/CATNR, FORMAT=tle) →
upserts the existing sat_tles table via a shared upsert_tle() helper
extracted into tle_handler (Central ingest refactored to call it, unchanged)
- observer_locations table (v23, SCHEMA_VERSION 22->23) + persistence helpers;
seeded from SatpassConfig.observers in main._init_components
- SatpassConfig: observers, tle_groups, norad_ids, tle_refresh_seconds,
min_elevation_deg, window_hours
Predictor + source-agnostic gate (4c-2):
- extracted gate_consolidated_pass(consolidated, *, now) from
consolidate_satpass_pending: dedup-vs-satpass_events + rate cap + format_pass
+ deferred commit. Central path byte-identical (114 tests unchanged)
- env/satpass.py: native adapter predicts passes for each sat x observer via
pass_predictor.compute_passes, consolidates IN-MEMORY per canonical hour
bucket (earliest AOS / latest LOS / max-el observer supplies peak_compass +
entry/exit observers), runs the shared gate, emits sat_pass. Commit rides
event.data so satpass_events dedups across ticks — NO satpass_pending, NO
Central-consumer timer dependency (works with Central off)
- registered in env/store.py gated on enabled and feed_source==native
32 new tests (tle_fetch 16, observer_locations 14... satpass_native 8, minus
overlaps); full suite 10-failure baseline (1672 passed).
Co-authored-by: Matt Johnson <mj@k7zvx.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>