022b(cli): gengnss almanac subcommand + --visible almanac-driven selection #39

Merged
ykp merged 4 commits from feat/022b-almanac-cli into main 2026-09-29 22:22:32 +02:00
Owner

Implements step 022b (almanac core 022a merged in #38). Per the owner's standing direction, the work order is untracked from the backlog index and archived with its outcome in docs/done/022b-almanac-cli.md.

What it adds

gengnss almanac [FILE|URL] (no args = latest CelesTrak Yuma; --sem switches parser/default URL):

  • table: sat_id healthy slot/k elevation° azimuth° — visibility columns only when --lat/--lon are given; --when ISO-8601 UTC (default now), --min-elev
  • --json prints the 022a interchange document (machine-parseable, even with --save); --save PATH writes it (GLONASS hand-curation target)
  • local files parsed directly (Yuma/SEM/JSON sniffed); URLs go through the on-disk cache (24 h staleness, GENGNSS_CACHE_DIR)
  • exit codes: 0 ok / 1 fetch failure / 2 usage — no tracebacks

--visible on run/show: selects every healthy almanac satellite visible from --lat/--lon at --when above --min-elev (no position = all healthy). Mutually exclusive with --signal/--preset (exit 2). --almanac FILE|URL overrides the source; --offline forbids network.

Reproducibility guarantee (the key property): --visible resolves at run time and freezes the selections as explicit entries in the sidecar — gengnss scenario <sidecar> regenerates byte-identically even when the live constellation later changes (test proves regeneration works with almanac fetching forbidden afterwards).

Core tweak: fetch_almanac gained offline=True (cache-only, any age; missing cache → clean RuntimeError).

Tests (tests/test_cli_almanac.py, 12, network-free)

  • table/JSON/save/sem parsing from fixture files; URL cache-reuse (fake opener, 1 network call for 2 runs)
  • run --visible: selections match the core visibility computation; sidecar frozen; byte-identical regeneration with fetching forbidden
  • show --visible resolved selections; no-position → all healthy; conflicts → exit 2; missing file → exit 2; offline empty-cache → exit 1, no traceback

ruff clean; pytest: 235 passed, 8 skipped. README CLI examples added.

Implements step **022b** (almanac core 022a merged in #38). Per the owner's standing direction, the work order is **untracked from the backlog index** and archived with its outcome in `docs/done/022b-almanac-cli.md`. ## What it adds **`gengnss almanac [FILE|URL]`** (no args = latest CelesTrak Yuma; `--sem` switches parser/default URL): - table: `sat_id healthy slot/k elevation° azimuth°` — visibility columns only when `--lat/--lon` are given; `--when` ISO-8601 UTC (default now), `--min-elev` - `--json` prints the 022a interchange document (machine-parseable, even with `--save`); `--save PATH` writes it (GLONASS hand-curation target) - local files parsed directly (Yuma/SEM/JSON sniffed); URLs go through the on-disk cache (24 h staleness, `GENGNSS_CACHE_DIR`) - exit codes: 0 ok / 1 fetch failure / 2 usage — **no tracebacks** **`--visible` on `run`/`show`**: selects every healthy almanac satellite visible from `--lat/--lon` at `--when` above `--min-elev` (no position = all healthy). Mutually exclusive with `--signal`/`--preset` (exit 2). `--almanac FILE|URL` overrides the source; `--offline` forbids network. **Reproducibility guarantee (the key property):** `--visible` resolves at run time and freezes the selections as explicit entries in the sidecar — `gengnss scenario <sidecar>` regenerates **byte-identically** even when the live constellation later changes (test proves regeneration works with almanac fetching forbidden afterwards). Core tweak: `fetch_almanac` gained `offline=True` (cache-only, any age; missing cache → clean `RuntimeError`). ## Tests (`tests/test_cli_almanac.py`, 12, network-free) - table/JSON/save/sem parsing from fixture files; URL cache-reuse (fake opener, 1 network call for 2 runs) - `run --visible`: selections match the core visibility computation; sidecar frozen; byte-identical regeneration with fetching forbidden - `show --visible` resolved selections; no-position → all healthy; conflicts → exit 2; missing file → exit 2; offline empty-cache → exit 1, no traceback `ruff` clean; `pytest`: **235 passed, 8 skipped**. README CLI examples added.
022b(cli): gengnss almanac subcommand + --visible almanac-driven selection
Some checks failed
ci / test (3.14) (push) Failing after 53s
ci / test (3.12) (push) Failing after 1m0s
ci / test (3.9) (push) Failing after 2m46s
ci / test (3.12) (pull_request) Failing after 2m41s
ci / test (3.14) (pull_request) Failing after 2m17s
ci / test (3.9) (pull_request) Failing after 2m15s
7a811a73a5
Implements docs/done/022b-almanac-cli.md (untracked from the backlog
per owner decision; 022a merged in #38):

- gengnss almanac [FILE|URL]: healthy-satellite table (healthy flag,
  GLONASS-style slot/k where present, elevation/azimuth columns when a
  position is given), --json machine output, --save, --sem parser
  switch, --offline cache-only mode; no args = latest CelesTrak Yuma;
  local files parsed directly (Yuma/SEM/JSON sniffed); fetch failures
  exit 1 with a clean message, usage/parse errors exit 2
- run/show --visible: selects every healthy almanac satellite visible
  from --lat/--lon at --when (ISO-8601 UTC) above --min-elev; mutually
  exclusive with --signal/--preset (exit 2); --almanac FILE|URL
  overrides the source; selections are frozen as explicit entries in
  the sidecar so regeneration stays byte-identical when the live
  constellation changes (verified: regen works even with fetching
  forbidden afterwards)
- core: fetch_almanac gains offline=True (cache-only, any age)
- README: almanac + --visible CLI examples

Tests: tests/test_cli_almanac.py (12 network-free tests: table/JSON/
save/sem/missing-file/offline-no-cache/URL-cache-reuse; run --visible
selection freeze + byte-identical regen; show --visible; conflict and
error exits). ruff clean; pytest 235 passed, 8 skipped.
ci: retrigger
Some checks failed
ci / test (3.14) (push) Failing after 50s
ci / test (3.12) (push) Failing after 58s
ci / test (3.9) (push) Failing after 1m25s
ci / test (3.12) (pull_request) Failing after 1m43s
ci / test (3.14) (pull_request) Failing after 1m38s
ci / test (3.9) (pull_request) Failing after 1m17s
4be4c1e4f2
ci: retrigger (forge network flake window?)
Some checks failed
ci / test (3.14) (push) Failing after 53s
ci / test (3.12) (push) Failing after 1m3s
ci / test (3.9) (push) Failing after 1m55s
ci / test (3.12) (pull_request) Failing after 1m47s
ci / test (3.9) (pull_request) Has been cancelled
ci / test (3.14) (pull_request) Has been cancelled
787336c547
fix(tests): make tests a package — cross-test imports broke plain pytest
All checks were successful
ci / test (3.9) (push) Successful in 1m59s
ci / test (3.12) (pull_request) Successful in 1m57s
ci / test (3.14) (pull_request) Successful in 2m23s
ci / test (3.9) (pull_request) Successful in 2m25s
ci / test (3.14) (push) Successful in 1m14s
ci / test (3.12) (push) Successful in 1m19s
0aa6f2fb77
tests/test_cli_almanac.py imports fixtures from tests.test_almanac;
without tests/__init__.py that import only resolves when pytest runs as
'python -m pytest' (CWD on sys.path) and fails at collection with
ModuleNotFoundError under the bare 'pytest' script CI uses. Making the
test directory a package fixes both invocation styles.
ykp merged commit cb6b5a8bd8 into main 2026-09-29 22:22:32 +02:00
ykp deleted branch feat/022b-almanac-cli 2026-09-29 22:22:32 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
ykp/gengnss!39
No description provided.