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.
This commit is contained in:
parent
23eb205fe8
commit
1d36729bed
18 changed files with 2079 additions and 0 deletions
53
tests/test_regime.py
Normal file
53
tests/test_regime.py
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
"""Tests for orbital regime classification."""
|
||||
|
||||
from pg_orrery_catalog.regime import classify_regime, regime_summary
|
||||
from pg_orrery_catalog.tle import TLERecord
|
||||
|
||||
|
||||
def _make_record_with_mm(norad_id: int, mean_motion: float) -> TLERecord:
|
||||
"""Create a TLERecord with a specific mean motion in line2."""
|
||||
line1 = f"1 {norad_id:05d}U 98067A 24001.50000000 .00000000 00000-0 00000-0 0 9990"
|
||||
mm_str = f"{mean_motion:011.8f}"
|
||||
line2 = f"2 {norad_id:05d} 51.6400 100.0000 0007417 30.0000 330.1234 {mm_str}999990"
|
||||
return TLERecord(
|
||||
line1=line1, line2=line2, name=f"SAT-{norad_id}",
|
||||
norad_id=norad_id, epoch=24001.0,
|
||||
)
|
||||
|
||||
|
||||
class TestClassifyRegime:
|
||||
def test_leo(self):
|
||||
assert classify_regime(15.5) == "LEO" # ISS-like
|
||||
assert classify_regime(11.26) == "LEO" # boundary
|
||||
|
||||
def test_meo(self):
|
||||
assert classify_regime(2.0) == "MEO" # GPS-like
|
||||
assert classify_regime(11.25) == "MEO" # just below LEO
|
||||
|
||||
def test_geo(self):
|
||||
assert classify_regime(1.0) == "GEO" # near-synchronous
|
||||
assert classify_regime(0.91) == "GEO"
|
||||
|
||||
def test_heo(self):
|
||||
assert classify_regime(0.5) == "HEO" # Molniya-like
|
||||
assert classify_regime(0.9) == "HEO" # boundary
|
||||
assert classify_regime(0.1) == "HEO" # deep space
|
||||
|
||||
|
||||
class TestRegimeSummary:
|
||||
def test_mixed(self):
|
||||
records = {
|
||||
1: _make_record_with_mm(1, 15.5), # LEO
|
||||
2: _make_record_with_mm(2, 2.0), # MEO
|
||||
3: _make_record_with_mm(3, 1.0), # GEO
|
||||
4: _make_record_with_mm(4, 0.5), # HEO
|
||||
}
|
||||
summary = regime_summary(records)
|
||||
assert summary["LEO"] == 1
|
||||
assert summary["MEO"] == 1
|
||||
assert summary["GEO"] == 1
|
||||
assert summary["HEO"] == 1
|
||||
|
||||
def test_empty(self):
|
||||
summary = regime_summary({})
|
||||
assert summary == {"LEO": 0, "MEO": 0, "GEO": 0, "HEO": 0}
|
||||
Loading…
Add table
Add a link
Reference in a new issue