Commit graph

5 commits

Author SHA1 Message Date
e7c79c9646 docs(ptica): шесть мест, где документация расходилась с кодом (#2464)
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI / changes (pull_request) Successful in 10s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Successful in 1m53s
CI / backend-tests (pull_request) Successful in 17m21s
Каждое проверено против кода или прод-данных, а не переписано по впечатлению.

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>
2026-08-20 15:02:17 +05:00
82fdabccba fix(forecasting): SAVEPOINT-isolate §22 db.execute failures + close RELEASE-trap (#2464) (#2466)
All checks were successful
Deploy / changes (push) Successful in 9s
Deploy / build-frontend (push) Has been skipped
Deploy / build-backend (push) Successful in 2m35s
Deploy / build-worker (push) Successful in 3m1s
Deploy / deploy (push) Successful in 1m34s
2026-07-08 05:41:35 +00:00
8206a0b067 perf(forecast): per-request memoization cache for §22 cold build (#1129)
All checks were successful
Deploy / changes (push) Successful in 6s
Deploy / build-backend (push) Successful in 1m49s
Deploy / build-frontend (push) Has been skipped
Deploy / build-worker (push) Successful in 2m45s
Deploy / deploy (push) Successful in 1m10s
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.
2026-06-08 05:26:27 +00: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
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