feat(regression): per-predictor quadratic + Smith River nr Gardiner fit - #120
Conversation
--quadratic squares every predictor, but an all-predictor quadratic fit can leave one squared term whose block-bootstrap CI straddles zero — a non-significant x² coefficient is an extrapolation hazard (at the high end of the predictor range it contributes hundreds of cfs of phantom signal, exactly where launch decisions happen). --quadratic-for SITE (repeatable, mutually exclusive with --quadratic) squares only the named predictors so the insignificant terms can be dropped. Internally the quadratic spec is normalized to a per-predictor mask (_quad_mask); bool stays accepted everywhere so existing callers and the bare --quadratic flag are unchanged. The report's family label, Reproduce snippet, coefficient labels, SQL stub, and fit JSON all follow the mask; the JSON keeps `quadratic` as a bool (any squared term) for backward compatibility and adds `quadratic_sites`. Extract _default_stability_starts and _quad_spec_from_args from main() to stay under the C901 complexity limit. Motivating case: Smith River nr Gardiner (14323100, retired 1973) from Siuslaw (14307620) + SF Coquille (14325000) — Siuslaw² is significant and halves the dry-season bias, SF Coquille² is not (CI straddles 0) and would add ~+1,175 cfs at the predictor's observed max. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…igrations The report's "SQL stub" section and both READMEs still instructed pasting an INSERT into a data/db/migrations file — stale since the Phase-5 metadata redesign (#106): calc_expression is a metadata table, so a new migration writing it now fails CI (test_migrations_schema_only.py). Replace the stub with the column values (data_type / expression / time_expression / note / provenance_slug) for a new calc_expression.csv row in kayak_data, and update both READMEs' workflow steps to the CSV + levels sync-metadata flow. Pre-redesign writeups keep their SQL stubs as immutable history. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…F Coquille Smith R nr Gardiner's USGS record is daily means 1965-10-01..1973-06-30 only, so the fit window is capped by the Siuslaw nr Mapleton record start (1967-10): n=2100 daily means. Donor screening over every active gauge overlapping that window (block-bootstrap 95% CIs, monthly blocks): - Siuslaw 14307620 solo (status quo): r2=0.928, RMSE 385 cfs - SF Coquille 14325000 adds real signal: +0.130, CI [+0.036, +0.225], VIF 4.3 — the only candidate south of the Smith, independent storm-position information - Rejected (CI straddles zero once Siuslaw is in): Alsea 14306500, Siletz 14305500, Umpqua-Elkton 14321000, Long Tom-Noti 14166500, Coast Fork-London 14152500 (significant vs Siuslaw alone but redundant given SF Coquille and the weaker duo) Form: quadratic on the Siuslaw only (--quadratic-for). Siuslaw² CI [+9.5e-07, +7.4e-06] excludes zero and halves the dry-season bias (+59% -> +30% of mean flow) and the Q1 residual; SF Coquille² is not significant and would contribute ~+1,175 cfs of phantom signal at the predictor's observed max, so it is dropped. Final: r2=0.9366, RMSE=360.4 cfs (vs 384.7 status quo, -6.3%). Handles reference the existing gauges Suislaw_Mapleton_merge and Coquille_Powers_merge; the calc_expression.csv row in kayak_data comes with the gauge/reach metadata wiring. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…V flow docs/migrations.md still pointed metadata edits at data/db/*.csv — stale since the Phase-6 data-repo split moved the CSVs to the kayak_data repo (METADATA_DIR). Repoint the five references; the data/db/migrations/ path is unchanged (schema migrations stay in the code repo). Also: the regression report's maintainer-only section was renamed from "## SQL stub" to "## `calc_expression` row" (b3509cc), but deploy.py's _HTML_DROP_SECTIONS only matched the old heading — the new Smith report would have leaked its calc-row scaffolding into the published HTML. Add the new heading (keeping the old one for the pre-redesign reports, which retain their SQL stubs as immutable history) and a filter test for it. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Review for merging/deploying on the live server: Finding:
Notes:
Verification:
Recommendation: hold merge until the reproduce-command/calc-handle issue is fixed. After that, this looks safe to merge and deploy with the paired metadata PR in the order above. |
Review — verified against the live tree and live DBVerdict: ready to merge. CI is green ( Correctness —
|
…andles Address both PR #120 reviews: - The "Generated by" snippet omitted --calc-handle, so re-running it on a report generated with custom handles would silently emit default p1::<site> handles into the calc_expression values — not bit-for-bit reproducible. Custom handles are now appended (defaults stay omitted; they self-reproduce). Smith report regenerated — fit unchanged, snippet now carries sm::/cp:: handles. Two snippet tests. - _default_stability_starts docstring (and the pre-existing argparse help it was copied from) described the offsets backwards: the code sweeps {start-5y, start, +5y, +10y, +15y}, not {start-15y..+5y}. Docs now match the code; scripts/regression/README.md tightened too. - Unit tests for the _quad_spec_from_args error paths (mutually exclusive flags, unknown site, mask construction). - docs/regression/README.md index gains the Smith entry. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
All review findings addressed in 66bea7f:
Gate: ruff + format clean, 1100 passed (same 3 pre-existing local Deploy-ordering notes acknowledged — code PR first, then kayak_data #2, with 🤖 Generated with Claude Code |
|
Rereview of the updated PR head ( No blocking findings. The earlier merge-blocking issue is fixed: the generated Smith report now includes both custom Minor non-blocking note:
Verification:
Recommendation: ready to merge and deploy after, or even before, the minor README index cleanup. Keep the deployment order from the earlier review: code PR first, then the paired |
Re-review of 66bea7f — follow-ups verifiedCI still green (
One new nit (introduced by this commit)
Bonus cross-check — paired kayak_data PR (
|
…index entry The Smith bullet landed between the McKenzie entry's lead/lag link and its "(verdict: ...)" continuation, making the McKenzie verdict read as the Smith bullet's tail — contradicting "No lead/lag companion" two lines up. Move the verdict lines back under the McKenzie entry. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Index nit fixed in 7f6c700 — the McKenzie 🤖 Generated with Claude Code |
Summary
Adds a calc-gauge estimate for the retired Smith River near Gardiner gauge (USGS 14323100, daily record 1965-10 → 1973-06 only), plus the tool feature the fit needed and a docs sweep it surfaced. Pairs with kayak_data PR carrying the 4 metadata rows (calc_expression 17 / source 347 / gauge 224 / gauge_source) — merge this PR first (or in the same deploy window) so the published report artifacts and HTML-filter fix are live when the metadata lands.
1.
--quadratic-for(per-predictor quadratic) —6d8947b--quadraticsquares every predictor; the Smith fit needed Siuslaw² (significant, halves dry-season bias from +59% → +30% of mean flow) without SF Coquille² (block-boot CI straddles zero, and would contribute ~+1,175 cfs of phantom signal at the predictor's observed max). Spec normalized to a per-predictor mask internally; bool stays accepted everywhere (existing callers unchanged); report family label, Reproduce snippet, coefficient labels, stub, and fit JSON (quadratic_sitesadded) all follow the mask. 6 new tests.2. Stub flow: kayak_data CSV, not migrations —
b3509ccThe generated report and both regression READMEs still said "paste the SQL stub into a migration" — which now fails CI (
test_migrations_schema_only.py; calc_expression is a metadata table). The report now emits thecalc_expression.csvcolumn values (incl.provenance_slug); READMEs updated to the CSV +levels sync-metadataflow.3. The Smith fit —
961cfeddocs/regression/smith_14323100_from_siuslaw_sfcoquille.{md,svg,json}:Donor screening over every active gauge overlapping the target's record (block-bootstrap 95% CIs, monthly blocks): SF Coquille 14325000 is the only significant addition to the Siuslaw (+0.130, CI [+0.036, +0.225], VIF 4.3 — the one donor south of the Smith). Rejected with CIs straddling zero once the Siuslaw is in: Alsea 14306500, Siletz 14305500, Umpqua-Elkton 14321000, Long Tom-Noti 14166500, Coast Fork-London 14152500. Full screening history recorded in the kayak_data
calc_expression.note.4. Migration-doc sweep + HTML-filter fix —
7210253docs/migrations.md: five references still pointed metadata edits atdata/db/*.csv— repointed at thekayak_datarepo (METADATA_DIR);data/db/migrations/unchanged.deploy.py::_HTML_DROP_SECTIONSonly matched the old## SQL stubheading, so the renamed## calc_expression rowsection would have leaked maintainer scaffolding into the published report HTML. New heading added (old kept for pre-redesign reports) + filter test.Test plan
ruff check/ruff format --check/mypy(CI scope) cleantest_config.pyfailures are local-Mac python3.14 subprocess issues, present on cleanmain)--quadratic-forfit reproduces the hand-built 4-param model exactly (same coefficients/CIs); mutually-exclusive and unknown-site error paths exit 1levels sync-metadata(4 inserts) →levels calculator→ Smith = 25.0 cfs, hand-verified against the expression (Siuslaw 304, SFCoq 83.3 → 25.34 → 25)levels build+ local PHP serve: gauge page renders fact-box/sparkline/residuals; published report HTML drops the maintainer-only sections🤖 Generated with Claude Code