feat: lintle verify --orbit — sampled sgp4 orbit-consistency (#144 core)#162
Merged
Conversation
, core) Add the opt-in `lintle verify --orbit` physics pass (Increment 2, goal 2). For each sampled satellite's epoch-sorted track, propagate every TLE forward to its neighbour's epoch with sgp4 and measure the position residual (km): - **soft VRFY-ORBIT-OUTLIER** (inconclusive) — a residual over a robust per-satellite threshold `max(100 km, median + 10·MAD)` (flat 100 km floor until >=10 epochs). Never convicts: a real manoeuvre looks identical to a corruption from a single pair, so a lone outlier is telemetry, not a conviction. - **hard VRFY-ORBIT-ERROR** — sgp4 rejecting an element set as unphysical (error codes 1-5). A safety net: on cleaned (validate-passing) data this is ~0. Decay (error 6) is a real end-of-life state, never convicted; it just breaks the propagation chain. Determinism: residuals are rounded to a 0.1 km quantum before thresholding and an outlier must clear the threshold by a full quantum, so the suspect set + exit code are byte-reproducible across platforms (golden Vanguard fixture locks the exact residuals). Epochs come from Satrec.jdsatepoch+jdsatepochF (sgp4's own parse, never re-derived). A 3-day adjacent-gap gate skips pairs too far apart to compare. Sampling unit is the satellite (continuity needs a contiguous track); the sample is deterministic (evenly-spaced over sorted catalogs), default `--sample 3000`, `--all` for the full sweep. Constant memory w.r.t. the corpus: records stream through verify's ExternalSorter, one track held at a time. Packaging: sgp4 promoted from dev-only to a verify-scoped runtime dependency. orbit.py is the sole sgp4 importer, lazily imported by run_verify only when --orbit is set; the clean/validate/repair path stays walled off (import-graph test still green). Deferred to a follow-up (core-first slice): leave-one-out spike-vs-manoeuvre culprit isolation, stratified oversampling, the --sensitivity dial, and LEO/MEO/GEO gap-gate tuning. Verified on a 100k-record real cleaned slice: 8577 satellites, 88025 pairs, 0 hard, 125 soft outliers (exit 0). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
README: a physics-pass paragraph in the verify section + the --orbit/--sample/ --all flags and the soft-outlier exit-code note. ARCHITECTURE: orbit.py in the verify/ tree + table row (the sole, lazy sgp4 importer); §7 moves sgp4 into the runtime-deps intro and out of the dev-only exempt list — the promotion has landed. CHANGELOG: an unreleased `verify --orbit` bullet. CLAUDE.md: the runtime-deps line and the sgp4-promotion sentence updated to past tense. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
lintle verify --orbit— sampled sgp4 orbit-consistency (#144, core slice)The opt-in physics pass (verify Increment 2, goal 2). For each sampled satellite's epoch-sorted
track, propagate every cleaned TLE forward to its neighbour's epoch with
sgp4and measure theposition residual (km).
Advances #144 — this is the core-first slice the owner chose; the deferred refinements
are tracked in a follow-up (see below). Does not close #144.
Verdict model (safe by construction)
VRFY-ORBIT-OUTLIER(inconclusive) — a residual over the robust per-satellitethreshold
max(100 km, median + 10·MAD)(flat 100 km floor until ≥10 epochs). Neverconvicts: a real manoeuvre and a corruption are indistinguishable from a single pair, so an
outlier is telemetry, not a conviction — it does not raise the exit code.
VRFY-ORBIT-ERROR—sgp4rejecting an element set as physically unphysical(error codes 1–5). A safety net: on cleaned (validate-passing) data this is ~0. Decay
(error 6) is a real end-of-life state, never convicted; it just breaks the propagation chain.
Determinism & correctness
byte-reproducible suspect set + exit code across platforms. Golden Vanguard fixture locks
the exact residuals
[8.4, 0.9, 0.6, 0.3, 0.4].Satrec.jdsatepoch + jdsatepochF(sgp4's own parse, never re-derived). 3-dayadjacent-gap gate skips pairs too far apart.
verify'sExternalSorter, one track ata time). Deterministic, evenly-spaced satellite sample; default
--sample 3000/--all.Packaging
sgp4promoted from dev-only to a verify-scoped runtime dependency.orbit.pyis the package'ssole
sgp4importer, lazily imported byrun_verifyonly under--orbit; theclean/validate/repair path stays walled off (import-graph test still green).
Deferred to the follow-up (core-first)
Leave-one-out spike-vs-manoeuvre culprit isolation, stratified oversampling of repaired/dup-epoch
satellites, the
--sensitivitydial, and LEO/MEO/GEO gap-gate tuning.Verification
ruff check+ruff format --checkclean.tle2006slice → 8577 satellites, 88025 pairs, 0 hard,125 soft outliers (exit 0). Example: catalog 879, a 2229.9 km residual flagged inconclusive.
🤖 Generated with Claude Code