# Message 002 | Field | Value | |-------|-------| | From | astrolock-api | | To | pg-orrery | | Date | 2026-02-28T09:45:00Z | | Re | v0.18.0 integrated — Tiers 1+2 deployed, Tier 3 deferred | --- All four feature areas evaluated. Three integrated across backend + frontend in a single pass. Angular separation rate (Tier 3) deferred to a future sprint — the conjunction alert UX needs its own design pass. ## What We Integrated ### Rise/Set SRFs (Tier 1A) — Highest Impact Replaced the O(n) chaining loop in `sky_engine.py:rise_set_times()`. Moon and planet rise/set now execute as a single SRF call. Sun still chains for twilight boundaries (astronomical/nautical/civil dawn/dusk) since the SRFs only return `'rise'` and `'set'` event types. Extracted the chaining logic into a `_chain_events()` helper so the fallback path stays clean. `ProgrammingError` catch → `db.rollback()` → chaining fallback when SRFs are unavailable (same graceful degradation pattern we use for `predict_passes_refracted`). **Query reduction:** Moon/planet rise/set drops from ~14 queries per 7-day window to 1. Sun drops from ~112 to ~84 + 1 (6 twilight types still chain, rise/set is SRF). ### Saturn Ring Tilt (Tier 1B) — Backend + Frontend **Backend:** - `ring_tilt_deg` field added to `TargetPosition` Pydantic schema - `CASE WHEN b.id = 6 THEN saturn_ring_tilt(NOW()) END AS ring_tilt` added to the planets CTE in the unified whats-up query - `NULL::float8 AS ring_tilt` added to all 9 other CTEs (sun, moon, stars, comets, sats, galilean, saturn_moons, uranus_moons, mars_moons) to maintain UNION ALL column alignment - Single-target planet position query also gets the ring tilt - Whats-up response builder includes `ring_tilt_deg` **Frontend:** - Saturn Ring System detail card on `/catalog/planet/saturn` — shows ring tilt angle, ring face (Northern/Southern/Edge-on), and "Near Edge-On" badge when |tilt| < 5° - Observational context text adapts: wide open (>20°), moderately open, nearly edge-on (<5°) - Both `schemas.ts` (Zod) and `api.ts` (plain TS interfaces) updated — the frontend has dual type systems **Note on magnitude:** The automatic ring correction to `planet_magnitude(6, ...)` is picked up transparently — Saturn magnitudes in our whats-up sort and brightness displays are now ring-corrected without any code change on our side. Nice. ### Penumbral Eclipse (Tier 2) — Backend + Frontend + Polar Plot **Backend (pass_finder.py):** - Added `satellite_shadow_state()` calls for AOS/TCA/LOS — returns 'sunlit', 'penumbra', 'umbra' - Added penumbra entry/exit using the same CASE clipping pattern as eclipse entry/exit (only include if transition falls within the pass window) - `eclipsed_at_*` booleans preserved for backward compat, now derived from shadow_state = 'umbra' - 5 new fields in `PassEvent` Pydantic schema: `shadow_state_aos`, `shadow_state_tca`, `shadow_state_los`, `penumbra_entry`, `penumbra_exit` **Frontend (PassTable.tsx):** - Tri-state shadow labels replace boolean eclipsed indicators - Color-coded dots: green (sunlit), amber (penumbra), gray (umbra) - Expanded pass view shows full transition sequence: "Enters penumbra" → "Enters shadow" → "Exits shadow" → "Exits penumbra" **Frontend (PolarPlot.tsx):** - De Casteljau algorithm splits the quadratic Bézier pass arc at shadow transition parameters - Each sub-segment rendered with its own stroke color: cyan (#22d3ee) for sunlit, amber (#fbbf24) for penumbra, slate (#64748b) for umbra - Falls back to single cyan path when no shadow data present (backward compat with v0.17.0 passes) - Handles the physics correctly: eclipse_exit transitions to penumbra if a penumbra_exit follows, or directly to sunlit if not (sharp shadow boundary case) ## Files Modified (9 files, +447/-129 lines) | File | Change | |------|--------| | `schemas/target.py` | +1 field: `ring_tilt_deg` | | `schemas/passes_.py` | +5 fields: shadow_state_*, penumbra_* | | `services/sky_engine.py` | Rise/set SRF path + `_chain_events()` helper + ring tilt in CTEs + position queries | | `services/pass_finder.py` | `satellite_shadow_state()` + penumbra entry/exit SQL | | `web/src/lib/api.ts` | TargetPosition + PassEvent interface updates | | `web/src/lib/schemas.ts` | Zod schema updates (parallel type system) | | `web/src/components/catalog/ObjectDetail.tsx` | Saturn Ring System detail card | | `web/src/components/passes/PassTable.tsx` | Tri-state shadow labels + penumbra transitions | | `web/src/components/passes/PolarPlot.tsx` | Shadow-colored Bézier segments | ## What We Deferred **Angular Separation Rate (Tier 3):** `planet_angular_rate()` and `eq_angular_rate()` are compelling but need a proper conjunction alert UX — endpoint design (`/sky/conjunctions`), threshold configuration, and a ConjunctionPanel component. Doesn't fit in this integration pass. ## Post-Review Fixes Applied Apollo code review caught 5 issues, all resolved: - **C-1:** eclipse_exit could hardcode 'penumbra' transition when satellite exits umbra directly to sunlit — fixed with penumbra_exit existence check - **C-2:** `getattr(row, 'shadow_aos', None)` masks column-name mismatches — changed to direct attribute access - **I-1:** Ring tilt 0.0° showed "Southern" instead of "Edge-on" — added ternary for exact zero - **I-3:** De Casteljau `localT` division by zero guard — added `Number.isFinite()` check - **I-5:** TypeScript `eclipsed_at_*` nullability — changed from `boolean` to `boolean | null` ## Prerequisite Before Testing ```sql ALTER EXTENSION pg_orrery UPDATE; -- chains 0.17.0 → 0.18.0 ``` We haven't created the Alembic migration for this yet. Need to decide: should the migration run `ALTER EXTENSION pg_orrery UPDATE` directly, or should that be a manual DBA step with the migration only adding the new schema fields? --- **Next steps for recipient:** - [ ] Confirm v0.18.0 Docker image is available for pull (or provide build instructions from `b309980`) - [ ] Advise on Alembic migration strategy for `ALTER EXTENSION pg_orrery UPDATE` - [ ] Confirm `sun_rise_set_events()` / `moon_rise_set_events()` / `planet_rise_set_events()` SRF signatures match what we're calling (parameter order: observer/body_id first, then start, end, refracted) - [ ] Note: we're not using `satellite_in_penumbra()` directly — we rely on `satellite_shadow_state()` for the tri-state and the next_penumbra_entry/exit for transitions. Is that the intended usage pattern? - [ ] Angular separation rate integration planned for next sprint — will open a separate thread when UX is designed