Fix propagation scoring and calibration audit #16

Merged
graham merged 2 commits from fix/audit-backlog into main 2026-09-22 07:23:18 -05:00
Owner

What changed

  • Correct propagation-score physics and missing-data handling in both Elixir and Rust, including rain gating, pressure direction/trend, 24 GHz moisture behavior, duct gating, time/sunset interactions, aurora handling, regional behavior, and widened score range.
  • Make HRRR/HRDPS inputs consistent: true 2 m surface data, below-ground filtering, uniform forecast-hour scoring, hourly HRDPS precipitation units, previous pressure, and the added profile/weather fields.
  • Replace synthetic backtest and calibration paths with the production scorer, genuine negative targets, rolling temporal holdouts, clustered confidence intervals, AUC/Brier gates, provenance, pending candidates, administrator review/apply, and hot-reloaded database overrides in Elixir and Rust.
  • Preserve contact signal quality and complete PSK Reporter weather inputs; add the required schema migrations and import support.
  • Vendor TypeScript and Leaflet/GeoJSON types so strict type-checking runs without npm in precommit and CI; fix the resulting hook errors.
  • Refresh the public algorithm/about documentation, changelog, golden fixture, and dated production validation artifacts; remove obsolete synthetic reports and feature code.

Production evidence

Measurements covered 74,590 valid contacts, 89,331,618 HRRR profiles, 9,220,896 PSK Reporter samples, and 40,081 beacon samples. They support lower-pressure favorability, beneficial 24 GHz moisture during duct formation with path loss modeled separately, and removal of the prior regional multiplier distortion.

The leakage-free recalibration found no independently held-out pre-cutoff negative history sufficient for a fresh per-band fit. All 12 existing overrides are therefore carried forward with insufficient_temporal_holdout provenance rather than publishing a falsely validated change. The canonical validation artifact evaluates 450,365 rows through the real production scorer.

Verification

  • env -u CI make precommit: 4,915 passed, 6 skipped, 22 locally excluded; strict TypeScript, warnings-as-errors compile, unused-dependency check, Credo (6,919 mods/funs, no issues), and xref all passed.
  • cargo clippy --all-targets -- -D warnings
  • cargo test --release: 264 passed, 1 ignored.
  • python3 -m py_compile scripts/recalibrate.py scripts/validate_algo.py scripts/validate_forecast.py
  • mix algo.score exercised the production JSONL scoring path.
  • Browser smoke checks rendered /about and the full /algo page with the new calibration/governance content.

Commit: 50a3fe07

Review round 2 (28be681c)

All five review findings addressed:

  • Held-out validation scores the candidate, not live weightssample/6 stores raw conditions; fit_band/2 scores train and validation under both candidate and current weights via Scorer.cell_score/3, matching Pskr.Recalibrator.validate_band/5. Report lines now show candidate delta=… vs current …, auc=… vs ….
  • HRDPS pressure tendencyrun_chain_step_hrdps fetches the preceding step's PRMSL (f-1 hourly, f-3 for the 3-hourly tail) via HrdpsClient::fetch_variable and attaches previous_pressure_mb, so Canadian cells take the same tendency branch as HRRR cells.
  • Monotone pressure-delta curve — deltas <= -2.5 / -0.8 / +0.5 / +2.0 mb score 80/70/60/55, larger rises 45, in both scorer.ex and scorer.rs; golden fixture regenerated; new monotonicity property + Rust unit test.
  • Weight-run lifecycle — retired runs become superseded (not rejected) with a new applied_at column; loaders order by applied_at DESC NULLS LAST, id DESC; the admin page allows Apply on superseded runs for rollback; apply_run/1 rejects rejected/applied statuses with {:error, :invalid_status}.
  • Validation stat/algo now shows summary.scored_rows (450,365 held-out rows) instead of the 11 contact rows.

Verification: env -u CI make precommit green (4,915 tests), cargo clippy --all-targets -- -D warnings clean, cargo test --release 265 passed, scorer_golden parity holds.

## What changed - Correct propagation-score physics and missing-data handling in both Elixir and Rust, including rain gating, pressure direction/trend, 24 GHz moisture behavior, duct gating, time/sunset interactions, aurora handling, regional behavior, and widened score range. - Make HRRR/HRDPS inputs consistent: true 2 m surface data, below-ground filtering, uniform forecast-hour scoring, hourly HRDPS precipitation units, previous pressure, and the added profile/weather fields. - Replace synthetic backtest and calibration paths with the production scorer, genuine negative targets, rolling temporal holdouts, clustered confidence intervals, AUC/Brier gates, provenance, pending candidates, administrator review/apply, and hot-reloaded database overrides in Elixir and Rust. - Preserve contact signal quality and complete PSK Reporter weather inputs; add the required schema migrations and import support. - Vendor TypeScript and Leaflet/GeoJSON types so strict type-checking runs without npm in precommit and CI; fix the resulting hook errors. - Refresh the public algorithm/about documentation, changelog, golden fixture, and dated production validation artifacts; remove obsolete synthetic reports and feature code. ## Production evidence Measurements covered 74,590 valid contacts, 89,331,618 HRRR profiles, 9,220,896 PSK Reporter samples, and 40,081 beacon samples. They support lower-pressure favorability, beneficial 24 GHz moisture during duct formation with path loss modeled separately, and removal of the prior regional multiplier distortion. The leakage-free recalibration found no independently held-out pre-cutoff negative history sufficient for a fresh per-band fit. All 12 existing overrides are therefore carried forward with `insufficient_temporal_holdout` provenance rather than publishing a falsely validated change. The canonical validation artifact evaluates 450,365 rows through the real production scorer. ## Verification - `env -u CI make precommit`: 4,915 passed, 6 skipped, 22 locally excluded; strict TypeScript, warnings-as-errors compile, unused-dependency check, Credo (6,919 mods/funs, no issues), and xref all passed. - `cargo clippy --all-targets -- -D warnings` - `cargo test --release`: 264 passed, 1 ignored. - `python3 -m py_compile scripts/recalibrate.py scripts/validate_algo.py scripts/validate_forecast.py` - `mix algo.score` exercised the production JSONL scoring path. - Browser smoke checks rendered `/about` and the full `/algo` page with the new calibration/governance content. Commit: `50a3fe07` ## Review round 2 (28be681c) All five review findings addressed: - **Held-out validation scores the candidate, not live weights** — `sample/6` stores raw conditions; `fit_band/2` scores train and validation under both candidate and current weights via `Scorer.cell_score/3`, matching `Pskr.Recalibrator.validate_band/5`. Report lines now show `candidate delta=… vs current …, auc=… vs …`. - **HRDPS pressure tendency** — `run_chain_step_hrdps` fetches the preceding step's PRMSL (f-1 hourly, f-3 for the 3-hourly tail) via `HrdpsClient::fetch_variable` and attaches `previous_pressure_mb`, so Canadian cells take the same tendency branch as HRRR cells. - **Monotone pressure-delta curve** — deltas <= -2.5 / -0.8 / +0.5 / +2.0 mb score 80/70/60/55, larger rises 45, in both `scorer.ex` and `scorer.rs`; golden fixture regenerated; new monotonicity property + Rust unit test. - **Weight-run lifecycle** — retired runs become `superseded` (not `rejected`) with a new `applied_at` column; loaders order by `applied_at DESC NULLS LAST, id DESC`; the admin page allows Apply on superseded runs for rollback; `apply_run/1` rejects `rejected`/`applied` statuses with `{:error, :invalid_status}`. - **Validation stat** — `/algo` now shows `summary.scored_rows` (450,365 held-out rows) instead of the 11 contact rows. Verification: `env -u CI make precommit` green (4,915 tests), `cargo clippy --all-targets -- -D warnings` clean, `cargo test --release` 265 passed, `scorer_golden` parity holds.
Fix propagation scoring and calibration audit
Some checks failed
skippy-bot/review Skippy review: 2 blocking findings open — see the PR thread
50a3fe07df
skippy-bot left a comment

🤖 Skippy PR review

5 findings — 2 blocking before merge.

Severity Location Issue
🟡 Warning lib/microwaveprop/propagation/recalibrator.ex:220 Held-out validation scores the live weights, not the candidate
🟡 Warning rust/prop_grid_rs/src/pipeline.rs:354 HRDPS steps never get previous pressure: pressure factor means two different things
🔵 Suggestion lib/microwaveprop/propagation/scorer.ex:579 Pressure-trend curve is not monotone: slowly rising beats steady
🔵 Suggestion lib/microwaveprop/propagation/algo_weight_runs.ex:62 Superseded run is recorded as rejected, erasing the applied history
🔵 Suggestion lib/microwaveprop/propagation/validation_report.ex:73 Validation stat understates the held-out corpus by four orders of magnitude

First review (242 files, +214588/-7697). Reviewed the scoring/calibration/governance surface: scorer.ex + scorer.rs, band_config on both sides, band_weights.ex/weight_overrides.rs, propagation.ex, path_compute.ex, backtest.ex, backtest/features removal, both recalibrators, validation_report.ex, the algo_weight_runs migration/admin path, hrrr/hrdps clients, pipeline.rs, planes.rs, hrrr_points.rs, adif/csv import, and the 4 migrations. Skipped as low risk: vendored TypeScript/Leaflet types, the 4MB of dated validation artifacts, docs/algo-reports prose, and test-only edits.

Reviewed 50a3fe07dffb. Comment skippy review to re-run.

### 🤖 Skippy PR review **5 findings** — 2 blocking before merge. | Severity | Location | Issue | | --- | --- | --- | | 🟡 Warning | `lib/microwaveprop/propagation/recalibrator.ex:220` | Held-out validation scores the live weights, not the candidate | | 🟡 Warning | `rust/prop_grid_rs/src/pipeline.rs:354` | HRDPS steps never get previous pressure: pressure factor means two different things | | 🔵 Suggestion | `lib/microwaveprop/propagation/scorer.ex:579` | Pressure-trend curve is not monotone: slowly rising beats steady | | 🔵 Suggestion | `lib/microwaveprop/propagation/algo_weight_runs.ex:62` | Superseded run is recorded as rejected, erasing the applied history | | 🔵 Suggestion | `lib/microwaveprop/propagation/validation_report.ex:73` | Validation stat understates the held-out corpus by four orders of magnitude | First review (242 files, +214588/-7697). Reviewed the scoring/calibration/governance surface: scorer.ex + scorer.rs, band_config on both sides, band_weights.ex/weight_overrides.rs, propagation.ex, path_compute.ex, backtest.ex, backtest/features removal, both recalibrators, validation_report.ex, the algo_weight_runs migration/admin path, hrrr/hrdps clients, pipeline.rs, planes.rs, hrrr_points.rs, adif/csv import, and the 4 migrations. Skipped as low risk: vendored TypeScript/Leaflet types, the 4MB of dated validation artifacts, docs/algo-reports prose, and test-only edits. <sub>Reviewed `50a3fe07dffb`. Comment `skippy review` to re-run.</sub> <!-- skippy-pr-review -->
Outdated
@ -489,1 +218,3 @@
initial_loss: initial_loss |> Nx.to_number() |> to_float()
time: time,
label: label,
score: Scorer.cell_score(conditions, config),
Author
Owner

Fixed in 28be681c. sample/6 now stores the raw conditions map instead of a pre-baked score, and fit_band/2 scores both the train and validation rows twice — once under the candidate weights/multipliers, once under the live weights — via Scorer.cell_score/3, the same approach as Pskr.Recalibrator.validate_band/5. The report line now reads candidate delta=… vs current …, auc=… vs … so the admin sees the candidates own separation rather than a constant.

Fixed in 28be681c. `sample/6` now stores the raw `conditions` map instead of a pre-baked score, and `fit_band/2` scores both the train and validation rows twice — once under the candidate `weights`/`multipliers`, once under the live weights — via `Scorer.cell_score/3`, the same approach as `Pskr.Recalibrator.validate_band/5`. The report line now reads `candidate delta=… vs current …, auc=… vs …` so the admin sees the candidates own separation rather than a constant.
Author
Owner

Fixed in 28be681c. run_chain_step_hrdps now fetches the preceding steps PRMSL file alongside the main blob (f-1 for hourly steps, f-3 for the 3-hourly tail, mirroring add_previous_hrrr_pressures convention) via a new HrdpsClient::fetch_variable, decodes it in the same spawn_blocking closure, and attaches previous_pressure_mb — so Canadian cells take the same pressure-tendency branch as HRRR cells. Decode/fetch failures log a warning and fall back to the absolute branch. algo.md documents the behavior.

Fixed in 28be681c. `run_chain_step_hrdps` now fetches the preceding steps PRMSL file alongside the main blob (f-1 for hourly steps, f-3 for the 3-hourly tail, mirroring `add_previous_hrrr_pressure`s convention) via a new `HrdpsClient::fetch_variable`, decodes it in the same `spawn_blocking` closure, and attaches `previous_pressure_mb` — so Canadian cells take the same pressure-tendency branch as HRRR cells. Decode/fetch failures log a warning and fall back to the absolute branch. `algo.md` documents the behavior.
Author
Owner

Fixed in 28be681c. The delta branch is now monotone: deltas <= -2.5, -0.8, +0.5, +2.0 mb score 80, 70, 60, 55, and larger rises score 45 — matching the documented direction (falling = improving). Same table in scorer.rs; priv/rust_golden/scores.bincode regenerated and scorer_golden passes. Added a score_pressure property asserting the delta branch is non-increasing in delta, plus a Rust unit test over the same deltas.

Fixed in 28be681c. The delta branch is now monotone: deltas <= -2.5, -0.8, +0.5, +2.0 mb score 80, 70, 60, 55, and larger rises score 45 — matching the documented direction (falling = improving). Same table in `scorer.rs`; `priv/rust_golden/scores.bincode` regenerated and `scorer_golden` passes. Added a `score_pressure` property asserting the delta branch is non-increasing in delta, plus a Rust unit test over the same deltas.
Author
Owner

Fixed in 28be681c. Retired runs are now marked superseded (distinct from rejected), and a new applied_at column records when each run went live — so the table answers "which candidate was live between A and B". latest_applied_payload and the Rust weight_overrides loader both order by applied_at DESC NULLS LAST, id DESC, removing the same-second inserted_at nondeterminism. The admin page renders Apply on superseded runs (rollback path), and apply_run/1 rejects rejected/applied statuses server-side with {:error, :invalid_status}. Migration 20260922121035 adds the column.

Fixed in 28be681c. Retired runs are now marked `superseded` (distinct from `rejected`), and a new `applied_at` column records when each run went live — so the table answers "which candidate was live between A and B". `latest_applied_payload` and the Rust `weight_overrides` loader both order by `applied_at DESC NULLS LAST, id DESC`, removing the same-second `inserted_at` nondeterminism. The admin page renders Apply on `superseded` runs (rollback path), and `apply_run/1` rejects `rejected`/`applied` statuses server-side with `{:error, :invalid_status}`. Migration `20260922121035` adds the column.
Author
Owner

Fixed in 28be681c. The stat now reads summary.scored_rows (450,365 in the 2026-09-22 artifact) and the /algo label says "held-out rows" instead of "held-out QSOs", so the PSKR and beacon mass is no longer hidden behind the 11 contact rows.

Fixed in 28be681c. The stat now reads `summary.scored_rows` (450,365 in the 2026-09-22 artifact) and the `/algo` label says "held-out rows" instead of "held-out QSOs", so the PSKR and beacon mass is no longer hidden behind the 11 contact rows.
@ -0,0 +59,4 @@
|> Multi.update_all(
:retire_previous,
from(r in AlgoWeightRun, where: r.status == "applied" and r.id != ^run.id),
set: [status: "rejected"]
First-time contributor

🔵 Suggestion — Superseded run is recorded as rejected, erasing the applied history

apply_run/1 retires the previously applied run by setting status: "rejected", which is the same value an administrator sees for a candidate nobody applied and the same value latest_applied_payload/0 filters out. After a second apply, the table can no longer answer "which candidate was live between date A and date B", and because the LiveView only renders Apply for status == "pending" there is no way to roll back to the previously live weights. Suggest a distinct state (superseded) or a superseded_by/retired_at pair, with the page allowing Apply on a superseded run. Note also that inserted_at is truncated to the second, so two runs inserted in the same second make ORDER BY inserted_at DESC LIMIT 1 nondeterministic for both the Elixir loader and weight_overrides.rs.

**🔵 Suggestion — Superseded run is recorded as rejected, erasing the applied history** `apply_run/1` retires the previously applied run by setting `status: "rejected"`, which is the same value an administrator sees for a candidate nobody applied and the same value `latest_applied_payload/0` filters out. After a second apply, the table can no longer answer "which candidate was live between date A and date B", and because the LiveView only renders Apply for `status == "pending"` there is no way to roll back to the previously live weights. Suggest a distinct state (`superseded`) or a `superseded_by`/`retired_at` pair, with the page allowing Apply on a superseded run. Note also that `inserted_at` is truncated to the second, so two runs inserted in the same second make `ORDER BY inserted_at DESC LIMIT 1` nondeterministic for both the Elixir loader and `weight_overrides.rs`.
skippy-bot marked this conversation as resolved
@ -489,1 +218,3 @@
initial_loss: initial_loss |> Nx.to_number() |> to_float()
time: time,
label: label,
score: Scorer.cell_score(conditions, config),
First-time contributor

🟡 Warning — Held-out validation scores the live weights, not the candidate

sample/6 stores Scorer.cell_score(conditions, config), so every train and validation row is scored with the weights resolved today. fit_band/2 then attaches validation_summary(split.validation) to the result, and that text is what an administrator reads in the report column of /admin/algo-weights (score_delta=...). The candidate's own weights/multipliers are never applied to the holdout, so the reported separation describes the current scorer and cannot show whether the candidate is better or worse: the same number comes out for every candidate fit on the same window. Scorer.cell_score/3 exists for exactly this and the sibling fitter in this PR (Pskr.Recalibrator.validate_band/5) uses it, scoring the holdout twice, with candidate weights and with nil. Score the validation rows both ways here too, and report candidate vs current AUC/score delta in the report string.

**🟡 Warning — Held-out validation scores the live weights, not the candidate** `sample/6` stores `Scorer.cell_score(conditions, config)`, so every train and validation row is scored with the weights resolved *today*. `fit_band/2` then attaches `validation_summary(split.validation)` to the result, and that text is what an administrator reads in the `report` column of `/admin/algo-weights` (`score_delta=...`). The candidate's own `weights`/`multipliers` are never applied to the holdout, so the reported separation describes the current scorer and cannot show whether the candidate is better or worse: the same number comes out for every candidate fit on the same window. `Scorer.cell_score/3` exists for exactly this and the sibling fitter in this PR (`Pskr.Recalibrator.validate_band/5`) uses it, scoring the holdout twice, with candidate weights and with `nil`. Score the validation rows both ways here too, and report candidate vs current AUC/score delta in the report string.
skippy-bot marked this conversation as resolved
@ -578,0 +576,4 @@
defp score_pressure_delta(delta) when delta < -2.5, do: 80
defp score_pressure_delta(delta) when delta < -0.8, do: 70
defp score_pressure_delta(delta) when delta < 0.5, do: 60
defp score_pressure_delta(delta) when delta < 2.0, do: 65
First-time contributor

🔵 Suggestion — Pressure-trend curve is not monotone: slowly rising beats steady

The clauses are ordered 80 (delta < -2.5), 70 (< -0.8), 60 (< 0.5), 65 (< 2.0), 45. A pressure rising 0.5-2.0 mb returns 65 while a nearly steady one (-0.8 to 0.5) returns 60, so a small rise scores better than flat, opposite to this PR's documented direction ("falling pressure is improving and rising pressure is degrading"). The pre-inversion table carried the same artefact in the mirror band (delta > -2.0 -> 65 next to delta > -0.5 -> 60), so this looks like the old ordering preserved through the sign flip rather than a deliberate shape. Make the thresholds monotone in delta (the 65 band should sit below the 60 band) in both lib/microwaveprop/propagation/scorer.exandrust/prop_grid_rs/src/scorer.rs, and regenerate priv/rust_golden/scores.bincode` in the same commit.

**🔵 Suggestion — Pressure-trend curve is not monotone: slowly rising beats steady** The clauses are ordered 80 (`delta < -2.5`), 70 (`< -0.8`), 60 (`< 0.5`), 65 (`< 2.0`), 45. A pressure rising 0.5-2.0 mb returns 65 while a nearly steady one (-0.8 to 0.5) returns 60, so a small rise scores better than flat, opposite to this PR's documented direction ("falling pressure is improving and rising pressure is degrading"). The pre-inversion table carried the same artefact in the mirror band (`delta > -2.0` -> 65 next to `delta > -0.5` -> 60`), so this looks like the old ordering preserved through the sign flip rather than a deliberate shape. Make the thresholds monotone in delta (the 65 band should sit below the 60 band) in both `lib/microwaveprop/propagation/scorer.ex` and `rust/prop_grid_rs/src/scorer.rs`, and regenerate `priv/rust_golden/scores.bincode` in the same commit.
skippy-bot marked this conversation as resolved
@ -54,2 +72,2 @@
bands: length(@eligible_gains),
test_contacts: get_in(@report, ["summary", "test_contacts"]),
bands: length(@tested_values),
test_contacts: get_in(@report, ["summary", "contact_rows"]) || 0,
First-time contributor

🔵 Suggestion — Validation stat understates the held-out corpus by four orders of magnitude

test_contacts now reads summary.contact_rows, which is 11 in docs/algo-reports/validation-2026-09-22.json (only three-position contacts) while summary.scored_rows is 450,365. /algo renders it as "median rho over 7 bands - 11 held-out QSOs", so the public page tells readers the score validation rests on 11 QSOs when the artifact scored 450,365 held-out rows (450,116 PSKR, 238 beacon, 11 contact). Point the stat at summary.scored_rows, or split it per source so the PSKR and beacon mass is visible.

**🔵 Suggestion — Validation stat understates the held-out corpus by four orders of magnitude** `test_contacts` now reads `summary.contact_rows`, which is 11 in `docs/algo-reports/validation-2026-09-22.json` (only three-position contacts) while `summary.scored_rows` is 450,365. `/algo` renders it as "median rho over 7 bands - 11 held-out QSOs", so the public page tells readers the score validation rests on 11 QSOs when the artifact scored 450,365 held-out rows (450,116 PSKR, 238 beacon, 11 contact). Point the stat at `summary.scored_rows`, or split it per source so the PSKR and beacon mass is visible.
skippy-bot marked this conversation as resolved
@ -350,3 +352,3 @@
let fused = tokio::task::spawn_blocking(move || {
derive_and_score(&grid, valid_time, false, Some(&mask), kp)
derive_and_score(&grid, valid_time, false, Some(&mask), kp, &weights)
First-time contributor

🟡 Warning — HRDPS steps never get previous pressure: pressure factor means two different things

add_previous_hrrr_pressure is called only from run_chain_step (line 161) and run_analysis_step (line 1587). So every HRDPS forecast step keeps prev_pressure_mb: None and lands in score_pressure/2's absolute branch (30-88, keyed on absolute MSL pressure), while HRRR-owned cells take the delta branch (45-80, keyed on a 1-hour tendency that is usually near zero). Pressure is one of the ten weighted factors, so an HRRR-owned cell and an HRDPS-owned cell on the border are scored on different quantities for the same valid time, which contradicts the PR's stated HRRR/HRDPS input consistency. Either fetch the preceding HRDPS step's PRMSL in run_chain_step_hrdps or make both models use the absolute branch deliberately; right now which branch runs is an artifact of which model owns the cell.

**🟡 Warning — HRDPS steps never get previous pressure: pressure factor means two different things** `add_previous_hrrr_pressure` is called only from `run_chain_step` (line 161) and `run_analysis_step` (line 1587). So every HRDPS forecast step keeps `prev_pressure_mb: None` and lands in `score_pressure/2`'s absolute branch (30-88, keyed on absolute MSL pressure), while HRRR-owned cells take the delta branch (45-80, keyed on a 1-hour tendency that is usually near zero). Pressure is one of the ten weighted factors, so an HRRR-owned cell and an HRDPS-owned cell on the border are scored on different quantities for the same valid time, which contradicts the PR's stated HRRR/HRDPS input consistency. Either fetch the preceding HRDPS step's PRMSL in `run_chain_step_hrdps` or make both models use the absolute branch deliberately; right now which branch runs is an artifact of which model owns the cell.
skippy-bot marked this conversation as resolved
Address review: pressure delta table, HRDPS tendency, weight-run lifecycle
All checks were successful
skippy-bot/review Skippy review: clean — 5 non-blocking notes open
28be681c9c
First-time contributor

Resolved all 5 earlier findings in 28be681c:

  • Held-out validation now scores each row twice through the production cell scorer (candidate weights vs nil), so the report's candidate delta / auc vs current finally describes the candidate.
  • HRDPS fetches the preceding step's PRMSL and pushes previous_pressure_mb, so Canadian cells take the same tendency branch as HRRR cells.
  • Pressure-delta curve is monotone in both runtimes (65 → 55), golden fixture and algo.md regenerated with it.
  • superseded + applied_at preserve the applied history, and a superseded run is re-appliable as the rollback path (latest_applied_payload and weight_overrides.rs both order by apply time, not insert time).
  • /algo reports held-out rows (450,365) instead of 11 QSOs.

Nothing new in 50a3fe07..28be681c. Check is green.

Resolved all 5 earlier findings in `28be681c`: - Held-out validation now scores each row twice through the production cell scorer (candidate weights vs `nil`), so the report's `candidate delta` / `auc` vs `current` finally describes the candidate. - HRDPS fetches the preceding step's PRMSL and pushes `previous_pressure_mb`, so Canadian cells take the same tendency branch as HRRR cells. - Pressure-delta curve is monotone in both runtimes (65 → 55), golden fixture and `algo.md` regenerated with it. - `superseded` + `applied_at` preserve the applied history, and a superseded run is re-appliable as the rollback path (`latest_applied_payload` and `weight_overrides.rs` both order by apply time, not insert time). - `/algo` reports held-out rows (450,365) instead of 11 QSOs. Nothing new in `50a3fe07..28be681c`. Check is green. <!-- skippy-pr-review -->
graham merged commit a3623d683f into main 2026-09-22 07:23:18 -05:00
graham deleted branch fix/audit-backlog 2026-09-22 07:23:18 -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!16
No description provided.