central/tests
malice 3f1fec9846
v0.13.0: sat_orbits adapter (forward-orbit-track per satellite) + antimeridian splitter
## Matt's "each sat's path" framing

After enabling the satellite family in v0.12.1, the `/events` map showed overlapping orange visibility-footprint circles from satpass_predict + a polar-orbit ground track wrapping the wrong way across the antimeridian (the v0.11.2 documented limitation). Matt's ask:

> honestly i just want each sats path.

Interpreted as: one continuous orbital track per satellite, color-coded, no observer-specific clutter, no visibility-footprint overlays. Six tracked sats = six distinguishable lines on the map.

## Family placement — global line counterpart to global points

| Adapter | What it publishes | Geometry | Cadence |
|---|---|---|---|
| satpass_predict (v0.11.1) | Observer-anchored pass alerts | LineString ground-track + Polygon footprint per pass | 1h |
| sat_positions (v0.12.0) | Current sub-sat POINT per sat | Point centroid only | 60s |
| **sat_orbits (this PR)** | Forward-orbit LINE per sat | LineString / MultiLineString, 90min horizon | 5min |

Each answers a different question; they complement.

## Antimeridian splitter — shared sat_common primitive

`split_antimeridian(coords)` lives in `sat_common.py` next to `gmst_rad` / `eci_to_ecef` / `subsatellite_point`. Returns `None` for <2 vertices, a `LineString` dict for the common no-crossing case, or a `MultiLineString` dict when one or more ±180° crossings exist. Each crossing closes the current segment at `sign(prev_lon)*180` with a linearly-interpolated latitude and starts the next at `sign(cur_lon)*180` with the same lat (sub-0.1° error at LEO orbital speeds, well below Leaflet rendering precision).

**Sibling concern fixed:** `satpass_predict._build_pass_geometry` now routes its `ground_track` through `split_antimeridian` too. This was the v0.11.2 documented limitation ("polar-orbit crossings near ±180° will produce a polygon that visually wraps the wrong way"). Sat_orbits and satpass_predict share the helper because the antimeridian problem is identical for both — and **44/44 existing satpass_predict tests still pass** because the splitter returns a LineString identical in shape to the prior inline construction when there's no crossing (which is the case for every CONUS-observer ISS-fixture test).

New test specifically for the splitter inside `_build_pass_geometry`: synthesized polar-orbit `ground_track` produces a `GeometryCollection` whose linear-geometry component is a `MultiLineString` with 2 segments (first ends at +180, second starts at -180).

## GUI per-NORAD-ID color helper

20-line addition to `events_list.html`:

```js
function orbitColorForNoradId(norad) {
    var hue = (norad * 137.508) % 360;  // golden-angle hue distribution
    return "hsl(" + hue.toFixed(1) + ", 70%, 50%)";
}
function getRowColor(adapter, row) {
    if (adapter === "tomtom_flow") return flowColor(row.dataset.severity);
    if (adapter === "sat_orbits") {
        var norad = parseInt((row.dataset.eventId || "").split(":")[0], 10);
        if (!isNaN(norad)) return orbitColorForNoradId(norad);
    }
    return getAdapterColor(adapter);
}
```

`event_id` shape is `<norad_id>:<iso>` (same as sat_positions), so JS reads the first colon-token. **Additive**: tomtom_flow keeps its severity-based color, every other adapter keeps its per-adapter palette color, sat_orbits gets per-satellite distinguishable lines.

## Phase A sanity (per spec)

```
vertices = 91                                      ✓ (90min @ 60s + 1 endpoint)
first vertex = (170.66°, -17.15°, 417.4km)        ✓ matches v0.11.1 ISS pin
last vertex  = (140.52°, -8.60°, 415.9km)         ✓ geographically distinct
antimeridian crossings in 90min track = 1
geometry type = MultiLineString, 2 segments        ✓ splitter integrates
```

## Diff size

**+838 / −9 = +829 net** across 15 files. Spec budget was ≤800 lines. **29 over** — much tighter than v0.12.0 (894) or v0.12.1 (848). Adapter LoC 275 (well under 350 cap). sat_common splitter 51 LoC (~budget).

Test breakdown: 285 (sat_orbits) + 60 (sat_common splitter) + 26 (satpass regression) + 12 (events_feed) + 4 (telemetry-separation) = 387 LoC tests. Production: 275 + 51 + 37 (migration) + 41 (doc) + 16 (partials) + 21 (JS) + 15 (satpass refactor) + 2 (wiring) = 458 LoC.

## Test plan

- [x] `pytest tests/test_sat_orbits.py` — 19 new tests, all pass.
- [x] `pytest tests/test_sat_common.py` — 7 new splitter tests, 16 total pass.
- [x] `pytest tests/test_satpass_predict.py` — **45/45 pass** (44 existing regression-guard + 1 new polar-orbit splitter integration test). The `_build_pass_geometry` rewire is byte-identical for non-crossing tracks.
- [x] `pytest tests/test_events_feed_frontend.py` — 125/125 pass (sat_orbits sample + expected subject extended).
- [x] `pytest tests/test_telemetry_separation.py` — 9/9 pass (`_TELEMETRY` pin extended with `sat_orbits`).
- [x] `pytest tests/test_consumer_doc.py` — 6/6 pass (new `### sat_orbits` subsection accepted).
- [x] Full sweep `pytest tests/` (excluding postgres-dep files): **1274 passed, 1 skipped, 0 failures**.
- [x] Ruff: clean on all new + touched satellite-family code.

## Deploy plan

1. Squash-merge PR #N → tag v0.13.0 at merge SHA → push tag.
2. `ssh central`, `git pull` on `/opt/central`. **No `uv sync`** (no new dep).
3. Apply migration 041 manually via psql (per option C):
   `sudo -u postgres psql central -f /opt/central/sql/migrations/041_add_sat_orbits_adapter.sql`
4. `sudo systemctl restart central-supervisor` (picks up new adapter + STREAM_CATEGORY_DOMAINS extension) + `sudo systemctl restart central-gui` (picks up new partials + ADAPTER_GROUPS extension + JS color helper).
5. **No** `central-archive` restart (CENTRAL_SAT pre-existed; only the category-domain tuple grew, archive already covers `central.sat.>`).
6. Verify: `config.adapters` has `sat_orbits` row with `enabled=false`; supervisor log shows discovery; no polling until Matt flips it.
7. Matt enables via `/adapters/sat_orbits/edit` when ready. First poll happens within 5min; orbit-track LineStrings surface at `/telemetry` filtered by adapter=sat_orbits, color-coded per NORAD ID.

## Halt acknowledgment

Per spec acceptance bar #6: **squash-merge NOT authorized**. Branch + PR open. Halting for line-by-line review.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-06-09 18:50:47 -06:00
..
fixtures v0.11.0: new celestrak_tle adapter + CENTRAL_SAT satellite-tracking stream (#100) 2026-06-09 00:54:19 -06:00
__init__.py scaffold: initial repository structure 2026-05-15 19:16:24 +00:00
conftest.py feat(nwis): site + stats enrichment — named location + WaterWatch normalcy band (v0.8.0) 2026-05-25 15:30:19 +00:00
README.md chore: normalize line endings to LF 2026-05-16 22:26:12 +00:00
test_adapters.py fix(4-1): resolve api_key alias from per-adapter settings, not class attr 2026-05-19 23:08:11 +00:00
test_api_key_resolver.py fix(4-1): resolve api_key alias from per-adapter settings, not class attr 2026-05-19 23:08:11 +00:00
test_api_keys.py feat(gui): implement first-run setup wizard (1b-8) (#24) 2026-05-17 22:06:22 -06:00
test_apply_enrichment_coordless.py fix(3-M.b): apply_enrichment always attaches _enriched for declared adapters 2026-05-21 04:04:25 +00:00
test_archive_bbox_filter.py v0.10.2: monitoring-area bbox enforced at supervisor publish (was archive-only) (#PR_NUMBER_PLACEHOLDER) 2026-06-05 20:34:10 -06:00
test_archive_multi_stream.py feat(2-E.5): single-source-of-truth stream registry 2026-05-19 07:37:01 +00:00
test_audit.py feat(gui): add auth core, setup gate, and first-run operator creation 2026-05-17 05:30:49 +00:00
test_auth.py feat(gui): implement first-run setup wizard (1b-8) (#24) 2026-05-17 22:06:22 -06:00
test_avalanche_org.py v0.10.11: extend avalanche_org adapter — tombstones, geo.bbox, hyphen slugs (#99) 2026-06-08 23:08:22 -06:00
test_backend_settings_schema.py fix(3-L.5): per-backend settings schemas (fixes build_enrichers TypeError) 2026-05-20 23:10:10 +00:00
test_bootstrap_config.py chore: housekeeping - orphan branch + three stale tests (#22) 2026-05-17 18:14:58 -06:00
test_celestrak_tle.py v0.11.1: satpass_predict adapter (server-side pass alerts for fixed observers) (#101) 2026-06-09 01:16:43 -06:00
test_config_source.py chore(M-b): clear get_settings lru_cache in test fixtures (fixes order-dependent crypto failures + 3 latent siblings) 2026-05-21 15:51:51 +00:00
test_config_store.py chore(M-b): clear get_settings lru_cache in test fixtures (fixes order-dependent crypto failures + 3 latent siblings) 2026-05-21 15:51:51 +00:00
test_consumer_doc.py v0.10.3: rip out state_511_atis adapter (superseded by itd_511 v0.10.0; Castle Rock legacy shape EOL per sister-site discovery) (#88) 2026-06-06 14:44:00 -06:00
test_crypto.py chore: normalize line endings to LF 2026-05-16 22:26:12 +00:00
test_csrf_handler.py feat(gui): implement first-run setup wizard (1b-8) (#24) 2026-05-17 22:06:22 -06:00
test_csrf_race_condition.py feat(gui): implement first-run setup wizard (1b-8) (#24) 2026-05-17 22:06:22 -06:00
test_dashboard.py feat(2-E.5): single-source-of-truth stream registry 2026-05-19 07:37:01 +00:00
test_dedup_mixin.py fix(wzdx): drop 'unknown' direction from subject + extract dedup mixin (v0.9.1) 2026-05-25 21:18:21 +00:00
test_enrichment_config_plumbing.py fix(3-L.5): per-backend settings schemas (fixes build_enrichers TypeError) 2026-05-20 23:10:10 +00:00
test_enrichment_framework.py feat(3-J): enrichment framework + GeocoderEnricher + NoOpBackend + FIRMS pilot 2026-05-20 04:39:49 +00:00
test_enrichment_locations_coverage.py feat(gui-bugs): fix eonet dashboard exception + out-of-range map bbox 2026-05-24 22:38:13 +00:00
test_enrichment_mile_marker.py v0.10.6: extract mile_marker from itd_511 comment field as _enriched.mile_marker (#94) 2026-06-07 21:38:04 -06:00
test_eonet.py v0.9.20: regional subject routing on quake / fire / hydro / disaster adapters 2026-05-27 23:50:30 -06:00
test_events_adapter_column.py docs: add test database setup, restore geom to test fixture 2026-05-17 18:26:48 +00:00
test_events_bbox_guard.py feat(map-rework): fit-to-results, marker clustering, map-filter toggle, shape/opacity encoding (v0.7.2) 2026-05-25 01:20:04 +00:00
test_events_feed.py feat(api): add paginated events feed JSON endpoint (#25) 2026-05-17 22:31:00 -06:00
test_events_feed_frontend.py v0.13.0: sat_orbits adapter (forward-orbit-track per satellite) + antimeridian splitter 2026-06-09 18:50:47 -06:00
test_events_filtering.py Hide tombstones from default events view + show-removed toggle (v0.9.11) 2026-05-26 22:14:38 +00:00
test_events_pagination.py feat(layout-pagination): collapse legend, stabilize rows, real offset paginator (v0.7.3) 2026-05-25 02:04:23 +00:00
test_events_retention.py v0.9.13: per-stream archived-events retention sweep 2026-05-27 02:31:11 +00:00
test_fire_fused.py v0.9.14: fused FIRMS+WFIGS fire view 2026-05-27 03:49:30 +00:00
test_firms.py v0.10.2: monitoring-area bbox enforced at supervisor publish (was archive-only) (#PR_NUMBER_PLACEHOLDER) 2026-06-05 20:34:10 -06:00
test_form_descriptors.py v0.11.3: fix GUI adapter-edit 500 on list[int] settings fields 2026-06-09 13:26:57 -06:00
test_gdacs.py fix(2-E): use canonical removed-event subject pattern 2026-05-19 07:08:15 +00:00
test_geocoder_enricher.py feat(3-J): enrichment framework + GeocoderEnricher + NoOpBackend + FIRMS pilot 2026-05-20 04:39:49 +00:00
test_gui_adapter_edit.py v0.10.3: rip out state_511_atis adapter (superseded by itd_511 v0.10.0; Castle Rock legacy shape EOL per sister-site discovery) (#88) 2026-06-06 14:44:00 -06:00
test_gui_scaffold.py fix(tests): update tests for lazy app loading and 302 redirect 2026-05-17 06:14:25 +00:00
test_inciweb.py fix(2-C): wire dedup into poll loop, add conditional fetch 2026-05-19 03:53:10 +00:00
test_itd_511.py v0.10.6: extract mile_marker from itd_511 comment field as _enriched.mile_marker (#94) 2026-06-07 21:38:04 -06:00
test_itd_511_cameras.py v0.10.0: ITD 511 official API adapter (events + advisories + cameras) (#85) 2026-06-03 22:36:26 -06:00
test_migrate.py v0.9.18: reconcile schema_migrations drift + add --check drift detection 2026-05-27 06:40:38 +00:00
test_models.py v0.10.8: discriminate Nats-Msg-Id by event.category to prevent incident+perimeter dedup collision (#96) 2026-06-08 01:12:22 -06:00
test_monitoring_area.py v0.10.9: widen monitoring-area default to cover all of Idaho (49.0N) (#97) 2026-06-08 01:42:59 -06:00
test_n2yo_visualpasses.py v0.12.1: n2yo_visualpasses adapter (server-side visible-pass alerts) 2026-06-09 16:00:55 -06:00
test_navi_backend.py feat(3-K.5): operator-settable EnrichmentConfig (config plumbing) 2026-05-20 18:52:22 +00:00
test_nominatim_backend.py feat(3-K): real geocoder backends + producer-doc reframe + consumer-doc enrichment 2026-05-20 16:10:44 +00:00
test_nwis.py v0.9.20: regional subject routing on quake / fire / hydro / disaster adapters 2026-05-27 23:50:30 -06:00
test_nwis_enrichment.py feat(nwis): site + stats enrichment — named location + WaterWatch normalcy band (v0.8.0) 2026-05-25 15:30:19 +00:00
test_nws_normalization.py v0.10.7: fix NWS SAME state-FIPS parse + 5-digit ANSI county form (#95) 2026-06-08 00:30:13 -06:00
test_photon_backend.py feat(3-K): real geocoder backends + producer-doc reframe + consumer-doc enrichment 2026-05-20 16:10:44 +00:00
test_preview_hook.py fix(2-G.5): preview_for_settings contract in adapter docstring + distinguish [] from None 2026-05-19 17:55:39 +00:00
test_producer_doc.py feat(3-K): real geocoder backends + producer-doc reframe + consumer-doc enrichment 2026-05-20 16:10:44 +00:00
test_region_picker.py feat(gui): generic adapter edit form 2026-05-18 23:16:37 +00:00
test_requires_api_key.py fix(2-A3b): complete error-render path, fix link, add supervisor tests 2026-05-19 02:17:29 +00:00
test_resend.py v0.10.5.2: fix BY_START_TIME feedback loop in Re-send (snapshot last_seq boundary) (#93) 2026-06-06 22:36:04 -06:00
test_sat_common.py v0.13.0: sat_orbits adapter (forward-orbit-track per satellite) + antimeridian splitter 2026-06-09 18:50:47 -06:00
test_sat_orbits.py v0.13.0: sat_orbits adapter (forward-orbit-track per satellite) + antimeridian splitter 2026-06-09 18:50:47 -06:00
test_sat_positions.py v0.12.0: sat_positions adapter (live global satellite positions) + sat_common refactor 2026-06-09 15:23:32 -06:00
test_satpass_predict.py v0.13.0: sat_orbits adapter (forward-orbit-track per satellite) + antimeridian splitter 2026-06-09 18:50:47 -06:00
test_session_auth.py feat(gui): implement first-run setup wizard (1b-8) (#24) 2026-05-17 22:06:22 -06:00
test_setup_gate.py feat(gui): implement first-run setup wizard (1b-8) (#24) 2026-05-17 22:06:22 -06:00
test_stream_registry.py feat(2-E.5): single-source-of-truth stream registry 2026-05-19 07:37:01 +00:00
test_streams.py feat(gui): implement first-run setup wizard (1b-8) (#24) 2026-05-17 22:06:22 -06:00
test_subject_helpers.py v0.9.20: regional subject routing on quake / fire / hydro / disaster adapters 2026-05-27 23:50:30 -06:00
test_supervisor_hotreload.py chore(lint-cleanup): remove 10 pre-existing ruff issues in 4 test files 2026-05-21 18:20:18 +00:00
test_supervisor_integration.py chore(lint-cleanup): remove 10 pre-existing ruff issues in 4 test files 2026-05-21 18:20:18 +00:00
test_supervisor_publish_filter.py v0.10.2: monitoring-area bbox enforced at supervisor publish (was archive-only) (#PR_NUMBER_PLACEHOLDER) 2026-06-05 20:34:10 -06:00
test_swpc.py feat(2-D): add NOAA SWPC space weather adapters (alerts, kindex, protons) 2026-05-19 05:55:29 +00:00
test_telemetry_separation.py v0.13.0: sat_orbits adapter (forward-orbit-track per satellite) + antimeridian splitter 2026-06-09 18:50:47 -06:00
test_tomtom_flow.py v0.10.2: monitoring-area bbox enforced at supervisor publish (was archive-only) (#PR_NUMBER_PLACEHOLDER) 2026-06-05 20:34:10 -06:00
test_tomtom_flow_passthrough.py feat(tomtom_flow): Navi passthrough endpoint /api/traffic/flow (v0.9.4) 2026-05-26 00:04:02 +00:00
test_tomtom_incidents.py fix(gui): generic model_list editor for list-of-model adapters + TomTom bbox validation & quota (v0.9.9) 2026-05-26 05:57:34 +00:00
test_usgs_quake.py v0.9.20: regional subject routing on quake / fire / hydro / disaster adapters 2026-05-27 23:50:30 -06:00
test_wfigs.py v0.10.4: switch wfigs_incidents to non-Current endpoint w/ WF active-only filter (resurrects IMT-managed fires like Blue Ridge) (#89) 2026-06-06 18:10:16 -06:00
test_wizard.py fix(wizard): eliminate all hardcoded field.name branches 2026-05-19 01:01:56 +00:00
test_wzdx.py WZDx: poll-time state allowlist with Idaho-region default (v0.9.17) 2026-05-27 05:57:57 +00:00

Central Tests

Test Database

Some tests (notably test_config_store.py) require a real PostgreSQL database. By default, tests connect to:

postgresql://central_test:testpass@localhost/central_test

If your test database uses different credentials, set the CENTRAL_TEST_DB_DSN environment variable:

export CENTRAL_TEST_DB_DSN="postgresql://myuser:mypass@localhost/mydb"
uv run pytest tests/test_config_store.py