mirror of
https://github.com/zvx-echo6/meshai.git
synced 2026-08-26 17:31:34 +00:00
feat(phase4a): peak_compass end-to-end + repair stale satpass test suite (#35)
Add azimuth-at-peak-elevation compass to the satpass pipeline (Matt's decision: extend pass_predictor) and repair the 24 stale satpass tests that were written against the pre-async handle_satpass return contract. - pass_predictor.PassInfo gains azimuth_at_peak; _build_pass populates it from the already-computed peak sample - !satpass DM wire + Central consolidated broadcast wire render aos→peak→los - satpass_pending gains a peak_compass column (migration v21, SCHEMA_VERSION 20->21); persisted at ingest, carried through consolidation from the max-elevation observer's row - 24 stale satpass tests repaired to the ingest→consolidate two-call contract (none weakened); satpass suite 112/112 green - full suite 34->10 failures (the 24 were these stale tests) NOTE: peak_compass changes the live Central satpass wire — deploy is HELD until the coordinated all-native flip (per Matt). 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
531bd20378
commit
a756143284
9 changed files with 239 additions and 115 deletions
|
|
@ -145,6 +145,27 @@ def _clear_handler_flags():
|
|||
delattr(handle_satpass, attr)
|
||||
|
||||
|
||||
def _ingest_and_consolidate(env, subject, *, now, data=None):
|
||||
"""Drive the two-call async satpass contract (ingest → consolidate).
|
||||
|
||||
handle_satpass() ingests the pass and returns None; the consumer then
|
||||
runs consolidate_satpass_pending(), which yields the (wire, data) to
|
||||
broadcast (or None). Returns the consolidation result so a test can
|
||||
assert on the real broadcast wire.
|
||||
"""
|
||||
from meshai.central.satpass_handler import (
|
||||
handle_satpass, consolidate_satpass_pending,
|
||||
drain_pending_consolidation_ids)
|
||||
drain_pending_consolidation_ids()
|
||||
assert handle_satpass(
|
||||
env, subject, data=data if data is not None else {}, now=now) is None
|
||||
for cid in drain_pending_consolidation_ids():
|
||||
res = consolidate_satpass_pending(cid)
|
||||
if res is not None:
|
||||
return res
|
||||
return None
|
||||
|
||||
|
||||
# ── (a) satpass_predict envelope: raw azimuths → non-empty compass ───
|
||||
|
||||
def test_satpass_predict_compass_from_raw_azimuths():
|
||||
|
|
@ -156,29 +177,26 @@ def test_satpass_predict_compass_from_raw_azimuths():
|
|||
_clear_handler_flags()
|
||||
|
||||
now = 1781330520 # well before the AO-27 pass window
|
||||
wire = handle_satpass(
|
||||
result = _ingest_and_consolidate(
|
||||
AO27_PREDICT_ENVELOPE,
|
||||
"central.sat.pass.us.id.filer",
|
||||
data={},
|
||||
now=now,
|
||||
)
|
||||
assert wire is not None, "handler returned None for satpass_predict envelope"
|
||||
assert result is not None, "handler returned None for satpass_predict envelope"
|
||||
wire, _ = result
|
||||
|
||||
lines = wire.split("\n")
|
||||
assert len(lines) == 2, f"Expected 2 lines, got {len(lines)}: {wire!r}"
|
||||
|
||||
# Line 1 must contain non-empty compass directions
|
||||
# 163.2° → S (8-point compass), 348.7° → N
|
||||
assert "S" in lines[0].split(",")[-1], f"Expected S (from 163.2°) in compass portion: {lines[0]!r}"
|
||||
assert "\u2192" in lines[0], f"Expected → arrow in line 1: {lines[0]!r}"
|
||||
|
||||
# Extract the compass portion: after bucket comma, before newline
|
||||
# Format: 🛰️ {name} {bucket}, {aos_compass}→{los_compass}
|
||||
arrow_idx = lines[0].index("\u2192")
|
||||
los_part = lines[0][arrow_idx + 1:]
|
||||
assert los_part != "", f"los_compass is empty in line 1: {lines[0]!r}"
|
||||
# 348.7° → N in 8-point compass
|
||||
assert los_part == "N", f"Expected N (from 348.7°) after arrow: {los_part!r}"
|
||||
# Line 1 format: name bucket, aos→peak→los. Raw azimuths convert
|
||||
# to a non-empty 3-point compass sweep.
|
||||
compass = lines[0].split(", ")[-1]
|
||||
parts = compass.split("\u2192")
|
||||
assert len(parts) == 3, f"Expected aos->peak->los sweep, got {parts!r}"
|
||||
# 163.2 -> S, 245.0 -> SW (peak), 348.7 -> N (8-point compass)
|
||||
assert parts[0] == "S", f"Expected S (from 163.2): {parts!r}"
|
||||
assert parts[1] == "SW", f"Expected SW (from peak 245.0): {parts!r}"
|
||||
assert parts[2] == "N", f"Expected N (from 348.7): {parts!r}"
|
||||
|
||||
|
||||
# ── (b) n2yo envelope: precomputed _compass strings used as-is ───────
|
||||
|
|
@ -192,21 +210,22 @@ def test_n2yo_precomputed_compass_unchanged():
|
|||
_clear_handler_flags()
|
||||
|
||||
now = 1781065800 # before NOAA-18 pass window
|
||||
wire = handle_satpass(
|
||||
result = _ingest_and_consolidate(
|
||||
N2YO_ENVELOPE,
|
||||
"central.sat.pass.us.id.filer",
|
||||
data={},
|
||||
now=now,
|
||||
)
|
||||
assert wire is not None, "handler returned None for n2yo envelope"
|
||||
assert result is not None, "handler returned None for n2yo envelope"
|
||||
wire, _ = result
|
||||
|
||||
lines = wire.split("\n")
|
||||
# Must use the precomputed strings: SE→N
|
||||
assert "SE" in lines[0], f"Expected SE from precomputed _compass: {lines[0]!r}"
|
||||
# The los_compass from precomputed is "N"
|
||||
arrow_idx = lines[0].index("\u2192")
|
||||
los_part = lines[0][arrow_idx + 1:]
|
||||
assert los_part == "N", f"Expected precomputed los_compass N, got {los_part!r}"
|
||||
# Must use the precomputed strings verbatim: SE→ENE→N
|
||||
compass = lines[0].split(", ")[-1]
|
||||
parts = compass.split("\u2192")
|
||||
assert len(parts) == 3, f"Expected aos->peak->los sweep, got {parts!r}"
|
||||
assert parts[0] == "SE", f"Expected precomputed aos SE: {parts!r}"
|
||||
assert parts[1] == "ENE", f"Expected precomputed peak ENE: {parts!r}"
|
||||
assert parts[2] == "N", f"Expected precomputed los N: {parts!r}"
|
||||
|
||||
|
||||
# ── (c) envelope with neither → empty compass, no crash ──────────────
|
||||
|
|
@ -228,16 +247,16 @@ def test_no_compass_no_azimuth_no_crash():
|
|||
d.pop(key, None)
|
||||
|
||||
now = 1781330520
|
||||
wire = handle_satpass(
|
||||
result = _ingest_and_consolidate(
|
||||
env,
|
||||
"central.sat.pass.us.id.filer",
|
||||
data={},
|
||||
now=now,
|
||||
)
|
||||
assert wire is not None, "handler crashed or returned None — should produce wire with empty compass"
|
||||
assert result is not None, "handler crashed or returned None — should produce wire with empty compass"
|
||||
wire, _ = result
|
||||
|
||||
lines = wire.split("\n")
|
||||
assert len(lines) == 2
|
||||
# Arrow should still be present with empty directions: "→" or similar
|
||||
assert "\u2192" in lines[0], f"Expected → in line 1 even with empty compass: {lines[0]!r}"
|
||||
# Arrow still present even with empty (peak empty -> no peak segment):
|
||||
assert "\u2192" in lines[0], f"Expected arrow in line 1 even with empty compass: {lines[0]!r}"
|
||||
# No crash = test passes
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue