Commit graph

24 commits

Author SHA1 Message Date
a0e61a38b4 feat(forecasting): §9.x→§22 orchestrator + fix supply-side district resolution (3a)
All checks were successful
Deploy / changes (push) Successful in 5s
Deploy / build-frontend (push) Has been skipped
Deploy / build-backend (push) Successful in 1m37s
Deploy / build-worker (push) Successful in 2m25s
Deploy / deploy (push) Successful in 1m7s
Add build_site_finder_report (orchestrator.py): computes the §9.x layers (market
metrics, supply layers, future-supply pressure, demand/supply forecast, scenarios,
score card, special indices, recommendation overlay) with their heterogeneous
signatures and feeds the PURE assemble_report → §22 SiteFinderReport. Default segment
= modal competitor class; each §9.x call _safe_call-wrapped (graceful). Standalone —
no endpoint/Celery/persistence (that is 3b).

Prod ground-truth of the orchestrator surfaced a false-BUY bug: future_supply
(compute_future_supply_pressure) read the mixed-vocab persisted view
v_supply_layers_latest by a SCALAR admin district_name, missing all Layer-1
micro-keyed rows → admin parcel (Кировский) got supply=0 → false +1.0 deficit →
'Строить: недонасыщен' headline despite ~45k competing units. Fix: resolve
admin→micros, filter district_name = ANY(CAST(:names AS text[])) where names =
micros (L1) + admin (L2/L3), with :has_district EKB-wide guard (extends PR #1054's
resolver to the persisted-view path it missed). future_supply is the only
v_supply_layers_latest consumer on the forecast path (verified).

Prod after: Кировский supply 0→~42953, deficit +1.0→−1.0 (honest oversupply),
MOI 0→116.6, false-BUY headline gone, overall 0.734→0.42. 80 module tests pass
(signature-trap + resolver-regression guards genuine); ruff clean. Refs #961 #969.
2026-06-05 08:21:04 +05:00
01a74ade7a feat(forecasting): add months-of-inventory (MOI) to §9.8 demand-supply forecast
All checks were successful
Deploy / changes (push) Successful in 6s
Deploy / build-frontend (push) Has been skipped
Deploy / build-backend (push) Successful in 1m21s
Deploy / build-worker (push) Successful in 2m17s
Deploy / deploy (push) Successful in 1m12s
deficit_index pins to -1.0 for every ЕКБ segment (12mo demand flow vs multi-year
supply stock → log-ratio clamps) → zero discriminating power, though the oversupply
is partly real. Add MOI (gross competing supply / demand_per_mo), the real-estate
absorption standard, as an additive non-saturating companion that DISCRIMINATES
(Уралмаш 42mo … Чермет 109mo) where deficit cannot. deficit_index math kept exactly
as-is (honest absolute: -1 = genuinely oversupplied); docstrings clarify -1 is common
and MOI is the discriminating companion (no recalibration). _gross_supply extract-method
(single source of truth; _project_supply behavior byte-identical, code-review-verified).
Surface MOI in §22 future_market (passthrough) + exec_summary key_numbers/verdict.
Guards: no demand → None, no supply → 0. Prod: MOI varies 42→109mo, deficit stays -1.
Discrimination test pins MOI separating two segments both at deficit -1. Refs #952.
2026-06-05 07:36:59 +05:00
681a922d99 feat(forecast): resolve admin district -> micro set in §9.x market/supply/sales filters
All checks were successful
Deploy / changes (push) Successful in 6s
Deploy / build-frontend (push) Has been skipped
Deploy / build-backend (push) Successful in 1m29s
Deploy / build-worker (push) Successful in 2m32s
Deploy / deploy (push) Successful in 1m7s
/analyze passes the official ЕКБ admin district (ekb_districts polygon, e.g.
'Кировский'), but objective_lots/corpus_room_month store informal micro-districts
('Втузгородок','ЖБИ') -> admin name matched 0 rows -> silent empty forecast.

Add resolve_objective_districts() (site_finder/district_resolver.py) mapping an
admin name to its clean micros via ekb_district_alias (note IS NULL), with
None -> EKB-wide fallback and raw-micro pass-through. Wire into the objective_lots
district filters of market_metrics (§9.2 stock+sales), supply_layers L1 (§9.3),
and sales_series Sources A+B (crm shares the micro vocab, prod-verified),
switching the scalar filter to psycopg3-safe = ANY(CAST(:districts AS text[])).
supply_layers L2/L3 keep the admin name (domrf_kn_objects.district_name is admin vocab).

Prod: Кировский/Ленинский/Орджоникидзевский obj_count 0 -> 32/64/31.
Tests mutation-verified non-vacuous. 192 module tests pass; ruff clean. Refs #969 #949.
2026-06-05 07:03:37 +05:00
4844847cae fix(affordability): calibrate key_rate->market mortgage spread to 4.5pp
All checks were successful
Deploy / changes (push) Successful in 6s
Deploy / build-frontend (push) Has been skipped
Deploy / build-backend (push) Successful in 1m29s
Deploy / build-worker (push) Successful in 2m40s
Deploy / deploy (push) Successful in 1m13s
_KEY_RATE_MARKET_SPREAD_PP was a 0.0 placeholder, so §7.9 affordability payments
used the bare CBR key_rate (~14.5%), understating borrower cost and OVERSTATING
affordability. Calibrate to 4.5pp from the prod anchor (macro_indicator
mortgage_rate_primary_domrf 19.125% @ 2026-04-19 vs key_rate ~14.5% -> observed
~4.6pp, rounded conservatively; inside the typical RF 3-5pp band), so
rate_used = key_rate + 4.5 ~= 19% matches the directly-observed market primary
mortgage rate. Makes affordability LESS optimistic / more accurate. Docs + tests
updated to the symbolic spread (new regression anchor pins spread==4.5 and
key_rate 14.5 -> ~19.0); rate_kind/graceful-fallback semantics unchanged.
Forecasting suite 841 passed; ruff clean.
2026-06-04 17:21:29 +05:00
9cffe3c9ec feat(forecasting): wire Almon-ADL rate estimator into §9.6 consumers (#978)
All checks were successful
Deploy / build-frontend (push) Has been skipped
Deploy / build-backend (push) Successful in 1m22s
Deploy / build-worker (push) Successful in 2m37s
Deploy / deploy (push) Successful in 1m9s
Deploy / changes (push) Successful in 5s
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.
2026-06-04 10:33:31 +00:00
b4eb6a8ad5 fix(forecasting): honest USP gate (di>0) + unit-explicit coverage fraction
All checks were successful
Deploy / changes (push) Successful in 6s
Deploy / build-frontend (push) Has been skipped
Deploy / build-backend (push) Successful in 2m16s
Deploy / build-worker (push) Successful in 2m31s
Deploy / deploy (push) Successful in 1m3s
Found by read-only services audit.
- recommendation._usp_from_deficits: skip di<=0 so «стройте его» is never emitted
  for OVERSUPPLIED formats; all-surplus top-K → [] (no white-space niches).
  Aligns with product_scoring._count_positive_usp (di>0). Was: «Дефицит формата
  X — стройте его» for a surplus format, reaching PDF/Excel USP-ниши.
- report_assembler._domrf_coverage: drop ambiguous >1.0 percent-guess; normalize
  per-branch (analyze pct /100, supply_layers fraction as-is). Sub-1% coverage
  (0.8%) no longer read as 80% → no inflated confidence in the near-zero-coverage
  case §15 flags. tests for both + end-to-end no-inflation. 241+148 pass.
2026-06-04 13:25:25 +05:00
9a845646fd fix(#978): train-only detrend in rate backtest + Almon distributed-lag regression
All checks were successful
Deploy / deploy (push) Successful in 1m9s
Deploy / changes (push) Successful in 5s
Deploy / build-frontend (push) Has been skipped
Deploy / build-backend (push) Successful in 1m29s
Deploy / build-worker (push) Successful in 2m20s
REOPENED 951-B §9.6.
PART A: fix look-ahead leakage in backtest_rate_sensitivity --detrend. The
ln(units) trend was fit over train+test then split, so test data shaped the
detrend and inflated the OOS hit-rate. _detrend_log now takes fit_n; backtest_tier
fits the trend on TRAIN months only (same split evaluate_oos uses) and projects
(a,b) point-in-time onto test. Default fit_n=None preserves prior behaviour.

PART B (DoD): new app/services/forecasting/regression.py — Almon polynomial
distributed-lag (deg 2) of Δln(district demand) on Δkey_rate lags 0..6 via
OLS-on-Almon-regressors (numpy lstsq) + per-lag reconstruction + manual
Newey-West HAC SEs (NO statsmodels). Output {best_lag_months, coef=long-run
multiplier, x_pct, r2, n, per_lag_coef, hac_se,...}; gate mirrors _elasticity_coef
(n<30 OR R²<0.1 OR Σβ≥0 → fallback); §9.6 phrase from the lag shape. ADVISORY,
shipped standalone (integration point documented), NOT wired — protects the live
compute_rate_sensitivity consumers.

125+31 tests (synthetic known-lag recovery, HAC computed/differs-from-OLS,
fallback gating, no-leakage detrend). ruff clean. Refs #978
2026-06-04 11:39:32 +05:00
3ecfc2d7dc feat(forecasting): seasonal (month-of-year) demand normalization (#979)
All checks were successful
Deploy / changes (push) Successful in 6s
Deploy / build-frontend (push) Has been skipped
Deploy / build-worker (push) Successful in 2m16s
Deploy / build-backend (push) Successful in 1m28s
Deploy / deploy (push) Successful in 1m6s
REOPENED — normalize.py was never created; only rate-regime discount existed.
New backend/app/services/forecasting/normalize.py with normalize_demand(series):
multiplicative month-of-year deseasonalization of the raw monthly demand
SalesSeries (§9.4). Pure/deterministic; min-data guard (<2 full years / empty
month / overall_mean<=0 → factor 1.0, no divide-by-zero, no thin-data noise).
Exposes seasonal factors for explainability. Synthetic unit test: seasonality
removed (month means equalised), flat unchanged, thin/empty/all-zero safe.

DoD (module + doc + test) MET. Production wiring into
rate_sensitivity._align_sales_deltas DEFERRED (documented TODO): deseasonalizing
the short rate-driven series perturbs the recovered β/lag on current data —
needs a points-per-month gate / joint seasonal+rate estimation + backtest before
wiring. Forecast stack is advisory regardless. Refs #979
2026-06-04 11:19:50 +05:00
9ff80f3ac0 fix(forecasting): #980 strongest deficit→deficit_index +1.0; #981 MAI uses CBR key rate
All checks were successful
Deploy / changes (push) Successful in 6s
Deploy / deploy (push) Successful in 1m7s
Deploy / build-frontend (push) Has been skipped
Deploy / build-backend (push) Successful in 1m39s
Deploy / build-worker (push) Successful in 2m39s
REOPENED #980: when effective competing supply is exhausted under positive demand
(projected_supply<=0, demand>0), deficit_index now caps to +1.0 (peak of [-1,+1])
instead of None. balance_ratio stays None (demand/0 undefined), but the strongest
build signal no longer reads downstream as thin data (market_fit fell to 0.5,
what_to_build dropped the cell). No-signal (supply<=0 AND demand<=0) stays None.

REOPENED #981: MAI now uses CBR key rate (macro_indicator key_rate/rf via
get_monthly_macro) as the market borrowing-cost proxy (~16-21%) instead of the
subsidized weighted rate (~7.83%), per §7.9 DoD. rate_kind='key_rate_proxy'.
If key_rate absent → rate_kind='market_unavailable' (no silent subsidy fallback).
Income (#946) still missing → payment_to_income None, confidence low.

778 forecasting tests green. Refs #980 #981
2026-06-04 11:10:26 +05:00
fc312fe606 feat(forecasting): §13 report assembler (#988, 955-A2) (#1021)
All checks were successful
Deploy / build-backend (push) Successful in 1m22s
Deploy / build-worker (push) Successful in 2m36s
Deploy / deploy (push) Successful in 1m13s
Deploy / changes (push) Successful in 5s
Deploy / build-frontend (push) Has been skipped
2026-06-03 08:51:23 +00:00
47fe5a55eb feat(forecasting): §15 confidence engine v2 (#990, 955-A4) (#1020)
All checks were successful
Deploy / changes (push) Successful in 6s
Deploy / build-frontend (push) Has been skipped
Deploy / build-backend (push) Successful in 1m24s
Deploy / build-worker (push) Successful in 2m58s
Deploy / deploy (push) Successful in 1m16s
2026-06-03 08:41:07 +00:00
75c9b34987 feat(forecasting): §13 SiteFinderReport object (#987, 955-A1) (#1019)
All checks were successful
Deploy / changes (push) Successful in 6s
Deploy / build-frontend (push) Has been skipped
Deploy / build-backend (push) Successful in 1m24s
Deploy / build-worker (push) Successful in 2m43s
Deploy / deploy (push) Successful in 1m14s
2026-06-03 08:33:01 +00:00
008f9d9ed1 feat(forecasting): §25 six special indices (#986, 954-C) (#1018)
All checks were successful
Deploy / changes (push) Successful in 5s
Deploy / build-frontend (push) Has been skipped
Deploy / build-backend (push) Successful in 1m28s
Deploy / build-worker (push) Successful in 2m31s
Deploy / deploy (push) Successful in 1m8s
2026-06-03 08:24:08 +00:00
98dea0315a feat(forecasting): §14.2 product scoring card (#985, 954-B) (#1017)
All checks were successful
Deploy / changes (push) Successful in 6s
Deploy / build-backend (push) Successful in 1m33s
Deploy / build-worker (push) Successful in 2m30s
Deploy / deploy (push) Successful in 1m10s
Deploy / build-frontend (push) Has been skipped
2026-06-03 08:11:54 +00:00
8057468c13 feat(forecasting): §11 macro-scenarios (#984, 954-A) (#1016)
All checks were successful
Deploy / changes (push) Successful in 5s
Deploy / build-frontend (push) Has been skipped
Deploy / build-backend (push) Successful in 2m29s
Deploy / build-worker (push) Successful in 3m37s
Deploy / deploy (push) Successful in 1m21s
2026-06-03 07:59:22 +00:00
691ccef4b7 feat(forecasting): class/commercial/USP §10.2/10.4/10.5 + §16 (#983, 953-B) (#1015)
All checks were successful
Deploy / changes (push) Successful in 6s
Deploy / build-frontend (push) Has been skipped
Deploy / build-backend (push) Successful in 1m32s
Deploy / build-worker (push) Successful in 2m32s
Deploy / deploy (push) Successful in 1m10s
2026-06-03 07:52:24 +00:00
72e9b24f2c feat(recommend): horizon-aware recommend_mix opt-in overlay (#982, 953-A) (#1014)
All checks were successful
Deploy / changes (push) Successful in 5s
Deploy / build-frontend (push) Has been skipped
Deploy / build-backend (push) Successful in 1m24s
Deploy / build-worker (push) Successful in 2m33s
Deploy / deploy (push) Successful in 1m4s
2026-06-03 07:40:33 +00:00
489e380f1e feat(forecasting): what-to-build ranker + MAI proxy (#981, 952-B) (#1013)
All checks were successful
Deploy / build-backend (push) Successful in 1m31s
Deploy / build-worker (push) Successful in 2m43s
Deploy / deploy (push) Successful in 1m9s
Deploy / changes (push) Successful in 5s
Deploy / build-frontend (push) Has been skipped
2026-06-03 07:07:17 +00:00
70ffa399fc feat(forecasting): demand-supply forecast engine (#980, 952-A) (#1012)
All checks were successful
Deploy / changes (push) Successful in 5s
Deploy / build-frontend (push) Has been skipped
Deploy / build-backend (push) Successful in 1m24s
Deploy / build-worker (push) Successful in 2m28s
Deploy / deploy (push) Successful in 1m10s
2026-06-03 06:56:33 +00:00
9758e21cbd feat(forecasting): §9.4 demand-normalization coefficient (#951f, advisory) (#1011)
All checks were successful
Deploy / changes (push) Successful in 5s
Deploy / build-frontend (push) Has been skipped
Deploy / build-backend (push) Successful in 1m22s
Deploy / build-worker (push) Successful in 2m34s
Deploy / deploy (push) Successful in 1m5s
2026-06-03 06:28:14 +00:00
69ad6c87fc feat(forecasting): §9.5 macro coefficient (#951e, advisory) (#1010)
All checks were successful
Deploy / changes (push) Successful in 5s
Deploy / build-frontend (push) Has been skipped
Deploy / build-backend (push) Successful in 1m22s
Deploy / build-worker (push) Successful in 3m27s
Deploy / deploy (push) Successful in 1m11s
2026-06-03 06:20:04 +00:00
f41e2536d8 feat(forecasting): §9.6 key-rate sensitivity module (#951d, advisory) (#1009)
All checks were successful
Deploy / changes (push) Successful in 5s
Deploy / build-frontend (push) Has been skipped
Deploy / build-backend (push) Successful in 1m30s
Deploy / build-worker (push) Successful in 2m39s
Deploy / deploy (push) Successful in 1m13s
2026-06-03 06:06:16 +00:00
c7bfc9e22a feat(forecasting): monthly sales series builder for §9.6 (#951c) (#1008)
All checks were successful
Deploy / changes (push) Successful in 5s
Deploy / build-frontend (push) Has been skipped
Deploy / build-backend (push) Successful in 1m20s
Deploy / build-worker (push) Successful in 2m17s
Deploy / deploy (push) Successful in 1m6s
2026-06-03 05:52:33 +00:00
ace3b99508 feat(forecasting): monthly macro series + regime classifier (#951b) (#1007)
All checks were successful
Deploy / changes (push) Successful in 5s
Deploy / build-frontend (push) Has been skipped
Deploy / build-backend (push) Successful in 1m23s
Deploy / build-worker (push) Successful in 2m43s
Deploy / deploy (push) Successful in 1m5s
2026-06-03 05:37:43 +00:00