chore(central-ripout 2b): relocate satellite code to env/satellite/ (#162)

* chore(central-ripout 2b): create env/satellite package, move pass_predictor

pass_predictor.py was 100% live (no dead entrypoint) — SGP4 pass
computation used by both the native satpass adapter and the on-demand
!satpass command. Straight move, no code changes: meshai.central.pass_predictor
-> meshai.env.satellite.pass_predictor. Owner directive: satellite code gets
its own folder under the feed adapters, separate from env.satpass (the
adapter) to avoid colliding with env/satpass.py.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* chore(central-ripout 2b): split satpass_handler.py -> env/satellite/pass_format.py

satpass_handler.py was a split file: live wire-formatting/gate logic plus
dead Central-envelope ingest machinery whose only caller was the
already-deleted central/consumer.py NATS bridge.

Moved (live, verified via rg — external callers in env/satpass.py and
commands/satpass_cmd.py, or transitively called by them):
  gate_consolidated_pass, format_pass, _check_rate_cap, _upsert_satpass,
  _attach_commit, _map_severity, _canonical_id, _azimuth_to_compass,
  _short_sat_name, _collapse_compass, _region_paren, _is_synthetic_observer,
  _format_time_12h/24h, _format_ampm, _tz_abbr, _date_label,
  plus the _SHORT_SAT_NAMES/_SHORT_NAME_SUBSTR/_SYNTHETIC_OBSERVERS tables.

Dropped (dead — zero callers outside the already-deleted consumer.py and
handle_satpass/consolidate_satpass_pending themselves; verified with rg):
  handle_satpass, consolidate_satpass_pending, _cleanup_pending,
  load_pending_schedule, _log_event_returning_id, _coerce_float,
  _coerce_int, _parse_iso_epoch, _now, CONSOLIDATION_DELAY,
  _pending_consolidation_ids, drain_pending_consolidation_ids,
  _elevation_bucket (already-orphaned pre-ripout: superseded by numeric
  "max NN°" wire format, zero callers anywhere but its own tests),
  SCHEMA_SATPASS_EVENTS/SCHEMA_SATPASS_PENDING (unused string constants —
  actual schema lives in persistence/migrations/*.sql, never imported).
  Also dropped now-unused `json`/`time`/`Any` imports.

Straight code move otherwise — no logic changes to any moved function.
Two docstrings updated for accuracy (module docstring, and
gate_consolidated_pass's docstring which referenced the now-deleted
Central consumer path).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* chore(central-ripout 2b): split tle_handler.py -> env/satellite/tle_store.py

tle_handler.py was a split file: live storage helpers plus a dead
Central-envelope ingest entrypoint whose only caller was the
already-deleted central/consumer.py NATS bridge.

Moved (live — used by env.tle_fetch, env.satpass, commands.satpass_cmd,
verified via rg): upsert_tle, get_fresh_tles, get_tle_by_norad,
search_tle_by_name.

Dropped (dead — handle_tle's only callers were tests and the deleted
consumer.py; verified with rg): handle_tle.

Straight code move otherwise — no logic changes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* chore(central-ripout 2b): repoint satellite consumers at env.satellite

Rewire the three production consumers (including their lazy/function-body
imports, not just module-top ones) to the new location:
  - env/satpass.py: central.tle_handler -> env.satellite.tle_store,
    central.pass_predictor -> env.satellite.pass_predictor,
    central.satpass_handler -> env.satellite.pass_format
  - env/tle_fetch.py: central.tle_handler.upsert_tle -> env.satellite.tle_store
  - commands/satpass_cmd.py: all three, same mapping

Also refreshed docstrings that pointed at the old module paths or described
the now-fully-deleted Central consolidation path
(consolidate_satpass_pending / satpass_pending buffer) as a live
alternative, and updated central/__init__.py's module docstring to stop
listing the three relocated modules among central's remaining contents.

No behavior changes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* chore(central-ripout 2b): update satpass/tle test suite for the relocation

Repoints every remaining test import at the new env.satellite.* modules
and removes/adapts coverage for the Central envelope-ingest path deleted
in this pass (handle_satpass, consolidate_satpass_pending, handle_tle, and
the filtering/coercion/staleness logic that lived only inside them):

- test_satpass_native.py, test_tle_fetch.py, test_satpass_command.py:
  import-path updates only (pass_predictor, tle_store). Also dropped
  test_satpass_command.py's TestTLEUpsert.test_returns_none_always
  (handle_tle-specific contract, no longer applicable) and rewrote its two
  latest-wins tests to call upsert_tle directly — same behavior under test,
  now exercised through the still-live primitive instead of the dead
  wrapper.
- test_satpass_native.py: deleted test_central_consolidate_feeds_shared_gate_merged
  (spied on consolidate_satpass_pending, which no longer exists). The
  merge-across-observers logic it guarded is native-side (_consolidate)
  and already covered by test_two_observers_consolidate_to_one_broadcast.
- test_satpass_handler.py: gutted to the one test that calls format_pass
  directly (test_format_pass_worst_case_fits_140); the rest exercised
  handle_satpass's observer/norad/elevation filters, which have no live
  equivalent (the native adapter filters at the config level, not
  per-envelope) and is redundant with test_satpass_native.py's dedup/wire
  coverage via the real SatpassAdapter path.
- test_satpass_broadcast_safety.py: kept every test that calls format_pass
  or gate_consolidated_pass-adjacent REGISTRY checks directly (wire format,
  clean-format rules, REGISTRY defaults); deleted TestNoradIdTypeCoercion
  and TestStalenessGuard (handle_satpass-only logic, no live equivalent)
  and the 6 _elevation_bucket tests (_elevation_bucket itself was dead
  before this pass too — zero callers anywhere but its own tests, already
  superseded by the numeric "max NN°" wire format per its own docstring).
- test_satpass_persisted_timer.py: dropped test_due_at_persisted_on_normal_ingest
  (handle_satpass-only); kept the two schema/migration tests, which don't
  touch satpass_handler.
- Deleted outright (tested ONLY the dead Central envelope-ingest path, no
  live equivalent to port to): test_satpass_event_path.py,
  test_satpass_compass_fallback.py, test_satpass_wire_fields.py.

Full suite: 2059 passed, 0 failed (was 0 failed on main pre-change).
Satpass/TLE subset (99 tests across 8 files) verified green in isolation.

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>
This commit is contained in:
malice 2026-07-17 15:46:36 -06:00 committed by GitHub
commit 41178831e4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 590 additions and 2483 deletions

View file

@ -67,11 +67,17 @@ def _seed_tle(conn, *, norad_id, name, line1, line2, epoch, updated_at=None):
class TestTLEUpsert:
"""T1: TLE upsert latest-wins on epoch."""
"""T1: TLE upsert latest-wins on epoch.
Exercises `upsert_tle` directly the shared latest-wins primitive used
by every writer of `sat_tles` (native env.tle_fetch is the only one
left; the Central envelope ingest path that used to call it via
`tle_handler.handle_tle` was retired with Central).
"""
def test_newer_epoch_updates(self):
_enable_satpass()
from meshai.central.tle_handler import handle_tle
from meshai.env.satellite.tle_store import upsert_tle
conn = get_db()
now = int(time.time())
@ -79,20 +85,9 @@ class TestTLEUpsert:
_seed_tle(conn, norad_id=25544, name="ISS", line1="OLD1", line2="OLD2",
epoch="2024-06-10T00:00:00Z")
# Send newer TLE
env = {
"data": {
"adapter": "celestrak_tle",
"data": {
"norad_id": 25544,
"satellite_name": "ISS (ZARYA)",
"tle_line1": "NEW1",
"tle_line2": "NEW2",
"epoch": "2024-06-15T00:00:00Z",
},
}
}
handle_tle(env, "central.sat.tle.25544", now=now)
# Upsert a newer TLE
upsert_tle(conn, 25544, "ISS (ZARYA)", "NEW1", "NEW2",
"2024-06-15T00:00:00Z", now=now)
row = conn.execute("SELECT line1, line2 FROM sat_tles WHERE norad_id=25544").fetchone()
assert row["line1"] == "NEW1"
@ -100,7 +95,7 @@ class TestTLEUpsert:
def test_older_epoch_skipped(self):
_enable_satpass()
from meshai.central.tle_handler import handle_tle
from meshai.env.satellite.tle_store import upsert_tle
conn = get_db()
now = int(time.time())
@ -108,49 +103,20 @@ class TestTLEUpsert:
_seed_tle(conn, norad_id=25544, name="ISS", line1="CURRENT1", line2="CURRENT2",
epoch="2024-06-15T00:00:00Z")
# Send older TLE — should be skipped
env = {
"data": {
"adapter": "celestrak_tle",
"data": {
"norad_id": 25544,
"satellite_name": "ISS (ZARYA)",
"tle_line1": "OLD1",
"tle_line2": "OLD2",
"epoch": "2024-06-10T00:00:00Z",
},
}
}
handle_tle(env, "central.sat.tle.25544", now=now)
# Upsert an older TLE — should be skipped
written = upsert_tle(conn, 25544, "ISS (ZARYA)", "OLD1", "OLD2",
"2024-06-10T00:00:00Z", now=now)
assert written is False
row = conn.execute("SELECT line1 FROM sat_tles WHERE norad_id=25544").fetchone()
assert row["line1"] == "CURRENT1", "older epoch should not overwrite"
def test_returns_none_always(self):
"""TLE handler is storage-only, never returns wire."""
_enable_satpass()
from meshai.central.tle_handler import handle_tle
env = {
"data": {
"adapter": "celestrak_tle",
"data": {
"norad_id": 99999,
"satellite_name": "TEST",
"tle_line1": "L1",
"tle_line2": "L2",
"epoch": "2024-06-15T00:00:00Z",
},
}
}
result = handle_tle(env, "central.sat.tle.99999")
assert result is None
class TestTLEStaleness:
"""T2: 14-day staleness exclusion at read time."""
def test_fresh_tle_returned(self):
from meshai.central.tle_handler import get_tle_by_norad
from meshai.env.satellite.tle_store import get_tle_by_norad
conn = get_db()
# Seed with recent epoch
recent = (datetime.now(timezone.utc) - timedelta(days=2)).isoformat()
@ -161,7 +127,7 @@ class TestTLEStaleness:
assert tle["norad_id"] == 25544
def test_stale_tle_excluded(self):
from meshai.central.tle_handler import get_tle_by_norad
from meshai.env.satellite.tle_store import get_tle_by_norad
conn = get_db()
# Seed with 15-day old epoch
stale = (datetime.now(timezone.utc) - timedelta(days=15)).isoformat()
@ -171,7 +137,7 @@ class TestTLEStaleness:
assert tle is None, "stale TLE (>14 days) should be excluded"
def test_search_excludes_stale(self):
from meshai.central.tle_handler import search_tle_by_name
from meshai.env.satellite.tle_store import search_tle_by_name
conn = get_db()
stale = (datetime.now(timezone.utc) - timedelta(days=15)).isoformat()
_seed_tle(conn, norad_id=25544, name="ISS (ZARYA)", line1=ISS_LINE1,
@ -185,7 +151,7 @@ class TestPassPredictor:
def test_iss_produces_passes(self):
"""ISS TLE for Boise should produce at least one pass in 24h."""
from meshai.central.pass_predictor import compute_passes
from meshai.env.satellite.pass_predictor import compute_passes
# Use a fixed time near the TLE epoch for best accuracy
start = datetime(2024, 6, 16, 0, 0, 0, tzinfo=timezone.utc)
passes = compute_passes(ISS_LINE1, ISS_LINE2, BOISE_LAT, BOISE_LON,
@ -194,7 +160,7 @@ class TestPassPredictor:
def test_pass_max_elevation_reasonable(self):
"""Max elevation should be between min_el and 90°."""
from meshai.central.pass_predictor import compute_passes
from meshai.env.satellite.pass_predictor import compute_passes
start = datetime(2024, 6, 16, 0, 0, 0, tzinfo=timezone.utc)
passes = compute_passes(ISS_LINE1, ISS_LINE2, BOISE_LAT, BOISE_LON,
window_h=24, min_el=10.0, now=start)
@ -204,7 +170,7 @@ class TestPassPredictor:
def test_pass_aos_before_los(self):
"""AOS should be before LOS for every pass."""
from meshai.central.pass_predictor import compute_passes
from meshai.env.satellite.pass_predictor import compute_passes
start = datetime(2024, 6, 16, 0, 0, 0, tzinfo=timezone.utc)
passes = compute_passes(ISS_LINE1, ISS_LINE2, BOISE_LAT, BOISE_LON,
window_h=24, min_el=10.0, now=start)
@ -214,7 +180,7 @@ class TestPassPredictor:
def test_pass_duration_reasonable(self):
"""Pass durations should be positive; 30s step may merge adjacent passes."""
from meshai.central.pass_predictor import compute_passes
from meshai.env.satellite.pass_predictor import compute_passes
start = datetime(2024, 6, 16, 0, 0, 0, tzinfo=timezone.utc)
passes = compute_passes(ISS_LINE1, ISS_LINE2, BOISE_LAT, BOISE_LON,
window_h=24, min_el=10.0, now=start)
@ -232,7 +198,7 @@ class TestPassPredictor:
over Boise (43.6°N, 51.6° inclination orbit). We assert that at
least one pass in 24h exceeds 30° a conservative threshold.
"""
from meshai.central.pass_predictor import compute_passes
from meshai.env.satellite.pass_predictor import compute_passes
start = datetime(2024, 6, 16, 0, 0, 0, tzinfo=timezone.utc)
passes = compute_passes(ISS_LINE1, ISS_LINE2, BOISE_LAT, BOISE_LON,
window_h=24, min_el=10.0, now=start)
@ -243,7 +209,7 @@ class TestPassPredictor:
def test_azimuth_range(self):
"""Azimuths should be in [0, 360) range."""
from meshai.central.pass_predictor import compute_passes
from meshai.env.satellite.pass_predictor import compute_passes
start = datetime(2024, 6, 16, 0, 0, 0, tzinfo=timezone.utc)
passes = compute_passes(ISS_LINE1, ISS_LINE2, BOISE_LAT, BOISE_LON,
window_h=24, min_el=10.0, now=start)
@ -252,7 +218,7 @@ class TestPassPredictor:
assert 0 <= p.azimuth_at_los < 360, f"LOS azimuth {p.azimuth_at_los} out of range"
def test_compass_conversion(self):
from meshai.central.pass_predictor import azimuth_to_compass
from meshai.env.satellite.pass_predictor import azimuth_to_compass
assert azimuth_to_compass(0) == "N"
assert azimuth_to_compass(45) == "NE"
assert azimuth_to_compass(90) == "E"
@ -442,7 +408,7 @@ class TestReplyFormat:
def test_line_format_matches_spec(self):
"""Lines should match 'NAME HH:MMHH:MM TZ max XX° DIR→DIR'."""
from meshai.central.pass_predictor import compute_passes, azimuth_to_compass, PassInfo
from meshai.env.satellite.pass_predictor import compute_passes, azimuth_to_compass, PassInfo
from meshai.commands.satpass_cmd import SatpassCommand
from zoneinfo import ZoneInfo