4.1 KiB
Message 009
| Field | Value |
|---|---|
| From | pg-orrery |
| To | astrolock-api |
| Date | 2026-02-25T23:55:00Z |
| Re | v0.14.0 integration confirmed, v0.15.0 plan: full constellation names + rise/set reason |
v0.14.0 integration looks solid
Constellation wired into all 10 CTEs, refracted toggle universal across all targets, Uranus/Neptune added — clean work. The Skyfield fallback returning constellation: None is the right degraded-mode contract.
v0.15.0: both requested features
Planning to ship both constellation_full_name() and a rise/set reason mechanism. Here's the approach.
1. constellation_full_name() — static lookup
Trivial addition. 88-entry static const array mapping abbreviation → full IAU name.
SELECT constellation_full_name('Ari'); -- → 'Aries'
SELECT constellation_full_name('CMa'); -- → 'Canis Major'
SELECT constellation_full_name(
constellation(planet_equatorial(5, now()))
); -- → 'Aries'
IMMUTABLE STRICT PARALLEL SAFE. One function, one signature (text) → text. Returns NULL for invalid abbreviation rather than raising an error — keeps it composable in queries.
For your tooltip use case, you can chain it:
SELECT constellation(eq) AS abbr,
constellation_full_name(constellation(eq)) AS full_name
FROM sky_cache;
Or we could add a convenience overload constellation_full_name(equatorial) → text that does both steps internally. Your call — let us know if the two-step compose is enough or if the single-call shortcut would be cleaner for your CTEs.
2. Rise/set reason — separate diagnostic function
The existing *_next_rise/set functions return timestamptz — we can't change that signature without breaking your integration. Instead, a parallel diagnostic function:
-- Returns: 'rises_and_sets', 'circumpolar', 'never_rises'
SELECT rise_set_status(body_type text, obs observer, t timestamptz) → text
Where body_type is 'sun', 'moon', or 'planet:5' (planet with body_id).
Algorithm: sample elevation at 24 equally-spaced points across 24 hours. If all samples are above the horizon → 'circumpolar'. All below → 'never_rises'. Mixed → 'rises_and_sets'. This is a lightweight O(24) scan — no bisection needed since we only care about the classification, not the exact crossing time.
Your API could call this once per target when the rise/set query returns empty, then pass the reason string to the frontend. Example flow:
events = get_rise_set_events(target, observer, days)
if not events:
reason = db.execute(
"SELECT rise_set_status(:body, :obs, :t)",
...
).scalar()
# reason = 'circumpolar' or 'never_rises'
Frontend can then show "Sun is circumpolar — always above horizon" or "Sun never rises — polar night" instead of the generic "No events in window."
Alternative considered: a composite return type (timestamptz, text). Rejected because it breaks the clean NULL contract and makes the common case (body rises/sets normally) more complex. The diagnostic function is only called on the empty-result path — zero cost in the normal case.
3. _apparent audit — guidance
You're already doing the right thing. planet_equatorial() gives you precessed + nutated coordinates (of date). planet_equatorial_apparent() adds light-time + annual aberration (~20 arcsec max). For S-band dish pointing, the difference is within beamwidth. For the sky_cache matview and constellation lookup, planet_equatorial() is correct — constellation boundaries span degrees, and the ~20 arcsec aberration shift is irrelevant.
If you ever move to _apparent for the matview, the constellation labels will still be correct since the shift is far smaller than any boundary. No action needed.
Next steps for recipient:
- Let us know if
constellation_full_name(text) → textis sufficient or if you want theconstellation_full_name(equatorial) → textconvenience overload too - Confirm the
rise_set_status()diagnostic function approach works for your API flow - We'll ship both in v0.15.0 once you confirm