feat(forecasting): wire validated Almon-ADL into §9.6 consumers (#978) #1049

Merged
bot-backend merged 1 commit from feat/wire-978-almon-rate-regime into main 2026-06-04 10:33:32 +00:00
Collaborator

What

Wires the backtest-validated Almon distributed-lag estimator (#978) into the three §9.6 production consumers, replacing compute_rate_sensitivity (single best-lag).

Why: OOS backtest (PR #1048) showed the current best_lag engine is directionally noise out-of-sample (0.148 Source B EKB-wide, lag-unstable). The Almon estimator (compute_district_rate_regression) is strictly less noisy on every tier — 0.407 Source B (lag-stable), 0.60 survivorship-free Source A (clears coin-flip+margin).

How (minimal-diff, reversible)

  • New thin adapter regression.compute_rate_regime_sensitivity(db, *, spec, months_back) -> RateSensitivity — maps DistributedLagFit onto the existing RateSensitivity contract so the 3 consumers swap only the function name.
    • beta ← coef (long-run Σβ — the correct quantity for a sustained future-rate regime; bounded downstream by the existing [0.5,1.2] clamp).
    • x_pct ← x_pct, y_lag_months ← best_lag_months, phrase ← phrase.
    • confidence: source 'regression' → "medium" / 'fallback' → "low" (never "high").
    • spec.district is None → low/beta=None, does not call the regression (it requires a district str).
  • Repoint 3 consumers: demand_normalization (β discount), product_scoring (x_pct → mortgage_sensitivity), demand_supply_forecast (phrase only, β NOT in arithmetic).
  • compute_rate_sensitivity itself untouched (still used by the backtest + its own tests). normalization_factor/clamp/scoring math untouched.

Honest degradation

source='fallback' (gate n≥30 ∧ R²≥0.1 ∧ Σβ<0 not met) → confidence 'low' → demand_normalization goes neutral (norm=1.0, not applied) + product_scoring low. No noisy β applied where there is no signal.

Tests

  • Consumer tests repointed to the real Almon path (patch compute_district_rate_regression with a real DistributedLagFit; zero vacuous mocks of the now-uncalled function — verified). district=None uses assert_not_called().
  • New adapter unit tests + end-to-end fallback-degradation tests.
  • Deep-code-review: APPROVE — ran an empirical mutation test (broke the adapter 3 ways; the repointed tests caught each), proving they would FAIL on a broken adapter (not false-green).
  • Whole forecasting suite: 840 passed, ruff clean, no circular imports, rate_sensitivity.py byte-identical.

Note

This changes prod §9.6 forecast numbers (rate-regime demand discount + mortgage_sensitivity score + explain phrase). Post-deploy spot-check planned on a live segment with a known district.

#979 (seasonal deseasonalization) was validated NEGATIVE in the same backtest → kept advisory, not wired.

## What Wires the backtest-**validated** Almon distributed-lag estimator (#978) into the three §9.6 production consumers, replacing `compute_rate_sensitivity` (single best-lag). **Why:** OOS backtest (PR #1048) showed the current `best_lag` engine is directionally **noise** out-of-sample (0.148 Source B EKB-wide, lag-unstable). The Almon estimator (`compute_district_rate_regression`) is strictly less noisy on every tier — **0.407** Source B (lag-stable), **0.60** survivorship-free Source A (clears coin-flip+margin). ## How (minimal-diff, reversible) - New thin adapter `regression.compute_rate_regime_sensitivity(db, *, spec, months_back) -> RateSensitivity` — maps `DistributedLagFit` onto the existing `RateSensitivity` contract so the 3 consumers swap only the function name. - `beta ← coef` (long-run Σβ — the correct quantity for a *sustained* future-rate regime; bounded downstream by the existing `[0.5,1.2]` clamp). - `x_pct ← x_pct`, `y_lag_months ← best_lag_months`, `phrase ← phrase`. - `confidence`: source `'regression' → "medium"` / `'fallback' → "low"` (never "high"). - `spec.district is None` → low/`beta=None`, **does not call** the regression (it requires a district str). - Repoint 3 consumers: `demand_normalization` (β discount), `product_scoring` (x_pct → mortgage_sensitivity), `demand_supply_forecast` (phrase only, β NOT in arithmetic). - `compute_rate_sensitivity` itself **untouched** (still used by the backtest + its own tests). `normalization_factor`/clamp/scoring math untouched. ## Honest degradation `source='fallback'` (gate n≥30 ∧ R²≥0.1 ∧ Σβ<0 not met) → confidence `'low'` → demand_normalization goes neutral (norm=1.0, not applied) + product_scoring low. No noisy β applied where there is no signal. ## Tests - Consumer tests **repointed to the real Almon path** (patch `compute_district_rate_regression` with a real `DistributedLagFit`; zero vacuous mocks of the now-uncalled function — verified). `district=None` uses `assert_not_called()`. - New adapter unit tests + end-to-end fallback-degradation tests. - **Deep-code-review: APPROVE** — ran an empirical mutation test (broke the adapter 3 ways; the repointed tests caught each), proving they would FAIL on a broken adapter (not false-green). - Whole forecasting suite: **840 passed**, ruff clean, no circular imports, `rate_sensitivity.py` byte-identical. ## Note This **changes prod §9.6 forecast numbers** (rate-regime demand discount + mortgage_sensitivity score + explain phrase). Post-deploy spot-check planned on a live segment with a known district. #979 (seasonal deseasonalization) was validated NEGATIVE in the same backtest → kept advisory, not wired.
bot-backend added 1 commit 2026-06-04 10:33:22 +00:00
Backtest (OOS directional hit-rate): single-best-lag compute_rate_sensitivity
is directionally noise (0.148 Source B EKB-wide, lag-unstable); the Almon
distributed-lag estimator (compute_district_rate_regression) is strictly less
noisy on every tier (0.407 Source B / 0.60 survivorship-free Source A,
lag-stable). Add a thin adapter compute_rate_regime_sensitivity mapping
DistributedLagFit onto the existing RateSensitivity contract (beta=long-run
sum-beta, confidence regression->medium / fallback->low, district=None->low and
no call) and repoint the three consumers (demand_normalization, product_scoring,
demand_supply_forecast). Magnitude bounded by the existing [0.5,1.2] clamp.
Reversible; compute_rate_sensitivity kept for the backtest. Consumer tests
repointed to the real Almon path (mutation-verified genuine) + adapter unit
tests + end-to-end fallback degradation. Forecasting suite 840 passed; ruff clean.
bot-backend merged commit 9cffe3c9ec into main 2026-06-04 10:33:32 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
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: lekss361/gendesign#1049
No description provided.