Put meteor showers, full moons and aurora on the calendar (#20) #127

Merged
claude-bot merged 1 commit from feat/20-astronomical into main 2026-09-04 23:55:58 +00:00
Contributor

Closes #20. Eclipses are split to #126.

Seven meteor showers and eleven named full moons become resolvable events, each with its own built-in scheme, plus an optional aurora layer.

Full moons are computed, not tabulated

They come from astral, which is already a dependency for sunset. So unlike the solstices they add nothing to the lookup-table horizon that #61 warns about — they keep working indefinitely rather than falling off the end of a table in 2035.

The tests assert astronomy rather than dates: that each returned day really is full (astral.moon.phase() within tolerance of 14), that consecutive gaps are whole synodic months, and that the answer moves year to year. A function that returned a fixed date would pass a weaker test, and this code is exactly the kind that looks right while being wrong.

One test premise of mine was wrong and is worth recording. I first asserted consecutive named moons are 28–31 days apart. It failed: May 2026 has two full moons (the 1st and the 31st), and picking one per calendar month necessarily skips one, so that gap is two synodic months. The test now asserts whole synodic multiples and documents the blue-moon case.

Meteor showers are fixed spans, not a table

Earth crosses the same debris stream at the same point in its orbit each year, so the peak moves by at most a day — a table would be maintenance for no accuracy. And the peak is a few hours inside several active nights, so a single-day event would usually miss it. Each shower is a multi-day span.

Schemes are explicit per event

A pre-existing test (test_bundled_events_have_explicit_theme_proposals) enforces that convention, and my initial tag-inherited entries defeated it. That was the right catch: the colours now differ by the character of each shower and moon rather than being eleven copies. The Harvest Moon is orange because the low autumn moon actually looks that way; the Geminids are colder and faster than the Perseids.

Aurora

AURORA_ENABLED, off by default, following the Calendarific pattern — a self-hosted install with no interest in this makes no outbound requests at all. Polls NOAA's planetary K-index and shows an aurora scheme above AURORA_MIN_KP, restoring the scheduled scheme when it subsides.

Edge-triggered: re-pushing an identical state every fifteen minutes would restart the effect each time and read as a stutter on the strip rather than a continuous aurora. The falling edge hands the night back to _push_today_scheme() rather than to a remembered state that may since have gone stale.

No visibility model, deliberately. Visibility depends on geomagnetic latitude as well as Kp, and that mapping is a rule of thumb rather than a formula. Rather than ship a bad model, the threshold is the knob — low in Alaska, high enough in Texas that it never fires — and it's documented in those terms.

Why eclipses are split out

They need an ephemeris and a per-observer visibility calculation. A lunar eclipse over the Indian Ocean means nothing to a roofline in Texas, and solar totality varies over a few hundred kilometres — so a worldwide eclipse event would be wrong most of the time. That's a heavier dependency than the image should carry after #60's hardening, and it deserved its own decision rather than a guess inside this one. Tracked in #126.

Verification

ruff check clean, mypy app clean across 69 source files, pytest 610 passed.

🤖 Generated with Claude Code

Closes #20. Eclipses are split to #126. Seven meteor showers and eleven named full moons become resolvable events, each with its own built-in scheme, plus an optional aurora layer. ## Full moons are computed, not tabulated They come from `astral`, which is already a dependency for sunset. So unlike the solstices they add **nothing to the lookup-table horizon** that #61 warns about — they keep working indefinitely rather than falling off the end of a table in 2035. The tests assert astronomy rather than dates: that each returned day really is full (`astral.moon.phase()` within tolerance of 14), that consecutive gaps are whole synodic months, and that the answer moves year to year. A function that returned a fixed date would pass a weaker test, and this code is exactly the kind that looks right while being wrong. **One test premise of mine was wrong and is worth recording.** I first asserted consecutive named moons are 28–31 days apart. It failed: May 2026 has two full moons (the 1st and the 31st), and picking one per calendar month necessarily skips one, so that gap is *two* synodic months. The test now asserts whole synodic multiples and documents the blue-moon case. ## Meteor showers are fixed spans, not a table Earth crosses the same debris stream at the same point in its orbit each year, so the peak moves by at most a day — a table would be maintenance for no accuracy. And the peak is a few *hours* inside several active nights, so a single-day event would usually miss it. Each shower is a multi-day span. ## Schemes are explicit per event A pre-existing test (`test_bundled_events_have_explicit_theme_proposals`) enforces that convention, and my initial tag-inherited entries defeated it. That was the right catch: the colours now differ by the character of each shower and moon rather than being eleven copies. The Harvest Moon is orange because the low autumn moon actually looks that way; the Geminids are colder and faster than the Perseids. ## Aurora `AURORA_ENABLED`, **off by default**, following the Calendarific pattern — a self-hosted install with no interest in this makes no outbound requests at all. Polls NOAA's planetary K-index and shows an aurora scheme above `AURORA_MIN_KP`, restoring the scheduled scheme when it subsides. Edge-triggered: re-pushing an identical state every fifteen minutes would restart the effect each time and read as a stutter on the strip rather than a continuous aurora. The falling edge hands the night back to `_push_today_scheme()` rather than to a remembered state that may since have gone stale. **No visibility model, deliberately.** Visibility depends on geomagnetic latitude as well as Kp, and that mapping is a rule of thumb rather than a formula. Rather than ship a bad model, the threshold is the knob — low in Alaska, high enough in Texas that it never fires — and it's documented in those terms. ## Why eclipses are split out They need an ephemeris *and* a per-observer visibility calculation. A lunar eclipse over the Indian Ocean means nothing to a roofline in Texas, and solar totality varies over a few hundred kilometres — so a worldwide eclipse event would be wrong most of the time. That's a heavier dependency than the image should carry after #60's hardening, and it deserved its own decision rather than a guess inside this one. Tracked in #126. ## Verification `ruff check` clean, `mypy app` clean across 69 source files, `pytest` 610 passed. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Put meteor showers, full moons and aurora on the calendar (#20)
All checks were successful
CI / Dockerfile lint (pull_request) Successful in 6s
CI / Alembic migration check (pull_request) Successful in 1m3s
CI / Frontend lint, test & build (pull_request) Successful in 1m9s
CI / Pre-commit hooks (pull_request) Successful in 1m19s
CI / Python lint & type-check (pull_request) Successful in 1m37s
CI / Python tests (pull_request) Successful in 4m1s
CI / Docker build, health smoke & E2E (pull_request) Successful in 1m57s
56834c4d9b
Seven meteor showers and eleven named full moons resolve as events, each
with its own built-in scheme, plus an optional aurora layer.

Full moons are COMPUTED from astral, which is already a dependency, so
unlike the solstices they add nothing to the lookup-table horizon (#61)
and keep working indefinitely. Tests assert astronomy rather than dates --
that each returned day really is full, that gaps are whole synodic months,
and that the result moves year to year -- because a function returning a
fixed date would pass a weaker test.

Meteor showers use fixed multi-day spans rather than a table. Earth
crosses the same debris stream on the same orbital date each year, so the
peak moves by at most a day; and the peak is a few hours inside several
active nights, so a single-day event would usually miss it.

Schemes are explicit per event rather than tag-inherited. A pre-existing
test enforces that convention and my tag entries defeated it, which was
the right catch: the colours now differ by the character of each shower
and moon rather than being copies, and the Harvest Moon is orange because
the low autumn moon actually looks that way.

Aurora (AURORA_ENABLED, off by default) polls NOAA's planetary K-index and
shows an aurora scheme above AURORA_MIN_KP, restoring the scheduled scheme
when it subsides. Edge-triggered, so a continuing storm does not restart
the effect every poll and stutter the strip. No visibility model:
visibility depends on geomagnetic latitude as well as Kp and that mapping
is a rule of thumb rather than a formula, so the threshold is the knob and
is documented in those terms rather than implementing a bad model.

Eclipses are split to #126 rather than guessed at. They need an ephemeris
AND a per-observer visibility calculation -- a lunar eclipse over the
Indian Ocean means nothing to a roofline in Texas, and solar totality
varies over a few hundred kilometres -- so a worldwide eclipse event would
be wrong most of the time. That is a heavier dependency than the image
carries after #60 and deserved its own decision.

One test premise of mine was wrong and worth recording: I asserted
consecutive named moons are 28-31 days apart, and May 2026 has two full
moons (the 1st and the 31st). One-per-calendar-month necessarily skips
one, so the gap is two synodic months. The test now asserts whole synodic
multiples and documents the blue-moon case.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
claude-bot deleted branch feat/20-astronomical 2026-09-04 23:55:59 +00:00
Sign in to join this conversation.
No description provided.