Score the evening window from real sunset, not 8 pm local #12

Merged
graham merged 1 commit from fix/time-of-day-sunset into main 2026-09-19 17:56:35 -05:00
Owner

What

classify_time_period/2 keyed the morning on latitude/month sunrise but the evening on a hardcoded local >= 20, and its clause order meant any hour with d > 6 before 20:00 scored 18 "Afternoon — full convective mixing". In December at 40°N the sun sets ~16:45 local, so 17:00–20:00 — the best radiation-inversion window of the day — was scored as peak convective mixing. (tmp/bugs.md §B, P1.)

  • BandConfig.sunset_hour/2: same solar-declination + hour-angle formula as sunrise_hour/2, mirrored about solar noon (12 + ha/15), clamped to [15.0, 20.0].
  • Scorer.score_time_of_day/5 threads the real sunset into classify_time_period/3: evening runs from local sunset for three hours, the daytime mixing clauses only apply before sunset, everything later stays night.
  • Mirrored exactly in rust/prop_grid_rs/src/{band_config,scorer}.rs — same clause order, scores and clamp.

Resulting curve at 40°N: December (sunset 16.6) → 16:35–19:35 scores 72 evening (was 18 "afternoon"), then 55 night; June (sunset 19.4) → 19:25–22:25 evening.

Verification

  • mix test test/microwaveprop/propagation/ → 568 passed, 0 failures (the whole scorer blast radius).
  • Targeted scorer / band_config / factor_narrative / validation_report suites → 82 passed.
  • cargo clippy --all-targets -- -D warnings clean; cargo test --release green, including scorer_golden. The golden fixture regenerates byte-identical (no golden sample lands in the corrected window), so the new window is pinned by mirrored Elixir/Rust unit tests at identical inputs instead.
  • mix credo --strict: no issues. mix format --check-formatted on the touched files: clean.
  • CHANGELOG entry added.

Known follow-ups (not in this PR)

scripts/validate_algo.py and scripts/validate_forecast.py still re-implement the old month-only sunrise plus the fixed 8 pm evening window. The validator is fixed by the sibling PR fix/validator-production-parity; scripts/validate_forecast.py and the algo.md description of the old window are untouched.

## What `classify_time_period/2` keyed the morning on latitude/month sunrise but the evening on a hardcoded `local >= 20`, and its clause order meant any hour with `d > 6` before 20:00 scored 18 "Afternoon — full convective mixing". In December at 40°N the sun sets ~16:45 local, so 17:00–20:00 — the best radiation-inversion window of the day — was scored as peak convective mixing. (`tmp/bugs.md` §B, P1.) - `BandConfig.sunset_hour/2`: same solar-declination + hour-angle formula as `sunrise_hour/2`, mirrored about solar noon (`12 + ha/15`), clamped to `[15.0, 20.0]`. - `Scorer.score_time_of_day/5` threads the real sunset into `classify_time_period/3`: evening runs from local sunset for three hours, the daytime mixing clauses only apply before sunset, everything later stays night. - Mirrored exactly in `rust/prop_grid_rs/src/{band_config,scorer}.rs` — same clause order, scores and clamp. Resulting curve at 40°N: December (sunset 16.6) → 16:35–19:35 scores 72 evening (was 18 "afternoon"), then 55 night; June (sunset 19.4) → 19:25–22:25 evening. ## Verification - `mix test test/microwaveprop/propagation/` → 568 passed, 0 failures (the whole scorer blast radius). - Targeted scorer / band_config / factor_narrative / validation_report suites → 82 passed. - `cargo clippy --all-targets -- -D warnings` clean; `cargo test --release` green, including `scorer_golden`. The golden fixture regenerates byte-identical (no golden sample lands in the corrected window), so the new window is pinned by mirrored Elixir/Rust unit tests at identical inputs instead. - `mix credo --strict`: no issues. `mix format --check-formatted` on the touched files: clean. - CHANGELOG entry added. ## Known follow-ups (not in this PR) `scripts/validate_algo.py` and `scripts/validate_forecast.py` still re-implement the old month-only sunrise plus the fixed 8 pm evening window. The validator is fixed by the sibling PR `fix/validator-production-parity`; `scripts/validate_forecast.py` and the `algo.md` description of the old window are untouched.
fix(algo): score the evening window from real sunset, not 8 pm local
All checks were successful
skippy-bot/review Skippy review: clean — no open findings
5e75fb4758
First-time contributor

First review at 5e75fb4: no blocking findings.

Checked and clean: sunset_hour/2 and the new classify_time_period/3 clause order are clause-for-clause identical between scorer.ex/band_config.ex and the Rust mirror (same DOY table, same mirrored clamp, same local < sunset guards, so the two time_of_day_* suites assert the same values on both sides).

Two things I verified rather than took on trust, both hold:

  • The five priv/rust_golden conditions (06:15, 07:32, 14:32, 05:10, 11:48 local) all score in the part of the day this PR did not touch, so leaving scores.bincode unregenerated is correct and the exact-match scorer_golden gate stays green.
  • No other in-repo consumer of the classifier is affected: the only duplicated copies are the two scripts/validate_*.py ports, which the PR already lists as follow-ups.
**First review at `5e75fb4`: no blocking findings.** Checked and clean: `sunset_hour/2` and the new `classify_time_period/3` clause order are clause-for-clause identical between `scorer.ex`/`band_config.ex` and the Rust mirror (same DOY table, same mirrored clamp, same `local < sunset` guards, so the two `time_of_day_*` suites assert the same values on both sides). Two things I verified rather than took on trust, both hold: - The five `priv/rust_golden` conditions (06:15, 07:32, 14:32, 05:10, 11:48 local) all score in the part of the day this PR did not touch, so leaving `scores.bincode` unregenerated is correct and the exact-match `scorer_golden` gate stays green. - No other in-repo consumer of the classifier is affected: the only duplicated copies are the two `scripts/validate_*.py` ports, which the PR already lists as follow-ups. <!-- skippy-pr-review -->
graham merged commit 5db4ad5492 into main 2026-09-19 17:56:35 -05:00
graham deleted branch fix/time-of-day-sunset 2026-09-19 17:56:35 -05:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
graham/prop!12
No description provided.