6 commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
| b6b9aa9929 |
feat(financial): DCF-график продаж по реальному темпу поглощения (rank 1)
Финмодель брала фиксированное окно продаж 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> |
|||
| d350d27325 |
feat(finmodel): financing overlay (кредит 15%) поверх unlevered DCF (epic #1881 PR-5)
Финансирование как ДОПОЛНИТЕЛЬНЫЙ 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 |
|||
| d4068a255b |
feat(finmodel): monthly DCF — real NPV/IRR/PBP, replace IRR-proxy (epic #1881 PR-3)
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 |
|||
| b1332d6b55 |
feat(generative): calibrate finmodel sale price from Objective market data (epic #1881 PR-2)
Заменяет хардкод-цену продажи жилья (класс-норма) на реальную рыночную медиану из 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 |
|||
| 65bab902e1 |
feat(generative): full static developer financial cascade (epic #1881 PR-1)
Объединяет наше (площади из реальной геометрии teap) + полноту девелоперской Excel-модели (каскад затрат + БДР с налогами). Расширяет financial.py из статического GDV−COST в полный статический расчёт. БЕЗ DCF (PR-3), БЕЗ новых вводных от пользователя — только нормативы + существующий TEAP. Каскад (financial.py): - Выручка: жильё (residential×price) + ПАРКИНГ (spaces×1.9М) - Затраты: СМР(резид+паркинг) + ПИР 2.5% + сети/ТУ 6000₽/м² + услуги заказчика 1.5% + резерв 3% + маркетинг/риэлтор 7% выручки + земля - БДР: валовая маржа → НДС (упрощённо) → прибыль до налога → налог 25% (ФЗ 176-ФЗ) → ЧИСТАЯ прибыль → ROI + чистая маржа% - Нормативы документированы (источник: Excel-эталон «Авангардная 13» + RU-рынок) FinancialModel (concept.py): +16 additive-полей (revenue breakdown, cost cascade, БДР/налоги, roi, margin_pct, irr_is_proxy). Старые 4 поля сохранены (backward-compat). IRR честно помечен irr_is_proxy=True (annualized net ROI, не DCF) — настоящий IRR/NPV/PBP в PR-3 (помесячный cashflow). НДС — документированное упрощение, не точный НК РФ. Офисы пропущены (нет площади в TEAP). PDF + concept UI: полный БДР-каскад + чистая прибыль/ROI + честные сноски. api-types.ts регенерён авторитетно (openapi-typescript + prettier, как CI). mypy strict clean (generative.*), +тесты (каскад/паркинг/налоги/backward-compat/ edge cost=0), 36 passed. Refs #1881 |
|||
| 8242e51b61 |
feat(generative): exporters (dxf/pdf) + generative test suite (#54 #55 #56)
Some checks failed
CI / frontend-tests (push) Has been skipped
CI / changes (push) Successful in 10s
CI / changes (pull_request) Successful in 7s
CI / frontend-tests (pull_request) Has been skipped
CI / backend-tests (push) Successful in 7m39s
CI / backend-tests (pull_request) Successful in 7m39s
Deploy / deploy (push) Has been cancelled
Deploy / build-frontend (push) Has been cancelled
Deploy / build-backend (push) Has been cancelled
Deploy / build-worker (push) Has been cancelled
Deploy / changes (push) Successful in 6s
Дополняет движок: exporters/{dxf,pdf}.py (ezdxf + WeasyPrint lazy) + 5 тест-модулей
(geometry/placement/teap_financial/exporters/api). Не вошли в предыдущий commit
(untracked-директории).
|