c1541962dc
feat(cockpit): поверхностить financial_estimate в Development Scan / Investment Clearance
...
CI / changes (pull_request) Successful in 6s
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Successful in 55s
CI / openapi-codegen-check (pull_request) Successful in 1m53s
- adaptEconomyCard(financial?) — показывает выручку/маржу/ROI/IRR из финмодели
когда financial_estimate присутствует; badge «предв.» → «ориент.», caption
честно говорит источник цены (рынок vs норматив класса), IRR — proxy-флаг
- DevelopmentScan: передаёт analysis.financial_estimate в adaptEconomyCard
- adaptFinanceDrawer: добавлена строка «Горизонт продаж» (sales_duration_months
+ caption: дефолт-норматив vs по темпу поглощения рынка)
- adaptInvestmentClearance: financing_enabled=true → «Profit · до финанс.» +
новый bignum «После финанс.» с post-financing net profit и caption ставки
- adaptSiteVerdict: verdict_label «С ограничениями» → word=«С ограничениями»
tone=warn (вместо Приобретать/good), корректно для ЗОУИТ area-gate parcels
- Тесты: +6 кейсов (economy real/null, sales horizon, clearance financing,
verdict «С ограничениями» / «Можно»); 20/20 pass
2026-06-24 21:16:55 +05:00
d350d27325
feat(finmodel): financing overlay (кредит 15%) поверх unlevered DCF (epic #1881 PR-5)
...
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
67b65c8d8a
feat(site-finder): bridge финмодели (DCF) в кокпит Investment Clearance (epic #1881 PR-4)
...
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
adf3ec7c00
fix(site-finder): velocity fallback 4500→750, coverage/proxy disclosure, market_trend staleness honesty (audit #1871 P2)
...
CI / changes (pull_request) Successful in 6s
CI / frontend-tests (pull_request) Successful in 1m5s
CI / openapi-codegen-check (pull_request) Successful in 1m59s
CI / backend-tests (pull_request) Successful in 10m9s
Три honesty/correctness-фикса из audit-issue #1871 (🟠 P2). Все факты
подтверждены prod-DB + разведкой. ETL не трогаем (отдельная задача).
velocity.py:
- _EKB_MEDIAN_FALLBACK 4500→750 (audit-verified медиана ЕКБ 593-766;
4500 завышало знаменатель нормализации 7.6× → занижало velocity_score
при DB-error fallback, latent). Консервативно 750 (верх диапазона).
- VelocityResult + as_dict() раскрывают: objective_coverage_pct,
proxy_used, proxy_sqm_per_deal — теперь видно что объём rosreestr-пути
фабрикуется count×45 м²/сделка при низком покрытии Objective (<50%).
Проброшено во все 4 конструктора, additive.
parcels.py (market_trend producer):
- Различать «источник устарел» от «нет сделок». rosreestr_deals
MAX(period_start_date)=2026-01-01 (stale 5.5 мес) → recent-окно пусто →
раньше голый null. Теперь status ∈ {ok, source_stale, no_deals} +
as_of_date + label + recent_deals_count. MAX(deal_date) добавлен в CTE.
Consumer scoring читает recent_deals_count защищённо (без регрессии).
frontend (types + MarketTrendBlock + VelocityBlock + ptica market-drawer
+ Section3 verdict + legacy guard):
- staleness/no_deals → честный caveat вместо фейкового тренда.
- velocity → caption «объём оценочный (прокси N м²/сделка, покрытие P%)».
- formatAsOfMonth парсит YYYY-MM напрямую (TZ-safe).
- Типы MarketTrend/Velocity расширены (optional, backward-compat;
отсутствие status = "ok").
Тесты: +3 velocity unit + extended as_dict contract. 17 velocity passed,
138 frontend vitest passed.
Refs #1871
2026-06-23 12:04:08 +05:00
2303207629
fix(ptica): honest isReal flags — placeholder stubs no longer marked real (audit #1871 P1.3)
...
CI / frontend-tests (pull_request) Failing after 53s
CI / changes (pull_request) Successful in 7s
CI / backend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Successful in 1m52s
Adapter — единственный источник истины real-vs-placeholder; стаб с isReal:true
рисуется как живое измерение (обычная типографика «—», неотличимо от данных).
Helpers real()/placeholder()/notReal() корректны — баг в их misuse на 5 местах.
ptica-adapt.ts:
- :163 encumbrances (passport-card): {value:PLACEHOLDER, isReal:true} → notReal("источник ЕГРН")
- :272 buySignalFromForecast при deficit_index==null: {value:"наблюдать", isReal:true}
→ notReal("прогноз недоступен (Сценарии §22)") — отличает «прогноза нет» от
«прогноз говорит наблюдать». di-present advisory-ветка (real) не тронута.
- :1942 adaptBuySignalGauge при di==null: убран synthetic gaugeValue=50 как real;
ранний if(di==null) зеркалит no-report ветку (value:null, isReal:false, available:false).
- :681 adaptPassportDrawer registry, :1048 adaptLegalDrawer registry: те же
«Обременения» стабы {value:DASH, isReal:true} → notReal (найдены code-review,
recon их пропустил — drawer-зеркала card-фикса).
ptica.module.css: .kvPlaceholder += opacity:0.7 — усиленный визуальный mute стаба.
Корректные места НЕ тронуты (recon-verified): market demand-label (условный
real/placeholder), engineering utilities, InvestScore potential.
+5 unit-тестов (ptica-adapt.honesty.test.ts): encumbrances stub, buy-signal di=null,
buy-signal real di (защита от over-downgrade), gauge di=null, оба drawer-обременения.
Refs #1871
2026-06-23 11:20:48 +05:00
28b853bcdf
feat(ptica): add ВРИ row to Градостроительство (real ЕГРН permitted_use)
...
CI / changes (pull_request) Successful in 7s
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Successful in 1m4s
CI / openapi-codegen-check (pull_request) Successful in 2m4s
Prototype's first ГРАДОСТРОИТЕЛЬСТВО row is ВРИ — prod lacked it. Add it from
the real egrn.permitted_use_text (e.g. «многоквартирные дома»); honest «нет
данных ЕГРН» fallback. Value wraps cleanly in the narrow card (verified, no
overflow).
2026-06-21 04:09:36 +05:00
8c43410ad2
feat(ptica): live 3D massing in Инсоляция card + honest Recommended Product verdict
...
CI / changes (pull_request) Successful in 6s
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Successful in 57s
CI / openapi-codegen-check (pull_request) Successful in 1m59s
- MassingScene: preview prop — viewport-only, hands-off autorotate (OrbitControls
input disabled, autoRotate on), canvas pointer-events:none; teardown preserved.
- InsolationCard: replaces the abstract SVG cube with a live autorotating massing
built from real parcel area + НСПД max_far (farIsReal). «Открыть 3D» still opens
the full interactive drawer.
- RecommendedProductCard: degenerate mix no longer shows «сигнал недоступен» —
it shows the real advisory verdict «Избегать» + reason (затоварка · индекс
дефицита из mix.deficit_index) + product_tz.gate_caveat. Meaningful mixes still
render quartirography bars. «Подробнее» → product drawer (full reasons).
- drop dead iso-cube CSS (pticaIsoSpin/.isoBoxPreview/.isoPreview*); add
.massingPreviewViewport + verdict styles (tokens, dark-scoped).
- forecast type: product_tz.gate_caveat.
Clickability sweep: no dead controls found; cockpit already cleanly wired.
2026-06-21 03:11:15 +05:00
5587154327
fix(ptica): populate scan cards with real /analyze data + product tabs + map zoom
...
CI / changes (pull_request) Successful in 7s
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Successful in 54s
CI / openapi-codegen-check (pull_request) Successful in 1m53s
Verified against a real captured /analyze+/forecast fixture rendered locally:
much of the cockpit's «—» was unmapped data, not missing data.
- РЫНОК: map Поглощение (market_pulse.avg_velocity_m2), add Конкурентов
(market_pulse.competitors_total), Уровень спроса (market_trend.label);
keep Медиана + Новых проектов. Prototype row order.
- СРЕДА (adaptEnvironmentCard now takes analysis): Шум (noise.estimated_db),
Воздух PM2.5 (air_quality.pm2_5), Ветер (wind dir + max_speed), Водоёмы
(hydrology nearest). Honest «—» where genuinely absent.
- ГРАДОСТРОИТЕЛЬСТВО: Пятно застройки = area × max_building_pct/100 (real).
- RECOMMENDED PRODUCT: prototype class tabs (обж_class present in mix),
default = recommended class; per-class honest empty-state preserved.
- MAP: dark-styled ZoomControl (+/-) + ScaleControl, matching prototype.
- types: optional MarketPulse/MarketPrice on ParcelAnalysis (no any).
Economy/Investment-clearance stay «после финмодели §23» (not built); the
0%/«нельзя»/all-«избегать» verdict is the parcel's real result, not a bug.
2026-06-21 02:18:20 +05:00
fcaa4d197b
fix(ptica): visual parity 1:1 with prototype (incl. ОКС empty-card stretch)
...
CI / changes (pull_request) Successful in 7s
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Successful in 59s
CI / openapi-codegen-check (pull_request) Successful in 1m57s
Multi-region audit vs the prototype styles.css/dashboard.html → 22 CSS fixes +
markup. Headline: .lowerGrid/.bottomGrid align-items:start so the sparse ОКС
«нет данных» card no longer stretches into a huge void. Plus restore missing
prototype bits: topbar icon-buttons (Экспорт→Отчёты, Полный экран→fullscreen,
Настройки aria-disabled «скоро»), rail help-dot, app-footer (honest disclaimer,
no fabricated model/timestamp), invest-score sparkline (decorative, aria-hidden),
green/amber value tones, engineering status-row dots (from real nearest_m),
verdict-card gradient, scan-card status-rows, .canvas/.hbar/.mixHead spacing.
All new CSS scoped under .pticaRoot[data-theme=dark] (status-row classes
descendant-scoped under .scanCard — no collision with the drawer's). Environment
card stays honest kvrow placeholders (no fake dots). a11y: aria-labels on icon
buttons. TS strict, no any. tsc/lint/prettier/build green. code-reviewer ✅ .
2026-06-21 01:10:36 +05:00
24b5e6047f
feat(ptica): complete cockpit lower-grid + bottom-grid sections
...
CI / changes (pull_request) Successful in 6s
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Successful in 1m6s
CI / openapi-codegen-check (pull_request) Successful in 2m1s
Port the prototype's missing sections — the React cockpit had only hero +
Development Scan, so the deployed page was incomplete («поплывшее»). Add:
- lower-grid: ОКС · Development Potential · Recommended Product (квартирография)
· Инсоляционная матрица (opens 3D);
- bottom-grid: Investment Clearance · Buy Signal · Legal Status · Site Verdict
(«Приобретать/Нельзя/Нужна проверка» plaque + checklist).
Move ОКС + Инсоляция out of DevelopmentScan → scan = 7 cards (prototype IA).
Data honesty: Development Potential (real КСИТ/площадь/плотность), Recommended
Product (forecast product_tz), Buy Signal + Site Verdict (forecast deficit +
gate_verdict, real, three-state), Legal (zouit/red_lines/gate). Investment
Clearance all «—» «после финмодели (§23)» — no fabricated numbers. Typed
{value,isReal,caption} adapters; scoped dark CSS; forecast-pending safe.
Cleanup: drop orphaned adaptOksCard/adaptInsolationCard; add .metric/.bignum.
2026-06-21 00:33:15 +05:00
4205ecb483
Merge pull request 'feat(ptica): rich dark map (POI/connections/competitors/satellite) + ПТИЦА as default analysis route' ( #1851 ) from feat/ptica-default-and-rich-map into main
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 3m17s
Deploy / deploy (push) Successful in 1m10s
2026-06-20 19:06:08 +00:00
ba05a4cb62
feat(ptica): rich dark cockpit map + ПТИЦА as the default analysis route
...
CI / changes (pull_request) Successful in 6s
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Successful in 49s
CI / openapi-codegen-check (pull_request) Successful in 1m48s
Fix two regressions vs the prototype:
1. PticaMapInner: render real data layers over the dark base by REUSING the
existing analysis-map components — POI by category, competitors/pipeline
(MarketLayers), connection points + colored polylines (ConnectionPointsLayer),
ЗОУИТ (ZouitLayer), custom POI add/edit/delete (CustomPoiLayer + mutation hooks).
Add Спутник/Схема base toggle (Esri default + --map-filter-sat); legend rows
toggle layers; «Точки подключения» driven by real CP data. Isochrones left
«скоро» (ORS is on-demand, not in /analyze).
2. /site-finder/analysis/[cad] now renders the ПТИЦА cockpit — every parcel entry
opens ПТИЦА, not the old design. [cad]/ptica redirects to the parent (single
cockpit copy). Old AnalysisPageContent kept in repo; legacy UI at /legacy/site-finder.
3. Surface 3D massing: new «Инсоляция · 3D-масса» scan card + 3D legend row + 3D
map tool all open the insolation/future3d drawers.
SSR-safe (Leaflet only behind dynamic ssr:false); real POI from score_breakdown;
no redirect loop. tsc/lint/prettier/build green. code-reviewer APPROVE.
2026-06-21 00:04:00 +05:00
167fcbace2
feat(ptica): wire real ПЗЗ regulation (КСИТ/height/zone/density) into cockpit
...
CI / changes (pull_request) Successful in 6s
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Successful in 51s
CI / openapi-codegen-check (pull_request) Successful in 1m46s
Read nspd_zoning.{max_far,max_height_m,max_floors,max_building_pct,
regulation_zone_index,regulation_source} (backend PR #1847 ) into the urban
scan-card, urban/buildability/potential drawers and the 3D massing КСИТ-target.
Defensive: every read is optional-chained + != null guarded → identical "—"
placeholders when fields absent, so the cockpit is unchanged until #1847 deploys,
then auto-lights-up. КСИТ density = area × max_far; пятно = area × max_building_pct
(percent 0..100, verified vs backend parser); sellable labeled estimate. 3D maxFar
= nspd_zoning.max_far ?? 3.5 (finite/>0 guarded). NspdZoning type +7 optional fields.
TS strict, no any.
2026-06-20 23:33:41 +05:00
95f93da83b
feat(ptica): right-slider detail drawer system + 17 drawers wired (PR#4)
...
PticaDrawer: controlled right slider (480px / full-screen <768), scrim+Esc
close, focus-on-open, body-scroll-lock (restored on close/unmount), dialog
a11y. Deep-link ?drawer= via useSearchParams + shallow router.replace (no
render loop; unknown keys ignored). Enables the «Подробнее» buttons on scan
+ hero cards.
17 drawer contents (passport/buildability/urban/restrictions/engineering/
market[4 sub-tabs]/economy/risks/georisks/oks/potential/product/finance/legal/
environment/insolation/future3d) built from ParcelAnalysis + forecast product_tz.
Many are REAL: environment (noise/air_quality/wind/weather), georisks (geology/
hydrology/geotech_risk), market (competitors/market_trend/velocity/pipeline),
restrictions (zouit/red_lines/zoning), engineering, passport. Absent fields
(КСИТ/ОКС/финмодель/insolation) show honest «—»/«скоро» via ptica-adapt.
All field paths verified against types. Dark theme scoped. TS strict, no any.
Follow-up: drawer focus-trap + focus-restore for full modal a11y.
2026-06-20 16:16:39 +05:00
b0736e3a45
feat(ptica): build Сценарии tab (§22 forecast) + real hero invest-score
...
Dark cockpit Scenarios tab wired to useParcelForecastQuery (ForecastEnvelope):
scenario cards (base/aggr/conserv), demand-vs-supply chart + horizons table,
scenario compare table, confidence panel, recommended product (квартирография
+ target price + success-score), scoring transparency. Calm polling skeleton
while the async forecast is pending — never an error.
Hero invest-score/buy-signal now surface real values once the forecast resolves
(scoring.overall×10; buy-signal from exec_summary.key_numbers.deficit_index,
derived independent of the overall score), keeping the "после прогноза"
placeholders while pending. advisory · §22 caption shown in both states.
All forecast field paths verified against types/forecast.ts. Dark theme stays
scoped under .pticaRoot[data-theme=dark]. TS strict, no any.
2026-06-20 15:48:54 +05:00
05eb70eb62
feat(site-finder): ПТИЦА dark cockpit — increment 1 (shell + hero + Development Scan)
...
CI / changes (pull_request) Successful in 5s
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Successful in 55s
CI / openapi-codegen-check (pull_request) Successful in 1m57s
New non-destructive route /site-finder/analysis/[cad]/ptica rendering the existing
/analyze data in the «ПТИЦА» operator-terminal cockpit. The light analysis page,
Section1-6, globals.css and package.json are untouched.
PR#1 scope: app-shell (topbar + 4 tabs + left rail), hero (dark Leaflet map +
parcel passport KV-grid + Buildability radial gauge + invest-score block), and the
Development Scan card grid — wired to useParcelAnalyzeQuery / ParcelAnalysis.
Data honesty: real fields render live (passport egrn.*, district, utilities.summary,
median_price, pipeline_24mo); absent fields show muted placeholders with source
captions (buildability/risk = derived proxy «предв.»; invest/buy-signal «после
прогноза»; КСИТ/height/ОКС/economy «—») via typed ptica-adapt.ts — no fake numbers
presented as live.
Dark theme is a CSS-module scoped under .pticaRoot[data-theme=dark] (cannot leak to
the light app); IBM Plex Mono added via next/font (no dep/lockfile change). Leaflet
via dynamic(ssr:false). TS strict, no any. Deferred to follow-up PRs: Scenarios
(forecast), Reports+Compare tabs, 16 detail drawers, Three.js 3D massing.
2026-06-20 15:29:53 +05:00