Commit graph

1700 commits

Author SHA1 Message Date
c1cdf3969c fix(scrapers): ban/stuck-ротация для cian+yandex (provider-aware _rotate_proxy_ip)
All checks were successful
CI / changes (pull_request) Successful in 6s
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
2026-06-26 19:10:26 +03:00
197068c515 fix(scrapers): save_listings — UPDATE by (source,source_id) при dedup_hash-дрейфе (avito_full_load краш) 2026-06-26 19:10:12 +03:00
e03c810306 feat(tradein-imv): route IMV backfill через BrowserFetcher.fetch_json за dormant-флагом (#915 Stage 3) (#1916)
All checks were successful
Deploy Trade-In / changes (push) Successful in 9s
Deploy Trade-In / build-frontend (push) Has been skipped
Deploy Trade-In / build-browser (push) Successful in 27s
Deploy Trade-In / test (push) Successful in 1m23s
Deploy Trade-In / build-backend (push) Successful in 51s
Deploy Trade-In / deploy (push) Successful in 46s
2026-06-26 09:54:28 +00:00
d479edeb3f feat(tradein-browser): additive POST /fetch-json + BrowserFetcher.fetch_json (#915 Stage 2) (#1915)
All checks were successful
Deploy Trade-In / changes (push) Successful in 9s
Deploy Trade-In / build-frontend (push) Has been skipped
Deploy Trade-In / test (push) Successful in 1m25s
Deploy Trade-In / build-backend (push) Successful in 52s
Deploy Trade-In / build-browser (push) Successful in 2m53s
Deploy Trade-In / deploy (push) Successful in 1m30s
2026-06-26 09:30:35 +00:00
e0cd378d19 fix(migration-133): dedup via cascade-delete, not child re-point (prod hotfix)
All checks were successful
Deploy Trade-In / test (push) Successful in 1m21s
Deploy Trade-In / build-backend (push) Successful in 24s
Deploy Trade-In / deploy (push) Successful in 49s
Deploy Trade-In / changes (push) Successful in 9s
Deploy Trade-In / build-frontend (push) Has been skipped
Deploy Trade-In / build-browser (push) Has been skipped
Migration 133 failed on prod (offer_price_history UNIQUE (listing_id,change_time) + multi-loser collisions). Drop data-child re-points, rely on FK ON DELETE CASCADE; keep only merged_into. Dry-run on prod: 9840 groups, 9889 losers deleted, clean. Refs #1773
2026-06-26 08:16:27 +00:00
1d5b081cb4 fix(cian): recover new-build house via cadastral anchor + listings (source,source_id) dedup (#1773) (#1912)
Some checks failed
Deploy Trade-In / changes (push) Successful in 13s
Deploy Trade-In / build-frontend (push) Has been skipped
Deploy Trade-In / build-browser (push) Has been skipped
Deploy Trade-In / test (push) Successful in 1m28s
Deploy Trade-In / deploy (push) Failing after 46s
Deploy Trade-In / build-backend (push) Successful in 50s
cian new-build cadastral anchor + listings (source,source_id) dedup (#1773): migration 133 (deep-reviewed data-loss-safe). Refs #1773
2026-06-26 07:45:22 +00:00
3f1925052b feat(scheduler): wire house_imv_backfill as scheduled source (#854) (#1910)
Some checks failed
Deploy Trade-In / build-backend (push) Blocked by required conditions
Deploy Trade-In / build-frontend (push) Blocked by required conditions
Deploy Trade-In / build-browser (push) Blocked by required conditions
Deploy Trade-In / deploy (push) Blocked by required conditions
Deploy Trade-In / changes (push) Successful in 11s
Deploy Trade-In / test (push) Has been cancelled
wire house_imv_backfill as scheduled source (#854): trigger fn + seed migration 132. Refs #854
2026-06-26 07:45:08 +00:00
f091554942 fix(scrapers): named BrowserSession concurrency const, fix Semaphore(3) doc drift (#1347) (#1913)
All checks were successful
Deploy / build-backend (push) Successful in 2m1s
Deploy / changes (push) Successful in 7s
Deploy / build-frontend (push) Has been skipped
Deploy / build-worker (push) Successful in 3m13s
Deploy / deploy (push) Successful in 1m33s
named BrowserSession concurrency const + fix Semaphore(3) doc drift (#1347): keeps 8 (issue-sanctioned doc option). Refs #1347
2026-06-26 07:44:29 +00:00
9c52e0b29f feat(etl): housing-class normalization fallback via yandex_realty trigram match (#38) (#1911)
Some checks failed
Deploy / build-worker (push) Blocked by required conditions
Deploy / build-frontend (push) Blocked by required conditions
Deploy / deploy (push) Blocked by required conditions
Deploy / build-backend (push) Blocked by required conditions
Deploy / changes (push) Has been cancelled
housing-class normalization fallback via yandex_realty trigram (#38): migration 169 + ETL + COALESCE consumers. Refs #38
2026-06-26 07:44:25 +00:00
5ed88c41a4 fix(avito): post-merge polish — unify fetch_mode guard + empty-render guard (#1811) (#1909)
Some checks failed
Deploy Trade-In / build-backend (push) Blocked by required conditions
Deploy Trade-In / deploy (push) Blocked by required conditions
Deploy Trade-In / changes (push) Successful in 10s
Deploy Trade-In / build-frontend (push) Has been skipped
Deploy Trade-In / build-browser (push) Has been skipped
Deploy Trade-In / test (push) Has been cancelled
avito SERP browser post-merge polish (#1811): unify fetch_mode guard, empty-render guard, debug logging. Refs #1811
2026-06-26 07:44:22 +00:00
67d41df880 fix(freshness): repoint nspd source to live nspd_quarter_dumps (data-table mode)
All checks were successful
Deploy / changes (push) Successful in 7s
Deploy / build-frontend (push) Has been skipped
Deploy / build-backend (push) Successful in 4m34s
Deploy / build-worker (push) Successful in 5m23s
Deploy / deploy (push) Successful in 1m59s
nspd freshness watched a defunct ledger (nspd_scrape_runs, WAF-banned manual April runs) → false "no successful runs". Add data-table mode (timestamp_col) + repoint nspd to live nspd_quarter_dumps.fetched_at_utc (fresh<14/stale<30). Run-ledger sources untouched. Refs #73
2026-06-26 06:51:00 +00:00
2304e09e37 fix(freshness): calibrate kn staleness thresholds to weekly cadence
All checks were successful
Deploy / changes (push) Successful in 6s
Deploy / build-frontend (push) Has been skipped
Deploy / build-backend (push) Successful in 2m41s
Deploy / build-worker (push) Successful in 3m18s
Deploy / deploy (push) Successful in 1m28s
kn (DOM.РФ ЖК) runs weekly (Mon cron) but thresholds were 2/5 → false "stale" every Wed-Sun. Recalibrate to 8/14 (weekly, mirrors objective) + regression test. Surfaced by the freshness monitor's first prod run. Refs #73
2026-06-26 06:33:29 +00:00
66da75681f feat(financial/dcf): dormant separate-index inflation capability (default OFF)
All checks were successful
Deploy / changes (push) Successful in 7s
Deploy / build-frontend (push) Has been skipped
Deploy / build-backend (push) Successful in 1m45s
Deploy / build-worker (push) Successful in 2m44s
Deploy / deploy (push) Successful in 1m22s
smr/sale_inflation_annual in _build_monthly_cashflow, gated by module constants =0.0 → byte-identical zero-diff (deep-review verified IEEE-754 identity + all 57 prior tests unchanged). Land not escalated; marketing/VAT/tax retimed on escalated revenue (total preserved). Capability wired+tested, OFF until calibrated indices + invariant reframe. No schema/codegen. Refs #1881
2026-06-25 12:12:02 +00:00
b82963d12e feat(financial): ground-floor нежилое (office/commercial) tranche
All checks were successful
Deploy / changes (push) Successful in 6s
Deploy / build-backend (push) Successful in 1m50s
Deploy / build-worker (push) Successful in 3m0s
Deploy / build-frontend (push) Successful in 3m18s
Deploy / deploy (push) Successful in 1m22s
Carve a documented office share (comfort/business 5%, econom 0) out of GFA before residential — fixes prior overstatement (all non-parking GFA = residential). total_floor_area unchanged → no double-count; office is additive revenue (+15% premium), нежилое → VAT-able (vat base = parking_va + office_va). Σ invariant holds. Additive schema + api-types regen + PDF/concept display. Deep-review . Refs #1881
2026-06-25 11:40:01 +00:00
55811ff7cc feat(workers): data-freshness monitor (Celery beat → GlitchTip)
All checks were successful
Deploy / changes (push) Successful in 15s
Deploy / build-backend (push) Successful in 1m58s
Deploy / build-worker (push) Successful in 3m9s
Deploy / build-frontend (push) Successful in 3m19s
Deploy / deploy (push) Successful in 1m24s
Extract compute_freshness(db) from the /admin/scrape/freshness handler (endpoint unchanged) + daily beat task scrape_freshness_check that alerts via sentry_sdk.capture_message when a source is stale/failed (error on critical-failed, else warning). Registered in celery include + beat (09:00 MSK). Refs #73
2026-06-25 11:29:34 +00:00
f249212695 fix(site-finder): isolate zone-regulation cache write off the request session (#1850 item 4)
All checks were successful
Deploy / build-worker (push) Successful in 2m57s
Deploy / changes (push) Successful in 7s
Deploy / build-frontend (push) Has been skipped
Deploy / deploy (push) Successful in 1m23s
Deploy / build-backend (push) Successful in 1m48s
Cache-miss upsert now runs in a fresh SessionLocal() instead of committing the shared /analyze transaction mid-request. Read-back via shared session unchanged. Refs #1850
2026-06-25 11:16:26 +00:00
39f099bc17 chore(mocks): regenerate parcel-analyze financial_estimate (consistent with current backend)
All checks were successful
Deploy / build-frontend (push) Successful in 3m29s
Deploy / deploy (push) Successful in 1m11s
Deploy / build-backend (push) Has been skipped
Deploy / build-worker (push) Has been skipped
Deploy / changes (push) Successful in 7s
Regenerate the drifted dev fixture financial_estimate via compute_financial() from its own teap_synth inputs — parking comfort-priced (#1893), VAT parking-only (#1898), levered_irr refreshed (#1900). Dev-only. Refs #1881
2026-06-25 11:02:36 +00:00
d2773e0c91 fix(site-finder): optimize zone-regulation resolve in /analyze (#1850)
All checks were successful
Deploy / build-backend (push) Successful in 1m40s
Deploy / deploy (push) Successful in 1m26s
Deploy / changes (push) Successful in 7s
Deploy / build-frontend (push) Has been skipped
Deploy / build-worker (push) Successful in 3m9s
Memoize coord→zone_index WFS lookup (thread-safe bounded LRU, negative caching), resolve once and thread into IRD block (no double resolve when both flags on), unify timeout into settings.geoportal_timeout_s. Lock guards the threadpool-shared memo (WFS call outside lock). Documented the cache-miss db.commit() smell (separate task). Deep-review .

Closes #1850
2026-06-25 09:12:41 +00:00
d589ddf6c4 feat(financial): levered (equity) IRR — capstone финмодели #1881
All checks were successful
Deploy / build-worker (push) Successful in 3m8s
Deploy / deploy (push) Successful in 1m30s
Deploy / build-frontend (push) Successful in 3m25s
Deploy / changes (push) Successful in 6s
Deploy / build-backend (push) Successful in 1m53s
Levered (equity) IRR from the LTC debt-schedule equity cashflow, mirroring the unlevered IRR + roi-proxy fallback. Honest negative-leverage (can be below project IRR). Σ equity_cf == net_profit_after_financing invariant holds. Unlevered untouched. Additive schema fields (api-types regenerated). Cockpit drawer + concept display. Deep-review .

Refs #1881
2026-06-25 09:06:46 +00:00
dc659da655 feat(site-finder): geo-radius market-price calibration in /analyze (#1881 follow-up)
All checks were successful
Deploy / changes (push) Successful in 7s
Deploy / build-backend (push) Successful in 2m1s
Deploy / build-worker (push) Successful in 3m12s
Deploy / build-frontend (push) Successful in 3m34s
Deploy / deploy (push) Successful in 1m24s
New price tier objective_geo_radius: ST_DWithin median of Objective new-build prices (objective_lots ⋈ complexes) within 3km of parcel centroid, between quarter-MV and district_reference. Closes the name-match gap (5 of 9 EKB districts had no Objective name-match). data/sql/168 functional GIST index (prod EXPLAIN 114ms→28ms). Degenerate-centroid guard + honest RU price_source captions. Deep-review .

Refs #1881
2026-06-25 07:59:56 +00:00
3073651812 fix(generative/financial): VAT only on parking, residential ДДУ exempt (ст. 149 НК)
All checks were successful
Deploy / changes (push) Successful in 7s
Deploy / build-backend (push) Successful in 1m48s
Deploy / build-frontend (push) Successful in 3m24s
Deploy / deploy (push) Successful in 1m30s
Deploy / build-worker (push) Successful in 3m40s
VAT charged on full gross margin → only on parking value-added; residential (реализация жилья + услуги застройщика по ДДУ) exempt per пп.22/23.1 п.3 ст. 149 НК; input VAT embedded in gross СМР. Σ cashflow == net_profit invariant preserved. PDF + concept-UI caveats synced. +3 tests, deep-review .

Refs #1881
2026-06-25 07:22:58 +00:00
89509824c2 chore(agents): bump all worker + analyst agents to opus 4.8
All worker + analyst project agents → model: opus (Opus 4.8); reviewers already opus. Guard files (_autonomous_pickup, auto-code-reviewer) untouched. Per Anton request.
2026-06-25 06:54:14 +00:00
4eaf184d0e fix(avito): graceful sidecar HTTP 500/503 in browser SERP fetch (#1790)
All checks were successful
Deploy Trade-In / build-frontend (push) Has been skipped
Deploy Trade-In / build-browser (push) Has been skipped
Deploy Trade-In / deploy (push) Successful in 58s
Deploy Trade-In / changes (push) Successful in 10s
Deploy Trade-In / test (push) Successful in 1m25s
Deploy Trade-In / build-backend (push) Successful in 2m0s
Classify tradein-browser sidecar HTTP 500/503 in _fetch_serp_html_browser and route into existing rotation/retry machinery: soft-ban → IP rotation, transient → bounded backoff, both exhausted → AvitoRateLimitedError (graceful mark_banned, partial preserved) instead of mark_failed. Fixes run-#356 class where one late 500 nuked a run that had saved 3186 listings.

Refs #1790
2026-06-25 06:47:41 +00:00
4bb09911b8 feat(financial): LTC 70%/equity 30% норматив МКД РФ в модели финансирования
All checks were successful
Deploy / changes (push) Successful in 7s
Deploy / build-backend (push) Successful in 2m35s
Deploy / build-frontend (push) Successful in 3m30s
Deploy / build-worker (push) Successful in 4m6s
Deploy / deploy (push) Successful in 2m19s
Заменяет 100%-кредитное покрытие разрыва реалистичной LTC-моделью (банк 70%, собственные средства 30%). total_interest снижается ~30%, net_profit_after_financing растёт. Backend 41/41, Frontend 20/20.
2026-06-24 16:49:01 +00:00
5a73f1d0b6 feat(cockpit): поверхностить financial_estimate в Development Scan / Investment Clearance (#1881 rank-5) (#1894)
All checks were successful
Deploy / build-backend (push) Has been skipped
Deploy / changes (push) Successful in 7s
Deploy / build-worker (push) Has been skipped
Deploy / build-frontend (push) Successful in 3m18s
Deploy / deploy (push) Successful in 1m10s
feat(cockpit): поверхностить financial_estimate в Development Scan / Investment Clearance (#1881 rank-5)
2026-06-24 16:20:19 +00:00
c13ab32aa2 Merge pull request 'fix(financial): парковка price/cost по классу — econom наземная, business=Excel (#1881)' (#1893) from fix/parking-economics-by-class into main
All checks were successful
Deploy / build-frontend (push) Has been skipped
Deploy / build-worker (push) Successful in 2m58s
Deploy / changes (push) Successful in 7s
Deploy / build-backend (push) Successful in 1m44s
Deploy / deploy (push) Successful in 1m21s
2026-06-24 12:21:23 +00:00
4eeda46cfa fix(financial): парковка price/cost по классу (econom наземная, business Excel)
All checks were successful
CI / changes (pull_request) Successful in 7s
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Successful in 1m57s
CI / backend-tests (pull_request) Successful in 11m44s
Финмодель брала ПЛОСКИЕ parking-константы для ВСЕХ классов: price 1.9М/cost 1.8М
(Excel «Авангардная 13» premium — подземная, near break-even). Но норма парковки
уже class-dependent (econom 0.8/comfort 1.0/business 1.5 м/м на квартиру). Econom
(дешёвое жильё) заряжался 1.8М/м/м ПОДЗЕМНОЙ застройки → огромный break-even капитал,
раздувающий пиковый долг/проценты. По РФ econom = наземная дешёвая парковка.

Фикс: _PARKING_PRICE_PER_SPOT / _PARKING_COST_PER_SPOT → dict[HousingClass]:
- econom 800k/350k (наземная), comfort 1.3М/1.0М (частично подземная),
  business 1.9М/1.8М (подземная, = Excel, unchanged).
- маржа/м/м: econom +450k, comfort +300k, business +100k.
- НОРМА парковки (_PARKING_PER_APARTMENT) НЕ тронута (возможно ПЗЗ-норматив).
- Константы помечены как TUNABLE ASSUMPTIONS (РФ-рынок), правятся в одном месте
  (паттерн _SALE_PRICE_PER_SQM / _CONSTRUCTION_COST_PER_SQM).

ВАЖНО (честно): парковка — ВТОРИЧНЫЙ драйвер. Улучшает econom в основном через
снижение peak-debt/процентов, НЕ переворачивает знак. ДОМИНИРУЮЩИЙ рычаг —
себестоимость СМР жилья к выручке (econom 72k на GFA vs выручка на sellable = 87%)
— отдельное продуктовое решение (не трогаю без согласования).

Инвариант Σ cashflow == net_profit сохранён (меняются только величины
revenue_parking/construction_parking в том же каскаде). Тесты: +6 (по классам,
маржа, инвариант econom+parking) + 3 существующих исправлены (хардкод comfort
1.9/1.8 → 1.3/1.0). tests/services/generative 90 passed. ruff+mypy(strict) чисто.
Схема не менялась.

Хозяйственное: untrack + gitignore backend/.coverage (артефакт блокировал checkout).

Refs #1881
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 16:58:21 +05:00
a45dfd4ee3 Merge pull request 'fix(gate): area-gate ЗОУИТ utility-easement blocker (sub-17) — снять over-block can_build (#1881)' (#1892) from fix/zouit-engineering-area-gate into main
All checks were successful
Deploy / changes (push) Successful in 7s
Deploy / build-frontend (push) Has been skipped
Deploy / build-backend (push) Successful in 4m31s
Deploy / deploy (push) Successful in 1m56s
Deploy / build-worker (push) Successful in 6m17s
2026-06-24 10:09:08 +00:00
bc1da8eac9 fix(gate): area-gate ЗОУИТ utility-easement blocker (sub-17) — снять over-block
All checks were successful
CI / changes (pull_request) Successful in 7s
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Successful in 1m52s
CI / backend-tests (pull_request) Successful in 11m34s
ZOUIT_OVERLAP_SUB17 (единственный subcategory-blocker = охранная зона
ЛЭП/газа/трубопровода) был ХАРД-блокером на ЛЮБОЕ пересечение → блокировал
434/574 (75.6%) участков как «нельзя строить МКД». Прод-замер: реальное
покрытие участка этими зонами median=6%, p90=47%, только 1/17 >60% — тонкие
инж-коридоры. По РФ охранная зона ограничивает застройку ВНУТРИ полосы, а не
весь участок (МКД сажается на незанятой части). Хард-блок на 6%-покрытии неверен
и (после фикса резолва зон #1891) прятал financial_estimate у ~90% участков.

Фикс: area-gate.
- quarter_dump_lookup._get_zouit_overlaps + _get_cad_zouit_overlaps: добавлен
  coverage_pct (доля площади участка под зоной, в geography м², NULLIF-guard).
- gate_verdict.compute_gate_verdict: sub-17 (и cad network/blocker) АГРЕГИРУЮТСЯ
  (sum-capped, консервативно к блоку) → блокер ТОЛЬКО если покрытие >
  settings.gate_zouit_engineering_blocker_min_coverage (0.6, env-tunable), иначе
  WARNING ZOUIT_ENGINEERING_PARTIAL с % покрытия. Больше нет 4× дубль-блокеров.
  СЗЗ/OKN/прочие ЗОУИТ — без изменений (warnings). main_vri/резидентность — без
  изменений.
- coverage_pct отсутствует (legacy/synthetic) → трактуем как 0 → warning
  (документировано: дамп всегда даёт coverage; ложный хард-блок дороже).

Тесты: +помесь gate/aggregation/coverage (131 combined passed); правка
_make_zouit_row (7-я колонка coverage_pct) в tests/test_quarter_dump_lookup.py.
Полный бэкенд локально: 2 failed → fixed → перепроверка. ruff чисто. Схема не
менялась (gate_verdict/overlaps — dict[str,Any], api-types regen не нужен).

Refs #1881
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 14:45:59 +05:00
41d97aeaf9 Merge pull request 'fix(site-finder): разгейтить geoportal зон-резолвер от gappy NSPD-дампа — починить видимость финмодели (#1881)' (#1891) from fix/zoning-resolution-decouple into main
All checks were successful
Deploy / changes (push) Successful in 6s
Deploy / build-frontend (push) Has been skipped
Deploy / build-backend (push) Successful in 1m46s
Deploy / build-worker (push) Successful in 2m47s
Deploy / deploy (push) Successful in 1m27s
2026-06-23 23:55:52 +00:00
2e53f0a1c4 test(analyze): авто-стаб geoportal зон-резолвера в api/v1 тестах (CI desync fix)
All checks were successful
CI / openapi-codegen-check (pull_request) Successful in 1m46s
CI / changes (pull_request) Successful in 7s
CI / frontend-tests (pull_request) Has been skipped
CI / backend-tests (pull_request) Successful in 11m26s
PR-A сделал get_or_fetch_zone_regulation БЕЗУСЛОВНЫМ в analyze. Analyze-тесты с
позиционным DB-моком (_make_db_for_analyze + call_idx/responses[]) не мокали
резолвер → новый безусловный вызов делает db.execute (get_cached) и сдвигает
последовательность ответов мока → parcel_meta получает чужой ответ = None.
Локально проходило (geoportal недоступен с dev-машины → резолвер возвращал None,
без db-вызова), в CI падало (раннер на проде ДОСТАЁТ geoportal → зона резолвится →
get_cached → desync): FAILED test_parcel_meta_found_in_cad_parcels.

Fix: autouse-фикстура в tests/api/v1/conftest.py глушит резолвер в no-op (return
None) для всех v1-тестов — блок 9e-bis ничего не синтезирует и не трогает БД
(идентично до-PR-A). test_analyze_zoning_regulation.py переопределяет резолвер
per-test (patch поверх autouse), поэтому его проверки сохранены.

Verify: затронутые analyze-файлы + zoning-файл = 72 passed, 0 failed. ruff чисто.

Refs #1891
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 04:33:17 +05:00
de13bfc64c fix(gate): main_vri-authoritative детекция МКД (убрать Ж-1/Ж-2 ИЖС false-positive, включить Ц-2)
Some checks failed
CI / changes (pull_request) Successful in 6s
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Successful in 1m51s
CI / backend-tests (pull_request) Failing after 12m21s
PR-A синтезировал zone_code = geoportal-индекс ("Ж-2") → gate.is_residential_zone
матчил ^Ж и считал ЛЮБУЮ Ж-* зону жилой-МКД. Но по zone_regulation_cache.main_vri
(authoritative ВРИ): Ж-1/Ж-2 = только ИЖС (нет кодов МКД) → ^Ж давал FALSE POSITIVE
("можно МКД" на ИЖС-участке — реальный дефект для девелопера); Ц-2 = разрешает МКД
(коды 2.5/2.6) но без Ж-префикса → FALSE NEGATIVE.

Фикс: is_residential_zone получил приоритет-0 — при наличии main_vri решает по нему
авторитетно (mkd_permitted_from_vri: коды 2.1.1/2.5/2.6 = МКД, точное совпадение
токена, "2.50"/"2.59"/"12.0" не матчат), переопределяя ^Ж/subcategory/keyword.
Эвристики остаются ТОЛЬКО как fallback при main_vri=None (dump-путь без geoportal).
parcels.py прокидывает _regulation.main_vri в nspd_zoning на ОБОИХ путях (synth +
dump-augment) → dump-зоны (Ц-2 и пр.) тоже апгрейдятся на main_vri-детекцию.
Унифицирует PR-A+PR-B. Обратная совместимость: main_vri default None.

Проверено по зонам: Ж-3/4/5 permit_mkd=True, Ж-1/2=False(ИЖС), Ц-2=True,
Ц-1/3/4+ЦС-*=False.

Тесты: +22 (mkd_permitted_from_vri true/false/none + no-false-prefix; override
suppress ИЖС / enable Ц-2 / overrides subcategory; fallback при None unchanged;
synth ИЖС-кейс не даёт residential). Полный бэкенд локально: 3442 passed, 0 failed,
coverage 70.3% (gate 65%). ruff чисто. Схема не менялась. Code-review 2×approve 0 major.

Refs #1881 #1891

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 03:59:31 +05:00
46f021e1c8 fix(site-finder): разгейтить geoportal зон-резолвер от gappy NSPD-дампа
Some checks failed
CI / changes (pull_request) Successful in 7s
CI / openapi-codegen-check (pull_request) Successful in 1m51s
CI / backend-tests (pull_request) Failing after 11m59s
CI / frontend-tests (pull_request) Has been skipped
financial_estimate фаерил ~0% в проде (0/542 анализов, 90д): резолв ПЗЗ-зоны на
участок проваливался на 94%. Причина — в analyze ДВА резолвера зоны:
(1) get_quarter_dump_data/_get_zoning (nspd_quarter_dumps, точный ST_Intersects) —
терзоны НЕ замощают квартал плотно, центроид участка падает в 45-86м ЗАЗОР между
валидными зонами → nspd_zoning=None у 512/542; (2) get_or_fetch_zone_regulation →
EKBGeoportalClient.zone_index_at (живой геопортал, cache-first) — РАБОТАЕТ, резолвит
gap-участки. Но (2), дающий max_far, был ЗАГЕЙЧЕН за `_nspd_zoning is not None`, т.е.
зазор дампа глушил рабочий резолвер. Плюс: dump кладёт кадастровый рег-номер
("66:41-7.14") в zone_code, а gate.is_residential_zone матчит ^Ж → не срабатывал.

Фикс (PR-A): убран `_nspd_zoning is not None` из условия — резолвер геопортала
работает при наличии centroid (region-guard: только КН 66:41, геопортал ЕКБ-only).
Когда dump зону не дал — синтезируем минимальный nspd_zoning из geoportal:
zone_code = индекс зоны ("Ж-2") → is_residential_zone ^Ж срабатывает → can_build_mkd
резолвится для Ж-* зон. Запись обратно в nspd_dump_data["nspd_zoning"] доходит до
gate / финмоста / ответа (читают по ключу). Когда dump зону ДАЛ — zone_code не
перетираем (raw_props.subcategory детектит жильё), только добавляем regulation-поля.
Геопортал — authoritative источник (point-in-zone по ПОЛНОМУ слою ПЗЗ); «зазор»
только в нашем кэше-дампе, не в реальности. Хот-path-safe (try/except, timeout 3с,
cache-first). Расширение dump-резидентности на Ц-*/ЦС-* → PR-B.

Тесты: +7 (синтез при dump=None+geoportal; нет синтеза при обоих None; dump zone_code
не перетёрт; геопортал падает→деградация; is_residential_zone Ж-2=True, ПК-1/ЦС-3=False).
Кейс синтеза ассертит РЕАЛЬНЫЙ gate can_build_mkd=True. 47 passed (вкл. pre-existing
zoning+gate), ruff+mypy чисто. Схема не менялась (api-types regen не нужен).

Прод-замер: 66:41:0303006:930 (Ж-5 gap) сейчас zone_code/max_far/financial=None,
can_build=False → после деплоя ожидаем Ж-5/max_far=4/can_build=True/financial=PRESENT.

Refs #1881

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 03:25:06 +05:00
d2b745e240 Merge pull request 'feat(financial): DCF-график продаж по реальному темпу поглощения (rank 1, #1881)' (#1890) from feat/dcf-velocity-schedule into main
All checks were successful
Deploy / changes (push) Successful in 7s
Deploy / build-backend (push) Successful in 1m56s
Deploy / build-frontend (push) Successful in 3m33s
Deploy / build-worker (push) Successful in 3m12s
Deploy / deploy (push) Successful in 1m32s
2026-06-23 22:08:08 +00:00
b6b9aa9929 feat(financial): DCF-график продаж по реальному темпу поглощения (rank 1)
All checks were successful
CI / changes (pull_request) Successful in 7s
CI / frontend-tests (pull_request) Successful in 1m10s
CI / openapi-codegen-check (pull_request) Successful in 2m3s
CI / backend-tests (pull_request) Successful in 10m10s
Финмодель брала фиксированное окно продаж 30 мес независимо от рынка
(schedule_is_default всегда true). Теперь окно считается из ЛОКАЛЬНОГО темпа
поглощения, который уже вычисляется в том же /analyze, но финмодель его
игнорировала.

Корректность абсорбции (ключевое): velocity.monthly_velocity_sqm — это СУММА
поглощения ВСЕГО конкурентного набора в радиусе (м²/мес), НЕ темп одного
проекта. Поэтому per-project absorption = monthly_velocity / max(n_with_sales,1)
(темп одного типичного локального продавца) — иначе модель считала бы, что новый
проект забирает весь рыночный темп (дико оптимистично). Поле
project_absorption_sqm_per_month добавлено в VelocityResult (objective-путь);
rosreestr-fallback и вырожденные пути → None (поквартальный count без
по-проектной декомпозиции не может задавать график).

financial.py: окно = clamp(ceil(residential/velocity), MIN=6, MAX=120) при
конечной velocity>0; иначе дефолт 30. Эскроу-инвариант сохранён:
sales_end=max(sales_start+base, constr_end). Инвариант Σ cashflow == net_profit
держится (перенос выручки во времени не меняет сумму). schedule_is_default
флипается в false когда график рыночный; новое поле sales_duration_months
(реализованное окно) для UI/PDF.

Wiring: parcels.py → synthesize_parcel_financial(velocity_sqm_per_month) →
compute_financial(market_velocity_sqm_per_month). Generative §1c путь пока
передаёт None (out of scope, follow-up).

Тесты: +13 (None→дефолт+инвариант; рыночная velocity; клампы MIN/MAX; эскроу;
non-finite→fallback; rosreestr→None; инвариант по размерам окна; регресс PR-3 —
ровно одна смена знака на коротком окне). Полный бэкенд: 3414 passed, 0 failed.
ruff+mypy(strict financial.py) чисто. api-types перегенерены.

Code-review: 2× approve, 0 majors (adversarial correctness-lens подтвердил
семантику абсорбции, инвариант, не-proxy IRR, клампы, rosreestr-None).

Refs #1881

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 02:56:48 +05:00
cb7df4657a Merge pull request 'feat(site-finder): backfill ЕКБ зон-регламента — разблокировать видимость финмодели (#1881)' (#1889) from feat/zone-regulation-backfill into main
All checks were successful
Deploy / changes (push) Successful in 8s
Deploy / build-backend (push) Successful in 2m6s
Deploy / build-worker (push) Successful in 3m14s
Deploy / build-frontend (push) Successful in 3m40s
Deploy / deploy (push) Successful in 1m29s
2026-06-23 21:27:58 +00:00
c972eebce6 chore(api-types): regen после нового admin-endpoint backfill (#1881)
All checks were successful
CI / changes (pull_request) Successful in 6s
CI / frontend-tests (pull_request) Successful in 1m6s
CI / openapi-codegen-check (pull_request) Successful in 2m0s
CI / backend-tests (pull_request) Successful in 10m12s
openapi-codegen-check: новый POST /admin/scrape/zone-regulations/backfill +
TriggerZoneRegulationsBackfillRequest добавили path/schema в OpenAPI →
api-types.ts должен байт-в-байт совпадать с генератором.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 02:17:04 +05:00
2ff34b1500 feat(site-finder): backfill ЕКБ зон-регламента — разблокировать видимость финмодели (#1881)
Some checks failed
CI / changes (pull_request) Successful in 7s
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Failing after 1m48s
CI / backend-tests (pull_request) Successful in 9m56s
financial_estimate (эпик #1881) фаерит только при разрезолвленном НСПД-регламенте
(max_far), а он резолвится ЛЕНИВО при анализе участка → zone_regulation_cache всего
33 зоны → у большинства участков financial_estimate=None («—» в кокпите). Backfill
проактивно кэширует ВСЕ террзоны ЕКБ (~100-120) → ЛЮБОй участок в известной зоне
сразу получает регламент и финмодель.

- backfill_ekb_zone_regulations (zone_regulation.py): WFS-перечисление террзон ЕКБ
  (features_in_bbox 'territorial_zone') → dedup по urban_index (предпочёт фичу с
  геометрией) → representative_point (внутри полигона, не centroid) → zone_regulation_at
  → upsert. ИДЕМПОТЕНТНО (cached skip перед fetch), per-zone try/except (один сбой не
  рушит batch), rate_delay вежливость к геопорталу, limit для частичного прогона.
- Celery task backfill_zone_regulations + регистрация в celery_app include +
  admin POST /api/v1/admin/scrape/zone-regulations/backfill (паттерн objective sync-our).
- Ленивый refresh_zone_regulations / get_or_fetch / upsert НЕ тронуты (additive путь).
- +9 тестов (dedup, idempotency cached-skip, per-zone error isolation, centroid-inside,
  limit). mypy/ruff clean.

Прод-прогон backfill — отдельный шаг после deploy (как Objective sync), не на deploy.
Code-review поймал незарегистрированный task (был бы NotRegistered) — исправлено.

Refs #1881
2026-06-24 02:11:41 +05:00
b7629067cf Merge pull request 'chore(mocks): add financial_estimate to parcel-analyze mock for local preview (#1881)' (#1888) from chore/mock-financial-estimate into main
All checks were successful
Deploy / changes (push) Successful in 7s
Deploy / build-backend (push) Has been skipped
Deploy / build-worker (push) Has been skipped
Deploy / build-frontend (push) Successful in 3m2s
Deploy / deploy (push) Successful in 1m16s
2026-06-23 21:01:30 +00:00
5359556068 chore(mocks): add financial_estimate to parcel-analyze mock for local preview (#1881)
All checks were successful
CI / openapi-codegen-check (pull_request) Successful in 1m50s
CI / changes (pull_request) Successful in 6s
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Successful in 51s
Финмодель (PR-1..5) видна в кокпите только при NEXT_PUBLIC_USE_MOCKS=true если
мок несёт financial_estimate (моки минуют backend → фронт читает JSON напрямую).
Раньше мок его не имел → Investment Clearance в локальном preview показывал «—».

Добавлен аутентичный financial_estimate (37 полей: каскад затрат + БДР + DCF
NPV/IRR/PBP + финансирование + teap_synth + caveat), сгенерённый РЕАЛЬНОЙ
synthesize_parcel_financial для участка area=8240, КСИТ 3.5, цена 165k/comfort/
high-rise — гарантирует совпадение с прод-выводом, не руками-числа. NPV 3.9М,
IRR 15.4%, чистая прибыль после финанс. 7.2М (проценты 224М съели маржу —
реалистичный тонкий high-rise).

Refs #1881
2026-06-24 01:58:44 +05:00
8bec98e435 Merge pull request 'fix(estimator): enable split-home disclosure at validated threshold 1.2 (audit #1871)' (#1887) from fix/finmodel-split-disclosure-threshold into main
All checks were successful
Deploy Trade-In / changes (push) Successful in 9s
Deploy Trade-In / build-frontend (push) Has been skipped
Deploy Trade-In / build-browser (push) Has been skipped
Deploy Trade-In / test (push) Successful in 1m23s
Deploy Trade-In / build-backend (push) Successful in 52s
Deploy Trade-In / deploy (push) Successful in 48s
2026-06-23 20:55:45 +00:00
77a3d3f75a fix(estimator): enable split-home disclosure at validated threshold 1.2 (audit #1871)
All checks were successful
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / changes (pull_request) Successful in 6s
CI / openapi-codegen-check (pull_request) Has been skipped
Включает wide-corridor disclosure (PR #1880, был default OFF) — но с
ИСПРАВЛЕННЫМ порогом по prod-данным.

Проверка на trade_in_estimates (60д): corridor_pct = (range_high-range_low)/
median_price имеет median≈0.48, p90≈0.93, p99≈1.38; **30.7% оценок > 0.6**.
Старый порог 0.6 прилепил бы caveat «дом разбит на секции» к ~трети оценок —
большинство НЕ split-дома (широкий коридор бывает от разных причин). Это была
бы ложная атрибуция (дезинформация).

- estimate_wide_corridor_threshold 0.6 → 1.2: genuine split-дома из аудита =
  148-170% (1.48-1.70) → 1.2 ловит только экстремальный хвост (>p99), не трогая
  нормальную оценочную неопределённость.
- estimate_wide_corridor_disclosure_enabled False → True (включаем после
  валидации). ENV-override сохранён (kill-switch).
- Формулировка смягчена: «дом разбит на секции» → «вероятно, дом разбит на
  секции разной этажности ИЛИ разнородный фонд» — мы ИНФЕРИМ split по ширине
  коридора, не доказываем структурно.
- Логика не тронута: фаерит только Tier A + corridor>threshold, только понижает
  confidence + дописывает explanation; point/median/range не трогает.

Тесты: обновлены default-ассерты (флаг ON, порог 1.2) + 2 behavioral boundary-
теста (фаерит при pct>threshold, НЕ фаерит при умеренном 1.045<1.2 — доказывает
что raise убрал false-positive flood). 13 passed.

Refs #1871
2026-06-24 01:54:58 +05:00
f0b689318e Merge pull request 'feat(finmodel): financing overlay (кредит 15%) поверх unlevered DCF (epic #1881 PR-5)' (#1886) from feat/finmodel-financing into main
All checks were successful
Deploy / changes (push) Successful in 7s
Deploy / build-backend (push) Successful in 2m23s
Deploy / build-worker (push) Successful in 3m25s
Deploy / build-frontend (push) Successful in 3m33s
Deploy / deploy (push) Successful in 1m27s
2026-06-23 20:23:47 +00:00
d350d27325 feat(finmodel): financing overlay (кредит 15%) поверх unlevered DCF (epic #1881 PR-5)
All checks were successful
CI / frontend-tests (pull_request) Successful in 59s
CI / openapi-codegen-check (pull_request) Successful in 1m53s
CI / backend-tests (pull_request) Successful in 10m1s
CI / changes (pull_request) Successful in 7s
Финансирование как ДОПОЛНИТЕЛЬНЫЙ overlay поверх готового unlevered cashflow:
кредитная линия покрывает накопительный кассовый разрыв, проценты капитализируются
(compound), ставка _CREDIT_RATE_ANNUAL=15% (норматив, ОТДЕЛЬНО от ставки
дисконтирования — разная семантика: стоимость долга vs требуемая доходность).

Backend (financial.py):
- _compute_financing(operating_cf, annual_rate) — чистая (READ-ONLY вход, не
  мутирует): процент на остаток на начало месяца ДО движений → капитализация;
  draw=-cf при оттоке, repay=min(cf,debt) при притоке (долг не уходит в минус).
  Возвращает peak_debt_rub / total_interest_rub / ending_debt_rub.
- compute_financial: вызов после DCF, net_profit_after_financing = net − interest.
- _build_monthly_cashflow и unlevered NPV/IRR/PBP НЕ ТРОНУТЫ → инвариант
  Σ unlevered cashflow == net_profit сохранён (регресс-тест).
- FinancialModel +6 полей (financing_enabled/annual_rate_used/peak_debt_rub/
  total_interest_rub/net_profit_after_financing_rub/financing_is_simplified).

Frontend: ParcelFinancialEstimate +6 полей; adaptFinanceDrawer + concept-каскад
показывают пиковый долг/проценты/чистую прибыль после финанс. (когда enabled).

MVP: только агрегаты, БЕЗ levered IRR (хрупко на хардкод-100%-долге — отдельный
PR). HEAVY caveat: проценты — реальная стоимость (net после < net до); 100%
покрытие разрыва (нет equity/LTC); compound; эскроу не моделируется; unlevered
headline не затронут. financing_is_simplified=True флаг.

mypy strict clean, +9 backend + 3 frontend тестов (compound точное значение,
no-mutation, инвариант-регресс, монотонность, net_after<net), api-types
регенерён авторитетно, deps не тронуты (hand-roll).

Refs #1881
2026-06-24 01:12:47 +05:00
9495c2d157 Merge pull request 'feat(site-finder): bridge финмодели (DCF) в кокпит Investment Clearance (epic #1881 PR-4)' (#1885) from feat/finmodel-sitefinder-bridge into main
All checks were successful
Deploy / changes (push) Successful in 6s
Deploy / build-backend (push) Successful in 2m6s
Deploy / build-worker (push) Successful in 3m15s
Deploy / build-frontend (push) Successful in 3m19s
Deploy / deploy (push) Successful in 1m24s
2026-06-23 17:55:03 +00:00
67b65c8d8a feat(site-finder): bridge финмодели (DCF) в кокпит Investment Clearance (epic #1881 PR-4)
All checks were successful
CI / frontend-tests (pull_request) Successful in 57s
CI / openapi-codegen-check (pull_request) Successful in 1m49s
CI / changes (pull_request) Successful in 6s
CI / backend-tests (pull_request) Successful in 9m54s
Investment Clearance в site-finder кокпите больше не «—»: синтезируем лёгкий
ТЭП из buildability участка (площадь + предельные параметры зоны НСПД/ПЗЗ —
КСИТ/%застройки/этажность) → прогоняем через полноценную финмодель (каскад
затрат PR-1 + калибр.цена PR-2 + помесячный DCF PR-3) → GDV/Cost/Profit/ROI/
IRR/NPV/PBP в кокпите.

Backend:
- new app/services/site_finder/parcel_financial.py (ЧИСТЫЕ функции, без БД):
  synthesize_teap_from_buildability (GFA=area×max_far, degradation pct+floors;
  residential=GFA×eff; apartments/parking — нормативы teap.py, не дублируются)
  + synthesize_parcel_financial (housing_class из цены, development_type из
  этажей, land_cost=кадастровая). None когда нельзя строить МКД / нет зонинга /
  нет площади.
- parcels.py: 1 вызов в try/except (hot-path-safe → None при сбое) + ключ
  financial_estimate в /analyze. Схема не тронута (AnalyzeResponse extra=allow,
  codegen не нужен). +18 тестов.

Frontend:
- ParcelFinancialEstimate тип (site-finder.ts, hand-typed).
- adaptInvestmentClearance(financial)/adaptFinanceDrawer(financial): реальные
  числа (GDV/Cost/Profit/ROI/IRR + NPV + срок окуп.) или честный placeholder
  когда null. Локализация класса/типа (high_rise→высотная). Прокинут analysis
  через PticaBottomGrid/drawer-registry. +6 тестов, 144 passed.

HEAVY caveat везде: ОРИЕНТИРОВОЧНАЯ модель по МАКС.застройке НСПД, НЕ реальная
концепция; land=кадастровая (не рыночная); график — типовой; IRR proxy-флаг.
mypy strict clean (generative.*), ruff/tsc/lint clean.

Закрывает эпик #1881 (финмодель = полноценная DCF, видна в кокпите).
Follow-up: финансирование (кредит/займы), §22 sales-pace, гео-радиус калибровки.

Refs #1881
2026-06-23 22:44:04 +05:00
ae90f84c90 Merge pull request 'feat(finmodel): monthly DCF — real NPV/IRR/PBP, replace IRR-proxy (epic #1881 PR-3)' (#1884) from feat/finmodel-dcf into main
All checks were successful
Deploy / changes (push) Successful in 7s
Deploy / build-backend (push) Successful in 1m53s
Deploy / build-frontend (push) Successful in 3m15s
Deploy / build-worker (push) Successful in 4m1s
Deploy / deploy (push) Successful in 1m28s
2026-06-23 17:02:54 +00:00
d4068a255b feat(finmodel): monthly DCF — real NPV/IRR/PBP, replace IRR-proxy (epic #1881 PR-3)
All checks were successful
CI / frontend-tests (pull_request) Successful in 57s
CI / backend-tests (pull_request) Successful in 9m54s
CI / changes (pull_request) Successful in 6s
CI / openapi-codegen-check (pull_request) Successful in 1m53s
Hand-rolled детерминированный DCF (без numpy-financial/новых deps): _npv /
_irr_monthly (bisection с bracket-guard) / _payback_months над помесячным
cashflow, построенным из статического каскада (PR-1) + калиброванной цены
(PR-2) по дефолтным нормативам фаз (ПИР 6мес → СМР по development_type
24/36/48 → распродажа, дисконт 15%).

- Налоги прорейтятся на признание выручки → Σ недисконтированного cashflow ==
  net_profit (ИНВАРИАНТ, тест — гарантия что DCF не разошёлся со статикой).
- IRR настоящий когда есть смена знака; вырожденный поток → fallback на
  roi-proxy (irr_is_proxy=True). Честно помечено.
- График продаж привязан к завершению стройки: sales_end = max(sales_start +
  длительность, construction_end) — продажи не заканчиваются раньше ввода
  (РФ ДДУ/эскроу). Без этого high_rise распродавался за 12 мес ДО ввода →
  двойная смена знака → profitable-проект молча падал в proxy + инверсия NPV.
- FinancialModel +npv_rub/payback_months/discount_rate_used/schedule_is_default
  (additive). development_type проброшен через placement.
- UI/PDF: NPV/IRR/PBP + честный caveat «график — типовое допущение, точность
  зависит от него». api-types регенерён авторитетно.

mypy strict clean (generative.*), +15 тестов (DCF helpers на учебных значениях,
ИНВАРИАНТ, IRR real-vs-proxy обе ветки, PBP, sales≥construction для всех типов,
profitable high_rise → настоящий IRR). 48 passed.

Refs #1881
2026-06-23 21:52:04 +05:00
2276c2057b Merge pull request 'feat(generative): calibrate finmodel sale price from Objective market (epic #1881 PR-2)' (#1883) from feat/finmodel-price-calibration into main
All checks were successful
Deploy / build-frontend (push) Successful in 3m32s
Deploy / changes (push) Successful in 7s
Deploy / build-backend (push) Successful in 2m3s
Deploy / build-worker (push) Successful in 3m29s
Deploy / deploy (push) Successful in 1m27s
2026-06-23 16:21:15 +00:00
b1332d6b55 feat(generative): calibrate finmodel sale price from Objective market data (epic #1881 PR-2)
All checks were successful
CI / frontend-tests (pull_request) Successful in 1m3s
CI / changes (pull_request) Successful in 7s
CI / openapi-codegen-check (pull_request) Successful in 2m0s
CI / backend-tests (pull_request) Successful in 10m6s
Заменяет хардкод-цену продажи жилья (класс-норма) на реальную рыночную
медиану из Objective по локации участка. compute_financial остаётся ЧИСТЫМ
(без БД) — DB-lookup в API-слое, цена прокидывается параметром.

- compute_financial: +optional market_price_per_sqm/price_source; цена =
  рынок если есть, иначе класс-норма. Паркинг/СМР НЕ калибруем (только жильё).
- concepts.py _lookup_market_price(db, lon, lat): центроид → ближайший
  ekb_district (ST_DWithin 5км) → медиана objective_lots.price_per_m2_rub
  (n>=10, фильтр 30k-600k) → fallback ekb_districts.median_price_per_m2 →
  None. try/except → graceful (None, class_norm) при любой ошибке (вне ЕКБ/
  нет гео/SQL) без краха генерации. psycopg3 CAST.
- FinancialModel +price_per_sqm_used/price_is_calibrated/price_source (additive).
- Threading market_price через generate→placement→compute_financial (optional
  kwargs, backward-compat).
- UI/CSV: честный caption источника (рынок Objective / справочник района /
  норматив класса). Старый лживый footnote «не калиброванная модель» → условный.

Prod-verified: калибруется 4 главных ЕКБ-района по name-match (Академический
204k лотов, Ленинский 38k, Кировский, Орджоникидзевский); остальные 5 admin-
районов честно → district_reference. Гео-радиус matching (полное покрытие) —
follow-up.

api-types.ts регенерён авторитетно. mypy strict clean (generative.*), +14
тестов (калибровка/lookup 4 ветки/SQL-ошибки graceful/backward-compat).

Refs #1881
2026-06-23 21:10:25 +05:00