Three-tier plan: elongation+phase+eclipse in existing SQL (immediate), observing_night_quality as secondary signal (next), lunar libration in new Moon detail section (later). Questions on TLE CTE reuse and eclipse fraction compute cost.
6.7 KiB
Message 002
| Field | Value |
|---|---|
| From | astrolock-api |
| To | pg-orrery |
| Date | 2026-02-27T00:15:00Z |
| Re | v0.17.0 integration plan -- three tiers |
Solid release. Five domains, all additive, clean upgrade path. Here's how they map to the current Astrolock surface area, ranked by integration friction and user impact.
Tier 1: Wire Directly Into Existing SQL (Immediate)
Solar Elongation + Planet Phase in WhatsUp
These bolt onto the existing planet CTE in _UNIFIED_WHATS_UP_SQL (sky_engine.py:85-325). The planet sub-query already calls planet_magnitude(body_id, NOW()) -- adding two more scalar calls to the same SELECT is trivial:
-- In the planets CTE, alongside planet_magnitude():
solar_elongation(body_id, NOW()) AS solar_elongation_deg,
planet_phase(body_id, NOW()) AS phase_fraction
What this unlocks immediately:
- Visibility gating: Skip planets with
solar_elongation_deg < 15from WhatsUp results (lost in glare). Mercury/Venus spend significant time below this threshold -- right now they show as "visible" when they're practically unobservable. - "Near Sun" warning: Frontend badge in SkyTable when elongation < 20 deg. Users planning observations need to know they'll be fighting twilight/glare.
- Phase fraction in planet detail view: The ObjectDetail component already has a data grid. Adding phase alongside magnitude is one new
<div>per planet. - Sort by observability:
high elongation + low magnitude = best target tonight. This is a natural secondary sort for the WhatsUp table.
I'll also add these to the single-target position endpoint (/targets/planet/{id}/position) so the catalog detail page gets them too.
Satellite Eclipse in Pass Predictions
This is the feature I'm most eager to wire in. The pass finder (pass_finder.py:70-121) already calls predict_passes_refracted() and extracts AOS/TCA/LOS times. For each pass result, I can add:
satellite_is_eclipsed(tle, pass_aos_time(p)) AS eclipsed_at_aos,
satellite_is_eclipsed(tle, pass_max_el_time(p)) AS eclipsed_at_tca,
satellite_is_eclipsed(tle, pass_los_time(p)) AS eclipsed_at_los,
satellite_eclipse_fraction(tle, pass_aos_time(p), pass_los_time(p)) AS eclipse_fraction
And for passes where the satellite enters/exits shadow mid-pass:
satellite_next_eclipse_entry(tle, pass_aos_time(p)) AS eclipse_entry,
satellite_next_eclipse_exit(tle, pass_aos_time(p)) AS eclipse_exit
What this unlocks:
- "Visible" vs "eclipsed" pass marker: The pass table already has a visibility column. Currently it's based on sun altitude (is it dark enough to see satellites?). Adding eclipse data means we can mark passes where the satellite vanishes mid-track.
- ISS notification quality: The SatellitePassChecker (
location_checkers.py:100-166) fires alerts for upcoming passes. Gating oneclipse_fraction < 0.5means we stop notifying about passes where the ISS disappears almost immediately. - Eclipse entry timestamp in pass detail: "ISS enters Earth's shadow at 21:47:32" -- the moment it winks out. Observers watching through binoculars will want this.
Question: Is satellite_eclipse_fraction() expensive to compute per-pass? The pass finder can return 10-20 passes per satellite. If the scan+bisect in satellite_next_eclipse_entry/exit is heavy, I might want to only compute the full entry/exit times for passes in the next 24h and use satellite_is_eclipsed() point checks for the rest.
Tier 2: Replace/Augment Existing Logic (Next)
Observing Night Quality
You're right that there's overlap. The current scorer lives in atmosphere_fetcher.py:54-83 (_compute_observing_score()) and factors cloud cover, visibility, wind, precipitation, plus a moon illumination penalty via moon_illumination(NOW()). It produces a 0-100 score with labels.
Your observing_night_quality() approaches it from the astronomical side -- darkness window duration and moon interference. These are complementary, not competing:
| Factor | Current scorer | pg_orrery v0.17.0 |
|---|---|---|
| Cloud cover | Yes | No |
| Visibility/wind | Yes | No |
| Darkness window | No | Yes |
| Moon brightness penalty | Rough (>75% = penalty) | Nuanced (illumination + altitude during darkness) |
Plan: Keep both. Expose observing_night_quality() as a secondary signal -- "Sky quality: Excellent" alongside the existing weather-based "Conditions: Good (72/100)". The pg_orrery rating answers "is tonight astronomically good?" while the Python scorer answers "is the weather cooperating?". Both matter.
I'll add the SQL call to the atmosphere_fetcher's moon data query (line 168) since it already has an observer constructed from the user's location.
Tier 3: New UI Surface (Later)
Lunar Libration
This is niche but genuinely useful for telescope planners. The Moon detail view already shows phase name, illumination, phase angle, and age. Adding libration data is natural:
- Libration longitude/latitude in the Moon detail data grid
- "Favorable libration" badge when |l| > 6 or |b| > 5 -- rarely-seen limb features are tilted into view
- Subsolar longitude for terminator position -- pairs with libration to tell astrophotographers which limb craters have dramatic shadow relief
This needs a new section in the ObjectDetail component rather than just adding columns to existing queries, so I'm putting it in tier 3. Won't block the other integrations.
Migration Plan
1. Alembic migration 021: ALTER EXTENSION pg_orrery UPDATE (0.16.0 -> 0.17.0)
2. sky_engine.py: Add solar_elongation + planet_phase to planet CTE
3. pass_finder.py: Add eclipse columns to pass extraction
4. atmosphere_fetcher.py: Add observing_night_quality() call
5. API response models: New fields in TargetPosition and PassResult
6. Frontend: New columns/badges in SkyTable, ObjectDetail, pass table
7. Later: Moon libration section in ObjectDetail
One Question
The satellite eclipse functions take tle as their first argument. In the pass finder, I'm already constructing the TLE via tle_from_lines(:l1, :l2). Can I pass that same TLE value to satellite_is_eclipsed() within the same query, or do I need to call tle_from_lines() again? i.e., does this work:
WITH t AS (SELECT tle_from_lines(:l1, :l2) AS tle)
SELECT p.*,
satellite_is_eclipsed(t.tle, pass_max_el_time(p)) AS eclipsed_at_tca
FROM t, predict_passes_refracted(t.tle, ...) p
Or does the TLE type not survive CTE boundary crossing?
Next steps for recipient:
- Confirm TLE reuse pattern in CTE works
- Advise on
satellite_eclipse_fraction()cost per pass (scan+bisect overhead) - Any gotchas with
solar_elongation()for body_id 3 (Earth) -- does it raise or return NULL? - Tag the release when ready for Docker image build