v0.12.1: n2yo_visualpasses adapter (server-side visible-pass alerts)

Adds a new SourceAdapter that polls n2yo's visualpasses endpoint for
server-side visible-pass data: sun illumination + visual magnitude, both
data points that local SGP4 propagation from raw TLEs (v0.11.1
satpass_predict) cannot compute. Complement, not replacement.

Subject collision with satpass_predict on
central.sat.pass.us.<state>.<observer_slug> is intentional. Vendor
disambiguation lives in data.category (pass.n2yo_visualpasses vs
pass.satpass_predict); v0.10.8 category-discriminated Nats-Msg-Id keeps
the JetStream dedup windows distinct. CONSUMER-INTEGRATION.md flags
this explicitly for future consumers.

Severity bucketed by visual magnitude (LOWER mag = BRIGHTER): <=-3 -> 4
(very bright), -3..-1 -> 3 (naked-eye easy), -1..2 -> 2 (binoculars),
>2 -> 1 (telescope-grade).

Settings ship the curated default 6 observers (Filer primary +
Boise/Idaho Falls/Ogden/SLC/Provo) x 6 sats (ISS, NOAA-15/18/19, SO-50,
AO-91), sized for 6*6*24 = 864 transactions/day under n2yo's free
1000/day cap. data_class=event (alerts on /events, like satpass_predict).

API key plumbing via the established tomtom_flow pattern:
requires_api_key='n2yo', api_key_field='api_key_alias' settings field
binds the GUI api_key_select dropdown. _api_key cached via
ConfigStore.get_api_key, refreshed in apply_config; missing-key path
logs INFO and yields zero events (graceful, no exception). Live key
scrubbed from log strings via _redact.

No new stream (CENTRAL_SAT pre-existing; 'pass' token already in
STREAM_CATEGORY_DOMAINS from v0.11.1+v0.12.0). No new dep
(aiohttp already in venv). Migration 040 seeds config.adapters row
only; the n2yo API key gets added via GUI /api-keys page before
enabling.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
malice 2026-06-09 15:52:47 -06:00
commit 0bf82b03a4
8 changed files with 848 additions and 1 deletions

View file

@ -0,0 +1,45 @@
-- Migration 040: register n2yo_visualpasses adapter (v0.12.1)
--
-- Server-side complement to v0.11.1 satpass_predict. n2yo's visualpasses
-- endpoint adds sun illumination + visual magnitude that SGP4-from-TLE
-- alone cannot compute. Subject collision with satpass_predict on
-- central.sat.pass.us.<state>.<observer_slug> is intentional; consumers
-- disambiguate via data.category (pass.n2yo_visualpasses vs
-- pass.satpass_predict). v0.10.8 category-discriminated Nats-Msg-Id keeps
-- the JetStream dedup windows distinct.
--
-- No stream changes: CENTRAL_SAT already routes via the "pass" token in
-- STREAM_CATEGORY_DOMAINS["CENTRAL_SAT"] = ("tle", "pass", "position").
--
-- No api_keys seeding: Matt adds the "n2yo" alias via the GUI /api-keys
-- page (Add -> alias "n2yo" -> paste key) before enabling the adapter.
-- Missing-key behavior is graceful (log INFO + zero-yield, no exception),
-- so the row can land in config.adapters before the key does without
-- breaking anything.
--
-- Ships disabled. Default 6 observers x 6 sats x 24 polls/day = 864
-- transactions/day, under n2yo's free 1000/day quota cap.
--
-- Idempotent: ON CONFLICT (name) DO NOTHING preserves operator-tuned state.
INSERT INTO config.adapters (name, enabled, cadence_s, settings)
VALUES (
'n2yo_visualpasses',
false,
3600,
'{
"observers": [
{"name": "Filer", "slug": "filer", "state": "ID", "lat": 42.57, "lon": -114.60, "elev_m": 1200},
{"name": "Boise", "slug": "boise", "state": "ID", "lat": 43.62, "lon": -116.20, "elev_m": 825},
{"name": "Idaho Falls", "slug": "idaho-falls", "state": "ID", "lat": 43.49, "lon": -112.04, "elev_m": 1438},
{"name": "Ogden", "slug": "ogden", "state": "UT", "lat": 41.22, "lon": -111.97, "elev_m": 1330},
{"name": "Salt Lake City", "slug": "salt-lake-city", "state": "UT", "lat": 40.76, "lon": -111.89, "elev_m": 1290},
{"name": "Provo", "slug": "provo", "state": "UT", "lat": 40.23, "lon": -111.66, "elev_m": 1387}
],
"norad_ids": [25544, 25338, 28654, 33591, 27607, 43017],
"days_ahead": 2,
"min_visibility_seconds": 300,
"api_key_alias": "n2yo"
}'::jsonb
)
ON CONFLICT (name) DO NOTHING;