update test_last_full_moon.py to produce the full moon image
Now runs the complete production pipeline without east-frame verification: find last full moon → fetch NASA Dial-a-Moon render → render_phase_closeup → test_last_full_moon_out.jpg with caption (phase, % lit, local time, NASA attribution). Also retains the sun_events_in_range() call as a regression check for the sunrise_sunset() bare-topos fix. https://claude.ai/code/session_01JieSeNQZ3X6fhsb11YyJrK
This commit is contained in:
+54
-19
@@ -1,10 +1,15 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""test_last_full_moon.py — smoke-test moon_phase.py against the most recent full moon.
|
"""test_last_full_moon.py — produce a full-moon close-up for the most recent full moon.
|
||||||
|
|
||||||
Exercises:
|
Runs the same NASA Dial-a-Moon fetch + render_phase_closeup pipeline that
|
||||||
- full_moons_in_range() — phase detection
|
moon-phase-monthly.sh uses, but bypasses the east-frame witness step so it
|
||||||
- sun_events_in_range() — sunrise/sunset almanac (requires bare topos, not earth+topos)
|
works any time without needing captured frames.
|
||||||
- illumination() / altaz() / parallactic_angle()
|
|
||||||
|
Also exercises sun_events_in_range() (the sunrise/sunset almanac call that
|
||||||
|
requires a bare topos, not an earth+topos observer) as a regression check
|
||||||
|
for that fix.
|
||||||
|
|
||||||
|
Output: test_last_full_moon_out.jpg in the sky-cam directory.
|
||||||
|
|
||||||
Run from the sky-cam directory:
|
Run from the sky-cam directory:
|
||||||
|
|
||||||
@@ -18,45 +23,75 @@ from datetime import datetime, timedelta, timezone
|
|||||||
HERE = pathlib.Path(__file__).resolve().parent
|
HERE = pathlib.Path(__file__).resolve().parent
|
||||||
sys.path.insert(0, str(HERE))
|
sys.path.insert(0, str(HERE))
|
||||||
|
|
||||||
import moon_phase as mp
|
OUT_PATH = HERE / 'test_last_full_moon_out.jpg'
|
||||||
|
|
||||||
|
|
||||||
def main() -> int:
|
def main() -> int:
|
||||||
|
import moon_phase as mp
|
||||||
|
import moon_dialamoon
|
||||||
|
from moon_composite import render_phase_closeup
|
||||||
|
|
||||||
now = datetime.now(timezone.utc)
|
now = datetime.now(timezone.utc)
|
||||||
|
|
||||||
# Last full moon: search back up to 35 days
|
# ── Find the most recent full moon ────────────────────────────────────────
|
||||||
candidates = mp.full_moons_in_range(now - timedelta(days=35), now)
|
candidates = mp.full_moons_in_range(now - timedelta(days=35), now)
|
||||||
if not candidates:
|
if not candidates:
|
||||||
print("ERROR: no full moon found in the last 35 days", file=sys.stderr)
|
print("ERROR: no full moon found in the last 35 days", file=sys.stderr)
|
||||||
return 1
|
return 1
|
||||||
fm = candidates[-1]
|
fm = candidates[-1]
|
||||||
|
|
||||||
print(f"last full moon (UTC) : {fm.strftime('%Y-%m-%dT%H:%M:%SZ')}")
|
|
||||||
print(f"local : {mp._format_local(fm)}")
|
|
||||||
|
|
||||||
# Moon stats at exact full moon moment
|
|
||||||
illum = mp.illumination(fm)
|
illum = mp.illumination(fm)
|
||||||
|
illum_pct = round(illum * 100)
|
||||||
pa = mp.phase_angle(fm)
|
pa = mp.phase_angle(fm)
|
||||||
alt, az = mp.altaz(fm)
|
alt, az = mp.altaz(fm)
|
||||||
para = mp.parallactic_angle(fm)
|
para = mp.parallactic_angle(fm)
|
||||||
print(f"illumination : {illum:.4f} ({illum*100:.1f}%)")
|
local_str = mp._format_local(fm)
|
||||||
print(f"phase angle : {pa:.2f}° (0° = full)")
|
|
||||||
print(f"altitude / azimuth : {alt:.2f}° / {az:.2f}°")
|
print(f"last full moon (UTC) : {fm.strftime('%Y-%m-%dT%H:%M:%SZ')}")
|
||||||
print(f"parallactic angle : {para:.2f}°")
|
print(f"local : {local_str}")
|
||||||
|
print(f"illumination : {illum:.4f} ({illum_pct}%)")
|
||||||
|
print(f"phase angle : {pa:.2f}° (0° = full)")
|
||||||
|
print(f"altitude / azimuth : {alt:.2f}° / {az:.2f}°")
|
||||||
|
print(f"parallactic angle : {para:.2f}°")
|
||||||
print()
|
print()
|
||||||
|
|
||||||
# Sunrise/sunset for the 24-hour window around the full moon — exercises the fix
|
# ── Sunrise/sunset for that day (regression check for topos fix) ──────────
|
||||||
day_start = fm.replace(hour=0, minute=0, second=0, microsecond=0)
|
day_start = fm.replace(hour=0, minute=0, second=0, microsecond=0)
|
||||||
day_end = day_start + timedelta(days=1)
|
day_end = day_start + timedelta(days=1)
|
||||||
events = mp.sun_events_in_range(day_start, day_end)
|
events = mp.sun_events_in_range(day_start, day_end)
|
||||||
if events:
|
if events:
|
||||||
print(f"sun events on {day_start.strftime('%Y-%m-%d')} (UTC):")
|
print(f"sun events on {day_start.strftime('%Y-%m-%d')} UTC:")
|
||||||
for t, is_rise in events:
|
for t, is_rise in events:
|
||||||
label = "sunrise" if is_rise else "sunset "
|
label = "sunrise" if is_rise else "sunset "
|
||||||
print(f" {label} {t.strftime('%H:%M:%S')} UTC ({mp._format_local(t)})")
|
print(f" {label} {t.strftime('%H:%M:%S')} UTC ({mp._format_local(t)})")
|
||||||
else:
|
else:
|
||||||
print(f"no sun events found on {day_start.strftime('%Y-%m-%d')} (polar location?)")
|
print(f"no sun events on {day_start.strftime('%Y-%m-%d')} (polar location?)")
|
||||||
|
print()
|
||||||
|
|
||||||
|
# ── Fetch NASA Dial-a-Moon render for that hour ───────────────────────────
|
||||||
|
print(f"fetching NASA Dial-a-Moon for {fm.strftime('%Y-%m-%dT%HZ')} ...")
|
||||||
|
try:
|
||||||
|
nasa_path = moon_dialamoon.fetch_for_time(fm)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"ERROR: dial-a-moon fetch failed: {e}", file=sys.stderr)
|
||||||
|
return 2
|
||||||
|
print(f"dial-a-moon cache : {nasa_path}")
|
||||||
|
print()
|
||||||
|
|
||||||
|
# ── Render full-screen composite ──────────────────────────────────────────
|
||||||
|
caption = (
|
||||||
|
f"Full Moon — {illum_pct}% lit — {fm.strftime('%B %Y')} — "
|
||||||
|
f"{local_str} — NASA SVS Dial-a-Moon"
|
||||||
|
)
|
||||||
|
render_phase_closeup(
|
||||||
|
str(nasa_path),
|
||||||
|
str(OUT_PATH),
|
||||||
|
output_size=(1920, 1080),
|
||||||
|
moon_height_pct=0.92,
|
||||||
|
caption=caption,
|
||||||
|
when_utc=fm,
|
||||||
|
)
|
||||||
|
print(f"wrote {OUT_PATH}")
|
||||||
print()
|
print()
|
||||||
print("OK")
|
print("OK")
|
||||||
return 0
|
return 0
|
||||||
|
|||||||
Reference in New Issue
Block a user