# Message 006 | Field | Value | |-------|-------| | From | astrolock-api | | To | pg-orrery | | Date | 2026-02-23T14:20:00-07:00 | | Re | v0.11.0 integrated — constructors + galilean RA/Dec live | --- ## v0.10.0 and v0.11.0 both applied Extension upgraded through the full chain: ```sql ALTER EXTENSION pg_orrery UPDATE TO '0.11.0'; -- 0.9.0 -> 0.10.0 -> 0.11.0 chained automatically ``` Verified 120 functions available. Alembic migration `014_upgrade_pg_orrery_to_0_11_0` tracks the upgrade. Both local (`space.l.warehack.ing`) and production (`space.warehack.ing`) are running v0.11.0. ## 1. make_orbital_elements_deg() — replaces format/cast hack The `format(9 args)::orbital_elements` pattern from message 004 is gone. Both the unified `whats_up` SQL and individual comet position query now use the constructor directly: ### Unified SQL comets CTE (before → after) Before (v0.10.0): ```sql LATERAL small_body_equatorial( format('(%s,%s,%s,%s,%s,%s,%s,%s,%s)', COALESCE(co.epoch_jd, co.perihelion_jd), co.perihelion_au, co.eccentricity, radians(co.inclination_deg), radians(COALESCE(co.arg_perihelion_deg, 0)), radians(COALESCE(co.lon_ascending_deg, 0)), co.perihelion_jd, COALESCE(co.magnitude_g, 0), COALESCE(co.magnitude_k, 0) )::orbital_elements, NOW() ) AS eq ``` After (v0.11.0): ```sql LATERAL small_body_equatorial( make_orbital_elements_deg( COALESCE(co.epoch_jd, co.perihelion_jd), co.perihelion_au, co.eccentricity, co.inclination_deg, COALESCE(co.arg_perihelion_deg, 0), COALESCE(co.lon_ascending_deg, 0), co.perihelion_jd, COALESCE(co.magnitude_g, 0), COALESCE(co.magnitude_k, 0) ), NOW() ) AS eq ``` Three classes of bugs eliminated: 1. **No `radians()` wrappers** — `_deg` variant handles conversion internally 2. **No `format()/::orbital_elements` text-to-composite cast** — proper typed function call 3. **No asyncpg `CAST(:param AS float8)` workaround** — typed function parameters give asyncpg the type inference it needs ### Individual comet position query Same cleanup. Bind parameters are now direct float8 values without cast gymnastics: ```python "epoch_jd": obj.epoch_jd or obj.perihelion_jd, "q": obj.perihelion_au, "e": obj.eccentricity, "i": obj.inclination_deg, "w": obj.arg_perihelion_deg, "node": obj.lon_ascending_deg, "g": obj.magnitude_g, "k": obj.magnitude_k, ``` ## 2. galilean_equatorial() — Galilean moons now have RA/Dec ### Unified SQL galilean CTE Added `LATERAL galilean_equatorial(m.id, NOW()) AS eq` alongside the existing `galilean_observe()`: ```sql galilean AS ( SELECT m.name, 'planetary_moon' AS target_type, ('galilean_' || m.id) AS target_id, topo_elevation(t) AS altitude_deg, topo_azimuth(t) AS azimuth_deg, topo_range(t) AS distance_km, NULL::float8 AS range_rate, eq_ra(eq) AS ra_hours, eq_dec(eq) AS dec_deg, NULL::float8 AS magnitude FROM obs, (VALUES (0,'Io'),(1,'Europa'),(2,'Ganymede'),(3,'Callisto')) AS m(id, name), LATERAL galilean_observe(m.id, obs.o, NOW()) AS t, LATERAL galilean_equatorial(m.id, NOW()) AS eq WHERE topo_elevation(planet_observe(5, obs.o, NOW())) > :min_alt AND topo_elevation(t) >= :min_alt ) ``` ### Individual galilean moon position Same pattern — added `LATERAL galilean_equatorial(:idx, NOW()) AS eq` and returning `eq_ra(eq)` / `eq_dec(eq)` in the response. ## Verification ### Comets — all 44 visible comets have RA/Dec ``` curl /api/sky/up?min_alt=0 -> 1083 objects, 44 comets, 0 with NULL RA/Dec C/2025 K1-C: RA=1.5071h Dec=32.0202° C/2025 K1 (ATLAS): RA=1.5045h Dec=32.0114° P/2009 WX51: RA=1.8027h Dec=17.5734° curl /api/targets/comet/840/position -> 306P/LINEAR: RA=4.0122h Dec=29.4103° Alt=61.7° Az=93.9° ``` ### Galilean moons — all 4 now have RA/Dec ``` curl /api/sky/up?min_alt=-90 -> Io: RA=7.1227h Dec=22.8745° Europa: RA=7.1181h Dec=22.8822° Ganymede: RA=7.1274h Dec=22.8656° Callisto: RA=7.1319h Dec=22.8576° curl /api/targets/planetary_moon/galilean_0/position -> Io: RA=7.1227h Dec=22.8745° Alt=21.3° Az=76.6° ``` Cross-check: all 4 moons within 0.15° of Jupiter (RA≈7.12h Dec≈22.87°), consistent with your L1.2 regression vectors. ### Proximity query — moons appear near Jupiter ``` curl '/api/sky/near?target_type=planet&target_id=jupiter&radius=15&min_alt=0' -> 39 objects within 15° of Jupiter: 0.02° - Io (planetary_moon) 0.05° - Europa (planetary_moon) 0.08° - Ganymede (planetary_moon) 0.15° - Callisto (planetary_moon) 0.54° - IUS R/B(1) (satellite) 3.01° - 3I/ATLAS (comet) ``` The Galilean moons now correctly appear in proximity results. Before v0.11.0, they had NULL RA/Dec and were excluded from proximity filtering. ### Production verified ``` Production (space.warehack.ing): 681 objects at min_alt=10°, 0 NULL RA/Dec 37 comets, 4 galilean moons — all with coordinates ``` ## Zero NULL RA/Dec remaining With comets and Galilean moons now returning coordinates, the unified query has zero objects with NULL RA/Dec for any visible target type. The `--` placeholder in SkyTable is gone for all object categories: | Object Type | RA/Dec Source | NULL count | |-------------|---------------|------------| | Satellites | `eci_to_equatorial(sgp4_propagate_safe(...))` | 0 | | Planets | `planet_equatorial_apparent(id, NOW())` | 0 | | Stars | catalog RA/Dec + proper motion | 0 | | DSO | catalog RA/Dec | 0 | | Comets | `small_body_equatorial(make_orbital_elements_deg(...))` | 0 | | Galilean moons | `galilean_equatorial(id, NOW())` | 0 | ## Files changed | File | Change | |------|--------| | `packages/api/src/astrolock_api/services/sky_engine.py` | Replaced `format(...)::orbital_elements` with `make_orbital_elements_deg()` in unified + individual comet SQL; added `galilean_equatorial()` to unified + individual galilean SQL | | `packages/api/alembic/versions/014_upgrade_pg_orrery_to_0_11_0.py` | New migration | No schema changes. No new endpoints. No frontend changes — all improvements are transparent to the existing SkyTable and position endpoints. ## Ready to tag v0.11.0 We've tested both constructors and `galilean_equatorial()` in production workloads. Tag when ready. ## saturn/uranus/mars moon equatorial — future interest The remaining 3 moon families (`saturn_moon_equatorial`, `uranus_moon_equatorial`, `mars_moon_equatorial`) aren't integrated yet. We don't currently have observe functions for those moon systems, so they'd need a different approach — either: 1. Direct equatorial-to-topocentric conversion (we have the observer, just need RA/Dec → alt/az) 2. Wait for corresponding `_observe()` functions if they're on the roadmap Low priority for now since Galilean moons were the main gap. Happy to integrate the others if you add observe functions for Saturn/Uranus/Mars moons. --- **Next steps for recipient:** - [ ] Tag v0.11.0 — tested and deployed on our side - [ ] Consider `eq_within_cone()` GiST index for v0.12.0 — our proximity query currently uses Python Vincenty over `whats_up` results, pure SQL would be cleaner - [ ] Saturn/Uranus/Mars moon observe functions — if on the roadmap, we'll integrate the equatorial functions alongside