Каждое проверено против кода или прод-данных, а не переписано по впечатлению.
1. macro_coefficient:99 — «СУММА backed-весов = 0.45». С #946 inflation стал
backed-каналом с весом 0.08: 0.18+0.12+0.10+0.05+0.08 = 0.53. Сумму не
обновили.
2. macro_series:305 и 3. sales_series:496 — оба обещали пустой результат «при
months_back < 0». Код клампит через max(0, months_back), поэтому сетка всегда
содержит текущий месяц. Проверено прогоном: months_back=-5 → 1 месяц.
Документировалось поведение, которого нет.
4. analytics_queries._velocity_baseline — «objective_corpus_room_month.district
matches domrf_kn_objects.district_name». Неверно, и соседний _elasticity_coef
описывает ту же колонку правильно (МИКРО-вокабуляр). Замер прода:
district (микро) Академический, ВИЗ, Юго-Западный, Уктус, Втузгородок…
district_name (админ) Академический, Чкаловский, Верх-Исетский, Ленинский…
Из 8 админ-имён в микро-колонке встречаются 4, и с меньшим объёмом (Ленинский
55 точек против 621 у Академического; Чкаловский и Верх-Исетский — ноль).
Вызывающий передаёт админ-имя. Резолв admin→micros тут НЕ делаю — это
отдельная задача; docstring лишь перестаёт утверждать обратное.
5. nspd_denorm.denorm_dump — «Caller отвечает за commit/close», при том что
функция сама вызывает db.commit() на 373. Вызывающий, понадеявшийся обернуть
это в свою транзакцию, получил бы уже зафиксированные строки.
6. nspd_client.search_by_quarter — смета «6/11/22 запроса, ~3.6с/~6.6с/~13с».
Фактически три из пяти core-слоёв и ВСЕ zouit/risk идут grid-walk'ом по 49
запросов: 150/395/934 запроса, ~90с/~237с/~560с. Занижение в 25-42 раза, и
это не безобидно: по такой оценке слои включают не задумываясь, а объём
запросов здесь — прямой фактор WAF-риска (ср. #2956, где НСПД сейчас отдаёт
403 на IP VPS).
Два из шести чисел проверяемы автоматически, и на них поставлен гейт: сумма
backed-весов сверяется с константами, смета запросов — с _GRID_WALK_LAYERS.
Мутационно проверен: вернуть 0.45 → красный, изменить вес канала не тронув
комментарий → красный, вернуть 6/11/22 → красный, контроль → 3 passed rc=0.
Плюс контроль на сам гейт: если _GRID_WALK_LAYERS опустеет, расчёт совпал бы с
любой мелкой цифрой тавтологически.
Прогоны: tests/services — 3116 passed rc=0.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
fedstat ИПЦ is reCAPTCHA-blocked; CBR publishes inflation openly. Add
fetch_inflation + parse_inflation_xlsx (CBR UniDbQuery DownloadExcel/132934,
monthly % г/г, region=rf, source=cbr) to cbr_macro.py; upsert
indicator_type=inflation_yoy via the existing cbr_macro_sync task (per-series
guard, SAVEPOINT-per-row, CAST not ::, ON CONFLICT on the PK).
Surface inflation_yoy in MonthlyMacro (frozen, carry-forward) and ACTIVATE the
reserved §9.5 inflation channel (macro_coefficient f_inflation: level-vs-4%-target
nudge, non-positive to avoid double-counting f_rate, excluded from
_RATE_DRIVEN_FACTORS). Channel was DEGRADED (no data) -> now BACKED + consumed;
_CONF_HIGH_MIN_BACKED 4->5. Deterministic (§16/§26); renorm claims the reserved
0.08 slice as designed. Live-verified (2026-04 5.58%); 194 macro + 902 forecasting
tests green. No migration, no new deps.
Refs #946.
Cold §22 forecast measured ~215-233s on prod: §9.x layers re-execute the same
horizon/segment-invariant DB loads with identical args hundreds of times per
report (profiled: get_competitors x69, market_metrics x124, get_monthly_macro
x290). Add a per-report ContextVar cache (forecast_cache(), opened once in the
orchestrator) + @cached(key_builder) on the expensive §9.x loaders so each
unique load runs ONCE and reuses the same frozen, read-only instance.
Output is byte-identical (memoized producers are frozen dataclasses / read-only
Pydantic, callers never mutate; cache is per-report, discarded on exit; no-op
outside the report build). No concurrency, no signature changes.
- forecast_request_cache.py: ContextVar cache + cached() decorator (no-op
outside context, reentrant, _MISS sentinel for cached None)
- @cached on competitors/future_supply/market_metrics/macro_series/
sales_series/macro_coefficient/demand_normalization/regression loaders
- orchestrator: wrap build_site_finder_report in forecast_cache()
- 58 tests: key discrimination (call-counting regression guard), no-op-outside,
per-report isolation, reentrancy, frozen-producer canary, amplification proof
(real get_monthly_macro xN->1)
code-reviewer APPROVE (keys correct, mutation-safe, output identical). 1265
forecast/cache tests green. No new deps. Refs #1129.