Ryan Malloy 1d36729bed Implement pg-orrery-catalog: TLE catalog builder for pg_orrery
Core modules:
- tle.py: NORAD decoding (Alpha-5 + Super-5, matching get_el.c),
  3LE/2LE parsing, TLERecord dataclass with epoch-based dedup
- config.py: TOML config + env var overlay (XDG-compliant paths)
- cache.py: File-based cache with staleness checking
- catalog.py: Multi-source merge with MergeStats tracking
- regime.py: LEO/MEO/GEO/HEO classification by mean motion

Source downloaders (httpx):
- celestrak.py: Active catalog + supplemental GP groups
- satnogs.py: JSON API with 3LE conversion
- spacetrack.py: POST auth flow, bulk GP download

Output formatters:
- sql.py: pg_orrery-compatible INSERT generation (E'' strings)
- tle_file.py: Standard 3LE text output
- json_out.py: JSON with orbital metadata and regime

CLI (Click + Rich):
- download: Cache TLEs from all sources
- build: Merge + output SQL/3LE/JSON (pipes to psql)
- load: Direct DB load via psycopg (optional [pg] extra)
- info: Cache stats and configuration display

58 tests covering NORAD decoding (all 4 encoding cases),
parsing, merge/dedup, SQL escaping, regime classification.
2026-02-18 00:31:46 -07:00

36 lines
1.1 KiB
Python

"""Orbital regime classification based on mean motion.
Thresholds match the bench/load_bench.sh SQL query:
LEO: mean_motion > 11.25 rev/day (period < 128 min, alt < ~2000 km)
MEO: mean_motion > 1.8 rev/day (period < 800 min)
GEO: mean_motion > 0.9 rev/day (near-synchronous)
HEO: everything else (Molniya, tundra, GTO, etc.)
"""
from .tle import TLERecord
def classify_regime(mean_motion: float) -> str:
"""Classify orbital regime from mean motion (revs/day)."""
if mean_motion > 11.25:
return "LEO"
if mean_motion > 1.8:
return "MEO"
if mean_motion > 0.9:
return "GEO"
return "HEO"
def classify_record(rec: TLERecord) -> str:
"""Classify a TLERecord's orbital regime."""
return classify_regime(rec.mean_motion)
def regime_summary(records: dict[int, TLERecord]) -> dict[str, int]:
"""Count objects per regime."""
counts: dict[str, int] = {"LEO": 0, "MEO": 0, "GEO": 0, "HEO": 0}
for rec in records.values():
regime = classify_record(rec)
counts[regime] = counts.get(regime, 0) + 1
return counts