feat(financial): model ground-floor нежилое (office/commercial) tranche
Carve a documented office share out of GFA before residential (fixes prior overstatement where all non-parking GFA = residential). total_floor_area unchanged → СМР already covers office GFA (no double-count): office is pure additive revenue at +15% premium. Offices нежилое → VAT-able: vat base = parking_value_added + office_value_added (consistent with #1898). Σ invariant holds. Additive schema (office_area_sqm, revenue_office_rub) → api-types regen. Refs #1881
This commit is contained in:
parent
d2773e0c91
commit
df1299175d
10 changed files with 281 additions and 39 deletions
|
|
@ -22,6 +22,11 @@ class TEAP(BaseModel):
|
||||||
|
|
||||||
built_area_sqm: float
|
built_area_sqm: float
|
||||||
total_floor_area_sqm: float
|
total_floor_area_sqm: float
|
||||||
|
# Нежилое (коммерция/офисы 1-го этажа) = GFA × нормативная доля по классу
|
||||||
|
# (см. ``_OFFICE_SHARE_OF_GFA`` в teap.py). Вырезается из GFA до расчёта жилой —
|
||||||
|
# жилая ужимается ровно на эту площадь, total_floor_area_sqm не меняется. Additive
|
||||||
|
# поле (default 0.0) → backward-compat: 0.0 = нежилое не моделировалось/не нужно.
|
||||||
|
office_area_sqm: float = 0.0
|
||||||
residential_area_sqm: float
|
residential_area_sqm: float
|
||||||
apartments_count: int
|
apartments_count: int
|
||||||
density: float
|
density: float
|
||||||
|
|
@ -62,6 +67,13 @@ class FinancialModel(BaseModel):
|
||||||
# ── Revenue breakdown ──────────────────────────────────────────────────────
|
# ── Revenue breakdown ──────────────────────────────────────────────────────
|
||||||
revenue_residential_rub: float
|
revenue_residential_rub: float
|
||||||
revenue_parking_rub: float
|
revenue_parking_rub: float
|
||||||
|
# Выручка нежилого (коммерция/офисы 1-го этажа) = office_area_sqm × цена нежилого
|
||||||
|
# (см. ``_OFFICE_SALE_PRICE_PER_SQM`` в financial.py). Чисто ADDITIVE выручка:
|
||||||
|
# СМР нежилого уже сидит в ``construction_rub`` (нежилая GFA входит в total_floor_area
|
||||||
|
# → construction = total_floor_area × ставка уже её покрывает; double-count'а нет).
|
||||||
|
# Нежилое ОБЛАГАЕТСЯ НДС (в отличие от льготы жилья ст.149 НК) — его value-added
|
||||||
|
# входит в базу НДС. Default 0.0 → backward-compat (нежилое не моделировалось).
|
||||||
|
revenue_office_rub: float = 0.0
|
||||||
|
|
||||||
# ── Cost cascade ───────────────────────────────────────────────────────────
|
# ── Cost cascade ───────────────────────────────────────────────────────────
|
||||||
construction_rub: float
|
construction_rub: float
|
||||||
|
|
|
||||||
|
|
@ -46,13 +46,28 @@ DCF can never silently diverge from the static cascade.
|
||||||
|
|
||||||
Cascade (in :func:`compute_financial`):
|
Cascade (in :func:`compute_financial`):
|
||||||
|
|
||||||
* **Revenue (GDV)** = residential area × sale price + parking spaces × parking price.
|
* **Revenue (GDV)** = residential area × sale price + parking spaces × parking price
|
||||||
* **Cost cascade** = construction (residential GFA + parking) + ПИР (design) +
|
+ office area × office price (нежилое 1-го этажа — коммерция/офисы).
|
||||||
networks (ТУ) + developer services + contingency + marketing/realtor + land.
|
* **Cost cascade** = construction (residential + OFFICE GFA + parking) + ПИР (design)
|
||||||
|
+ networks (ТУ) + developer services + contingency + marketing/realtor + land.
|
||||||
* **БДР / taxes** = gross margin − VAT − profit tax → net profit.
|
* **БДР / taxes** = gross margin − VAT − profit tax → net profit.
|
||||||
* **Metrics** = net ROI on cost, net margin on revenue, and DCF NPV/IRR/payback.
|
* **Metrics** = net ROI on cost, net margin on revenue, and DCF NPV/IRR/payback.
|
||||||
|
|
||||||
НДС — ОСВОБОЖДЕНИЕ ЖИЛЬЯ, НДС ТОЛЬКО НА ПАРКИНГ (ст. 149 НК РФ)
|
OFFICES / COMMERCIAL — нежилое 1-го этажа (ADDITIVE ВЫРУЧКА, БЕЗ double-count СМР)
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
TEAP теперь несёт ``office_area_sqm`` — нежилое (коммерция/офисы 1-го этажа),
|
||||||
|
ВЫРЕЗАННОЕ из GFA до расчёта жилой (см. ``_OFFICE_SHARE_OF_GFA`` в teap.py: жилая
|
||||||
|
ужимается ровно на эту площадь, total_floor_area_sqm НЕ меняется). Финмодель:
|
||||||
|
|
||||||
|
* **Выручка** ``revenue_office = office_area_sqm × _OFFICE_SALE_PRICE_PER_SQM[class]``
|
||||||
|
(умеренная премия к жилью; см. константу).
|
||||||
|
* **СМР нежилого — НЕ отдельная строка, double-count'а НЕТ.** Нежилая GFA осталась
|
||||||
|
в ``total_floor_area_sqm`` → ``construction_resid = total_floor_area_sqm × ставка
|
||||||
|
СМР`` УЖЕ её покрывает (как покрывала, когда эта площадь считалась жилой). Сдвиг
|
||||||
|
carve-out жилой ↔ нежилой меняет только классификацию площади и ВЫРУЧКУ, а СМР по
|
||||||
|
total_floor_area инвариантен. Поэтому нежилое = чисто additive ВЫРУЧКА.
|
||||||
|
|
||||||
|
НДС — ОСВОБОЖДЕНИЕ ЖИЛЬЯ, НДС НА ПАРКИНГ + НЕЖИЛОЕ (ст. 149 НК РФ)
|
||||||
------------------------------------------------------------
|
------------------------------------------------------------
|
||||||
Реализация жилых помещений и услуги застройщика по ДДУ ОСВОБОЖДЕНЫ от НДС
|
Реализация жилых помещений и услуги застройщика по ДДУ ОСВОБОЖДЕНЫ от НДС
|
||||||
(пп.22 и пп.23.1 п.3 ст. 149 НК РФ) → выходной НДС на жильё = 0. Входной НДС по
|
(пп.22 и пп.23.1 п.3 ст. 149 НК РФ) → выходной НДС на жильё = 0. Входной НДС по
|
||||||
|
|
@ -60,13 +75,19 @@ Cascade (in :func:`compute_financial`):
|
||||||
себестоимости (рыночные ориентиры ``_CONSTRUCTION_COST_PER_SQM`` даны С НДС) →
|
себестоимости (рыночные ориентиры ``_CONSTRUCTION_COST_PER_SQM`` даны С НДС) →
|
||||||
чистая нагрузка по НДС на жилую часть ≈ 0.
|
чистая нагрузка по НДС на жилую часть ≈ 0.
|
||||||
|
|
||||||
Машиноместа — НЕЖИЛЫЕ помещения: их реализация ОБЛАГАЕТСЯ НДС 20%, входной НДС
|
Машиноместа И нежилое (коммерция/офисы) — НЕЖИЛЫЕ помещения: их реализация
|
||||||
вычитается. Поэтому НДС начисляется ТОЛЬКО на нежилой value-added паркинга
|
ОБЛАГАЕТСЯ НДС 20%, входной НДС вычитается. Поэтому НДС начисляется на нежилой
|
||||||
(встроенный НДС в добавленной стоимости машиномест):
|
value-added ОБОИХ нежилых частей (встроенный НДС в добавленной стоимости):
|
||||||
|
|
||||||
vat = max(0, revenue_parking − construction_parking) × VAT_RATE / (1 + VAT_RATE)
|
parking_value_added = max(0, revenue_parking − construction_parking)
|
||||||
|
office_value_added = max(0, revenue_office − office_construction)
|
||||||
|
где office_construction = office_area_sqm × _CONSTRUCTION_COST_PER_SQM[class]
|
||||||
|
(СМР-доля нежилого по той же ставке, что и весь СМР; та же доля УЖЕ сидит
|
||||||
|
в construction_rub — здесь она лишь ВОССТАНАВЛИВАЕТСЯ как база НДС, в
|
||||||
|
каскад затрат повторно НЕ добавляется → double-count'а нет).
|
||||||
|
vat = (parking_value_added + office_value_added) × VAT_RATE / (1 + VAT_RATE)
|
||||||
|
|
||||||
Если паркинг тоже окажется под ДДУ-льготой — оценка консервативна (чуть занижает
|
Если паркинг/нежилое окажутся под ДДУ-льготой — оценка консервативна (чуть занижает
|
||||||
прибыль завышенным НДС), что соответствует honest/conservative-политике модуля.
|
прибыль завышенным НДС), что соответствует honest/conservative-политике модуля.
|
||||||
Полную механику входного/выходного НДС с эскроу-спецификой не моделируем —
|
Полную механику входного/выходного НДС с эскроу-спецификой не моделируем —
|
||||||
жильё освобождено, входной НДС встроен в gross-СМР.
|
жильё освобождено, входной НДС встроен в gross-СМР.
|
||||||
|
|
@ -80,11 +101,6 @@ bisection cannot bracket a root; we then fall back to the PR-1 annualised net-RO
|
||||||
proxy (``roi / _PROJECT_YEARS``, clamped ±1) and flag ``irr_is_proxy=True`` so the
|
proxy (``roi / _PROJECT_YEARS``, clamped ±1) and flag ``irr_is_proxy=True`` so the
|
||||||
frontend shows the «оценочный» caveat.
|
frontend shows the «оценочный» caveat.
|
||||||
|
|
||||||
OFFICES / COMMERCIAL — SKIPPED
|
|
||||||
------------------------------
|
|
||||||
TEAP carries no office/commercial area (only residential + parking), so commercial
|
|
||||||
revenue/cost is intentionally **not** modelled here. Add when TEAP gains the field.
|
|
||||||
|
|
||||||
PRICE CALIBRATION — PR-2
|
PRICE CALIBRATION — PR-2
|
||||||
------------------------
|
------------------------
|
||||||
The housing **sale price** can be calibrated to real market data: callers pass
|
The housing **sale price** can be calibrated to real market data: callers pass
|
||||||
|
|
@ -194,6 +210,26 @@ _PARKING_COST_PER_SPOT: dict[HousingClass, float] = {
|
||||||
"business": 1_800_000.0, # подземный (Excel «Авангардная 13») — себестоимость
|
"business": 1_800_000.0, # подземный (Excel «Авангардная 13») — себестоимость
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ── Цена ПРОДАЖИ нежилого (коммерция/офисы 1-го этажа), руб/кв.м по классу ─────
|
||||||
|
# НОРМАТИВ (РФ-рынок встроенно-пристроенной коммерции). Ликвидный стрит-ритейл 1-го
|
||||||
|
# этажа в городском МКД обычно продаётся С ПРЕМИЕЙ к жилью того же дома (лучшая
|
||||||
|
# проходимость/витрина, инвестиционный спрос на ГАБ — готовый арендный бизнес).
|
||||||
|
# Берём УМЕРЕННУЮ премию ~+15% к нормативу продажи жилья класса (консервативно — не
|
||||||
|
# задираем; в реальных проектах премия бывает выше, но зависит от локации/трафика):
|
||||||
|
# econom ~127k = 110k × 1.15 (доля нежилого = 0 → на выручку не влияет, но
|
||||||
|
# константа задана для полноты/будущих сценариев).
|
||||||
|
# comfort ~167k = 145k × 1.15
|
||||||
|
# business ~242k = 210k × 1.15
|
||||||
|
# СЕБЕСТОИМОСТЬ нежилого ОТДЕЛЬНО не вводим: нежилая GFA уже в total_floor_area_sqm →
|
||||||
|
# ``construction = total_floor_area × ставка СМР`` её УЖЕ покрывает (см. docstring
|
||||||
|
# раздел OFFICES). Поэтому нежилое — чисто additive ВЫРУЧКА; для базы НДС берём
|
||||||
|
# СМР-долю нежилого по той же ставке ``_CONSTRUCTION_COST_PER_SQM`` (см. ниже).
|
||||||
|
_OFFICE_SALE_PRICE_PER_SQM: dict[HousingClass, float] = {
|
||||||
|
"econom": 126_500.0, # 110_000 × 1.15
|
||||||
|
"comfort": 166_750.0, # 145_000 × 1.15
|
||||||
|
"business": 241_500.0, # 210_000 × 1.15
|
||||||
|
}
|
||||||
|
|
||||||
# ── Доли каскада затрат (источник — Excel-эталон + RU-рынок) ──────────────────
|
# ── Доли каскада затрат (источник — Excel-эталон + RU-рынок) ──────────────────
|
||||||
# ПИР (проектирование + экспертиза + РД) как доля от СМР.
|
# ПИР (проектирование + экспертиза + РД) как доля от СМР.
|
||||||
# Excel: ПИР 147М при СМР 6123М ≈ 2.4% -> округлённо 2.5%.
|
# Excel: ПИР 147М при СМР 6123М ≈ 2.4% -> округлённо 2.5%.
|
||||||
|
|
@ -557,12 +593,13 @@ def compute_financial(
|
||||||
) -> FinancialModel:
|
) -> FinancialModel:
|
||||||
"""Свести ТЭП + класс + стоимость земли в полный :class:`FinancialModel` с DCF.
|
"""Свести ТЭП + класс + стоимость земли в полный :class:`FinancialModel` с DCF.
|
||||||
|
|
||||||
Полный девелоперский каскад: выручка (жильё + паркинг) → каскад затрат (СМР +
|
Полный девелоперский каскад: выручка (жильё + паркинг + нежилое 1-го этажа) →
|
||||||
ПИР + сети + услуги заказчика + резерв + маркетинг + земля) → БДР с НДС и
|
каскад затрат (СМР + ПИР + сети + услуги заказчика + резерв + маркетинг + земля) →
|
||||||
налогом на прибыль → чистая прибыль + ROI. Поверх статики — помесячный DCF
|
БДР с НДС и налогом на прибыль → чистая прибыль + ROI. Поверх статики — помесячный
|
||||||
(NPV/IRR/PBP) по дефолт-графику фаз (см. docstring модуля). НДС — жильё
|
DCF (NPV/IRR/PBP) по дефолт-графику фаз (см. docstring модуля). НДС — жильё
|
||||||
освобождено (ст. 149 НК РФ), начисляется только на паркинг; офисов нет в
|
освобождено (ст. 149 НК РФ), начисляется на нежилые части (паркинг + коммерция/
|
||||||
TEAP — пропущены.
|
офисы); СМР нежилого уже в total_floor_area → отдельной строки затрат нет
|
||||||
|
(см. раздел OFFICES в docstring модуля).
|
||||||
|
|
||||||
Чистая функция: НЕ ходит в БД. Калибровку цены продажи жилья делает API-слой
|
Чистая функция: НЕ ходит в БД. Калибровку цены продажи жилья делает API-слой
|
||||||
(lookup рынка) и прокидывает сюда через ``market_price_per_sqm``.
|
(lookup рынка) и прокидывает сюда через ``market_price_per_sqm``.
|
||||||
|
|
@ -605,13 +642,20 @@ def compute_financial(
|
||||||
# подземный); НОРМА м/м на квартиру задаётся в teap (не здесь).
|
# подземный); НОРМА м/м на квартиру задаётся в teap (не здесь).
|
||||||
parking_price = _PARKING_PRICE_PER_SPOT[housing_class]
|
parking_price = _PARKING_PRICE_PER_SPOT[housing_class]
|
||||||
parking_cost = _PARKING_COST_PER_SPOT[housing_class]
|
parking_cost = _PARKING_COST_PER_SPOT[housing_class]
|
||||||
|
# Нежилое (коммерция/офисы 1-го этажа) — цена продажи по классу (премия к жилью).
|
||||||
|
office_price = _OFFICE_SALE_PRICE_PER_SQM[housing_class]
|
||||||
|
|
||||||
# ── Выручка (GDV) ──────────────────────────────────────────────────────────
|
# ── Выручка (GDV) ──────────────────────────────────────────────────────────
|
||||||
revenue_residential = teap.residential_area_sqm * sale_price
|
revenue_residential = teap.residential_area_sqm * sale_price
|
||||||
revenue_parking = teap.parking_spaces * parking_price
|
revenue_parking = teap.parking_spaces * parking_price
|
||||||
revenue = revenue_residential + revenue_parking
|
# Нежилое — чисто ADDITIVE выручка (СМР по нему уже в construction ниже).
|
||||||
|
revenue_office = teap.office_area_sqm * office_price
|
||||||
|
revenue = revenue_residential + revenue_parking + revenue_office
|
||||||
|
|
||||||
# ── Затраты (каскад) ───────────────────────────────────────────────────────
|
# ── Затраты (каскад) ───────────────────────────────────────────────────────
|
||||||
|
# ВАЖНО: ``total_floor_area_sqm`` — это ВСЯ GFA (жилая + нежилое 1-го этажа; см.
|
||||||
|
# teap: нежилое вырезано из ЖИЛОЙ, но осталось в total). Значит СМР по нежилой GFA
|
||||||
|
# УЖЕ покрыт здесь → отдельной строки затрат нежилого НЕ добавляем (no double-count).
|
||||||
construction_resid = teap.total_floor_area_sqm * construction_cost
|
construction_resid = teap.total_floor_area_sqm * construction_cost
|
||||||
construction_parking = teap.parking_spaces * parking_cost
|
construction_parking = teap.parking_spaces * parking_cost
|
||||||
construction = construction_resid + construction_parking
|
construction = construction_resid + construction_parking
|
||||||
|
|
@ -627,12 +671,17 @@ def compute_financial(
|
||||||
gross_margin = revenue - cost # валовая (как было)
|
gross_margin = revenue - cost # валовая (как было)
|
||||||
# НДС: жилые помещения и услуги застройщика по ДДУ освобождены (пп.22 / пп.23.1
|
# НДС: жилые помещения и услуги застройщика по ДДУ освобождены (пп.22 / пп.23.1
|
||||||
# п.3 ст. 149 НК РФ) → output-НДС на жильё = 0, а невозмещаемый входной НДС по СМР
|
# п.3 ст. 149 НК РФ) → output-НДС на жильё = 0, а невозмещаемый входной НДС по СМР
|
||||||
# уже сидит в gross-ставках себестоимости. НДС начисляется ТОЛЬКО на нежилую часть
|
# уже сидит в gross-ставках себестоимости. НДС начисляется на НЕЖИЛЫЕ части —
|
||||||
# (машиноместа — нежилые, их реализация облагается): встроенный НДС в value-added
|
# машиноместа И коммерция/офисы 1-го этажа (их реализация облагается): встроенный
|
||||||
# паркинга. Если паркинг тоже окажется по ДДУ-льготе — оценка консервативна (чуть
|
# НДС в их value-added. Если паркинг/нежилое тоже окажутся по ДДУ-льготе — оценка
|
||||||
# занижает прибыль), что соответствует «honest/conservative» политике модуля.
|
# консервативна (чуть занижает прибыль), что соответствует «honest» политике модуля.
|
||||||
parking_value_added = revenue_parking - construction_parking
|
parking_value_added = revenue_parking - construction_parking
|
||||||
vat = max(0.0, parking_value_added) * _VAT_RATE / (1.0 + _VAT_RATE)
|
# СМР-доля нежилого по той же ставке СМР (она УЖЕ внутри construction_resid выше —
|
||||||
|
# здесь лишь восстанавливаем её как базу НДС, в каскад затрат повторно НЕ кладём).
|
||||||
|
office_construction = teap.office_area_sqm * construction_cost
|
||||||
|
office_value_added = revenue_office - office_construction
|
||||||
|
nonresidential_value_added = max(0.0, parking_value_added) + max(0.0, office_value_added)
|
||||||
|
vat = nonresidential_value_added * _VAT_RATE / (1.0 + _VAT_RATE)
|
||||||
profit_before_tax = gross_margin - vat
|
profit_before_tax = gross_margin - vat
|
||||||
profit_tax = max(0.0, profit_before_tax) * _PROFIT_TAX_RATE
|
profit_tax = max(0.0, profit_before_tax) * _PROFIT_TAX_RATE
|
||||||
net_profit = profit_before_tax - profit_tax
|
net_profit = profit_before_tax - profit_tax
|
||||||
|
|
@ -719,6 +768,7 @@ def compute_financial(
|
||||||
# revenue breakdown
|
# revenue breakdown
|
||||||
revenue_residential_rub=round(revenue_residential, 2),
|
revenue_residential_rub=round(revenue_residential, 2),
|
||||||
revenue_parking_rub=round(revenue_parking, 2),
|
revenue_parking_rub=round(revenue_parking, 2),
|
||||||
|
revenue_office_rub=round(revenue_office, 2),
|
||||||
# cost cascade
|
# cost cascade
|
||||||
construction_rub=round(construction, 2),
|
construction_rub=round(construction, 2),
|
||||||
pir_rub=round(pir, 2),
|
pir_rub=round(pir, 2),
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,11 @@ the ``TEAP`` contract block with *real* numbers:
|
||||||
|
|
||||||
* ``built_area_sqm`` — пятно застройки (сумма площадей footprint-ов).
|
* ``built_area_sqm`` — пятно застройки (сумма площадей footprint-ов).
|
||||||
* ``total_floor_area_sqm`` — общая поэтажная площадь (GFA) = пятно * этажность.
|
* ``total_floor_area_sqm`` — общая поэтажная площадь (GFA) = пятно * этажность.
|
||||||
* ``residential_area_sqm`` — продаваемая жилая = GFA * коэффициент эффективности
|
* ``office_area_sqm`` — нежилое (встроенно-пристроенный 1-й этаж: коммерция/
|
||||||
(вычет МОП/лестниц/тех.помещений; зависит от класса жилья).
|
офисы) = GFA * нормативная доля нежилого (зависит от класса жилья). Вырезается
|
||||||
|
из GFA ПЕРЕД расчётом жилой → жилая ужимается на эту площадь (total GFA не меняется).
|
||||||
|
* ``residential_area_sqm`` — продаваемая жилая = (GFA − нежилое) * коэффициент
|
||||||
|
эффективности (вычет МОП/лестниц/тех.помещений; зависит от класса жилья).
|
||||||
* ``apartments_count`` — жилая / средняя площадь квартиры (зависит от класса).
|
* ``apartments_count`` — жилая / средняя площадь квартиры (зависит от класса).
|
||||||
* ``density`` — плотность застройки = FAR = GFA / площадь участка.
|
* ``density`` — плотность застройки = FAR = GFA / площадь участка.
|
||||||
* ``parking_spaces`` — машиноместа по упрощённой норме (мест на квартиру).
|
* ``parking_spaces`` — машиноместа по упрощённой норме (мест на квартиру).
|
||||||
|
|
@ -52,6 +55,27 @@ _PARKING_PER_APARTMENT: dict[HousingClass, float] = {
|
||||||
"business": 1.5,
|
"business": 1.5,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ── Доля нежилого (коммерция/офисы 1-го этажа) в GFA по классу ─────────────────
|
||||||
|
# НОРМАТИВ (не догадка): городские МКД РФ почти всегда несут встроенно-пристроенные
|
||||||
|
# нежилые помещения 1-го этажа (магазины/услуги/офисы). По обзорам застройки РФ их
|
||||||
|
# доля в общей поэтажной площади (GFA) типично 3–8% для городских МКД (1-й этаж из
|
||||||
|
# 9–25). Берём КОНСЕРВАТИВНЫЕ значения нижней границы диапазона:
|
||||||
|
# econom 0.0 — массовый эконом часто строят БЕЗ коммерции (спальные районы,
|
||||||
|
# "квартиры на 1-м этаже"); нулём не завышаем выручку.
|
||||||
|
# comfort 0.05 — комфорт почти всегда имеет коммерческий стрит-ритейл 1-го этажа.
|
||||||
|
# business 0.05 — бизнес: представительный 1-й этаж (лобби+коммерция), но та же
|
||||||
|
# умеренная доля (бОльшая часть нижних уровней — лобби/МОП, а не
|
||||||
|
# продаваемая коммерция) — консервативно не задираем.
|
||||||
|
# Нежилое ВЫРЕЗАЕТСЯ из GFA до расчёта жилой (жилая ужимается ровно на эту площадь),
|
||||||
|
# total GFA при этом не меняется — это и есть фикс реализма (раньше ВСЯ непаркинговая
|
||||||
|
# GFA считалась жилой, чуть завышая жилую площадь). Источник доли — отраслевой обзор,
|
||||||
|
# НЕ проектный факт → caveat в финмодели честно это помечает.
|
||||||
|
_OFFICE_SHARE_OF_GFA: dict[HousingClass, float] = {
|
||||||
|
"econom": 0.0,
|
||||||
|
"comfort": 0.05,
|
||||||
|
"business": 0.05,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def compute_teap(
|
def compute_teap(
|
||||||
*,
|
*,
|
||||||
|
|
@ -71,8 +95,15 @@ def compute_teap(
|
||||||
built_area = float(sum(fp.area for fp in footprints))
|
built_area = float(sum(fp.area for fp in footprints))
|
||||||
total_floor_area = built_area * max(0, floors)
|
total_floor_area = built_area * max(0, floors)
|
||||||
|
|
||||||
|
# Нежилое (коммерция/офисы 1-го этажа) вырезаем из GFA ДО расчёта жилой: жилая
|
||||||
|
# считается только по ОСТАВШЕЙСЯ (непаркинговой, нежилой-исключённой) GFA. total
|
||||||
|
# GFA не меняется — нежилое остаётся в нём (СМР по нему уже покрыт в финмодели).
|
||||||
|
office_share = _OFFICE_SHARE_OF_GFA[housing_class]
|
||||||
|
office_area = total_floor_area * office_share
|
||||||
|
residential_gfa = total_floor_area - office_area
|
||||||
|
|
||||||
efficiency = _EFFICIENCY_BY_CLASS[housing_class]
|
efficiency = _EFFICIENCY_BY_CLASS[housing_class]
|
||||||
residential_area = total_floor_area * efficiency
|
residential_area = residential_gfa * efficiency
|
||||||
|
|
||||||
avg_apartment = _AVG_APARTMENT_SQM[housing_class]
|
avg_apartment = _AVG_APARTMENT_SQM[housing_class]
|
||||||
apartments_count = math.floor(residential_area / avg_apartment) if avg_apartment else 0
|
apartments_count = math.floor(residential_area / avg_apartment) if avg_apartment else 0
|
||||||
|
|
@ -86,15 +117,17 @@ def compute_teap(
|
||||||
teap = TEAP(
|
teap = TEAP(
|
||||||
built_area_sqm=round(built_area, 1),
|
built_area_sqm=round(built_area, 1),
|
||||||
total_floor_area_sqm=round(total_floor_area, 1),
|
total_floor_area_sqm=round(total_floor_area, 1),
|
||||||
|
office_area_sqm=round(office_area, 1),
|
||||||
residential_area_sqm=round(residential_area, 1),
|
residential_area_sqm=round(residential_area, 1),
|
||||||
apartments_count=apartments_count,
|
apartments_count=apartments_count,
|
||||||
density=round(density, 3),
|
density=round(density, 3),
|
||||||
parking_spaces=parking_spaces,
|
parking_spaces=parking_spaces,
|
||||||
)
|
)
|
||||||
logger.info(
|
logger.info(
|
||||||
"TEAP: built=%.0f GFA=%.0f resid=%.0f apts=%d FAR=%.2f parking=%d",
|
"TEAP: built=%.0f GFA=%.0f office=%.0f resid=%.0f apts=%d FAR=%.2f parking=%d",
|
||||||
teap.built_area_sqm,
|
teap.built_area_sqm,
|
||||||
teap.total_floor_area_sqm,
|
teap.total_floor_area_sqm,
|
||||||
|
teap.office_area_sqm,
|
||||||
teap.residential_area_sqm,
|
teap.residential_area_sqm,
|
||||||
teap.apartments_count,
|
teap.apartments_count,
|
||||||
teap.density,
|
teap.density,
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,7 @@ from app.services.generative.financial import compute_financial
|
||||||
from app.services.generative.teap import (
|
from app.services.generative.teap import (
|
||||||
_AVG_APARTMENT_SQM,
|
_AVG_APARTMENT_SQM,
|
||||||
_EFFICIENCY_BY_CLASS,
|
_EFFICIENCY_BY_CLASS,
|
||||||
|
_OFFICE_SHARE_OF_GFA,
|
||||||
_PARKING_PER_APARTMENT,
|
_PARKING_PER_APARTMENT,
|
||||||
HousingClass,
|
HousingClass,
|
||||||
)
|
)
|
||||||
|
|
@ -48,7 +49,8 @@ _FINANCIAL_CAVEAT: str = (
|
||||||
"НСПД/ПЗЗ (КСИТ/высота/%застройки), НЕ по реальной проектной концепции. "
|
"НСПД/ПЗЗ (КСИТ/высота/%застройки), НЕ по реальной проектной концепции. "
|
||||||
"ТЭП синтезирован из предельных параметров зоны; класс жилья и темп продаж — "
|
"ТЭП синтезирован из предельных параметров зоны; класс жилья и темп продаж — "
|
||||||
"рыночные/нормативные прокси; land_cost = кадастровая стоимость (не рыночная); "
|
"рыночные/нормативные прокси; land_cost = кадастровая стоимость (не рыночная); "
|
||||||
"график фаз — типовой."
|
"график фаз — типовой. Доля нежилого (коммерция/офисы 1-го этажа) — нормативный "
|
||||||
|
"прокси по классу (комфорт/бизнес ~5% GFA, эконом 0%), не проектный факт."
|
||||||
)
|
)
|
||||||
|
|
||||||
# ── Класс жилья по цене продажи, руб/кв.м (рыночный прокси) ─────────────────────
|
# ── Класс жилья по цене продажи, руб/кв.м (рыночный прокси) ─────────────────────
|
||||||
|
|
@ -101,7 +103,9 @@ def synthesize_teap_from_buildability(
|
||||||
``max_building_pct`` + ``max_floors`` → ``GFA = area × (pct/100) × floors``.
|
``max_building_pct`` + ``max_floors`` → ``GFA = area × (pct/100) × floors``.
|
||||||
* ``built`` (пятно) = ``area × (max_building_pct/100)``; если % нет →
|
* ``built`` (пятно) = ``area × (max_building_pct/100)``; если % нет →
|
||||||
оценка ``GFA / max_floors`` (или ``GFA`` при отсутствии этажности).
|
оценка ``GFA / max_floors`` (или ``GFA`` при отсутствии этажности).
|
||||||
* ``residential`` = ``GFA × efficiency[class]`` (вычет МОП/тех по классу).
|
* ``office`` (нежилое 1-го этажа) = ``GFA × office_share[class]``, вырезается из
|
||||||
|
GFA до расчёта жилой (зеркалит ``compute_teap``; см. ``_OFFICE_SHARE_OF_GFA``).
|
||||||
|
* ``residential`` = ``(GFA − office) × efficiency[class]`` (вычет МОП/тех по классу).
|
||||||
* ``apartments`` = ``floor(residential / avg_apt[class])``.
|
* ``apartments`` = ``floor(residential / avg_apt[class])``.
|
||||||
* ``parking`` = ``ceil(apartments × parking_norm[class])``.
|
* ``parking`` = ``ceil(apartments × parking_norm[class])``.
|
||||||
* ``density`` = ``GFA / area`` (= ``max_far``).
|
* ``density`` = ``GFA / area`` (= ``max_far``).
|
||||||
|
|
@ -136,8 +140,14 @@ def synthesize_teap_from_buildability(
|
||||||
else:
|
else:
|
||||||
built_area = gfa
|
built_area = gfa
|
||||||
|
|
||||||
|
# Нежилое (коммерция/офисы 1-го этажа) вырезаем из GFA до расчёта жилой — точно
|
||||||
|
# как compute_teap: жилая считается по ОСТАВШЕЙСЯ GFA, total (gfa) не меняется.
|
||||||
|
office_share = _OFFICE_SHARE_OF_GFA[housing_class]
|
||||||
|
office_area = gfa * office_share
|
||||||
|
residential_gfa = gfa - office_area
|
||||||
|
|
||||||
efficiency = _EFFICIENCY_BY_CLASS[housing_class]
|
efficiency = _EFFICIENCY_BY_CLASS[housing_class]
|
||||||
residential_area = gfa * efficiency
|
residential_area = residential_gfa * efficiency
|
||||||
|
|
||||||
avg_apartment = _AVG_APARTMENT_SQM[housing_class]
|
avg_apartment = _AVG_APARTMENT_SQM[housing_class]
|
||||||
apartments_count = math.floor(residential_area / avg_apartment) if avg_apartment else 0
|
apartments_count = math.floor(residential_area / avg_apartment) if avg_apartment else 0
|
||||||
|
|
@ -151,6 +161,7 @@ def synthesize_teap_from_buildability(
|
||||||
return TEAP(
|
return TEAP(
|
||||||
built_area_sqm=round(built_area, 1),
|
built_area_sqm=round(built_area, 1),
|
||||||
total_floor_area_sqm=round(gfa, 1),
|
total_floor_area_sqm=round(gfa, 1),
|
||||||
|
office_area_sqm=round(office_area, 1),
|
||||||
residential_area_sqm=round(residential_area, 1),
|
residential_area_sqm=round(residential_area, 1),
|
||||||
apartments_count=apartments_count,
|
apartments_count=apartments_count,
|
||||||
density=round(density, 3),
|
density=round(density, 3),
|
||||||
|
|
@ -293,6 +304,7 @@ def synthesize_parcel_financial(
|
||||||
"teap_synth": {
|
"teap_synth": {
|
||||||
"residential_area_sqm": teap.residential_area_sqm,
|
"residential_area_sqm": teap.residential_area_sqm,
|
||||||
"total_floor_area_sqm": teap.total_floor_area_sqm,
|
"total_floor_area_sqm": teap.total_floor_area_sqm,
|
||||||
|
"office_area_sqm": teap.office_area_sqm,
|
||||||
"parking_spaces": teap.parking_spaces,
|
"parking_spaces": teap.parking_spaces,
|
||||||
"apartments_count": teap.apartments_count,
|
"apartments_count": teap.apartments_count,
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -131,6 +131,7 @@ def test_concepts_response_matches_contract_keys() -> None:
|
||||||
assert set(variant["teap"].keys()) == {
|
assert set(variant["teap"].keys()) == {
|
||||||
"built_area_sqm",
|
"built_area_sqm",
|
||||||
"total_floor_area_sqm",
|
"total_floor_area_sqm",
|
||||||
|
"office_area_sqm",
|
||||||
"residential_area_sqm",
|
"residential_area_sqm",
|
||||||
"apartments_count",
|
"apartments_count",
|
||||||
"density",
|
"density",
|
||||||
|
|
@ -145,6 +146,7 @@ def test_concepts_response_matches_contract_keys() -> None:
|
||||||
# revenue breakdown
|
# revenue breakdown
|
||||||
"revenue_residential_rub",
|
"revenue_residential_rub",
|
||||||
"revenue_parking_rub",
|
"revenue_parking_rub",
|
||||||
|
"revenue_office_rub",
|
||||||
# cost cascade
|
# cost cascade
|
||||||
"construction_rub",
|
"construction_rub",
|
||||||
"pir_rub",
|
"pir_rub",
|
||||||
|
|
|
||||||
|
|
@ -26,10 +26,11 @@ from app.services.generative.financial import (
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _teap(residential: float, gfa: float, parking: int = 10) -> TEAP:
|
def _teap(residential: float, gfa: float, parking: int = 10, office: float = 0.0) -> TEAP:
|
||||||
return TEAP(
|
return TEAP(
|
||||||
built_area_sqm=100.0,
|
built_area_sqm=100.0,
|
||||||
total_floor_area_sqm=gfa,
|
total_floor_area_sqm=gfa,
|
||||||
|
office_area_sqm=office,
|
||||||
residential_area_sqm=residential,
|
residential_area_sqm=residential,
|
||||||
apartments_count=10,
|
apartments_count=10,
|
||||||
density=1.0,
|
density=1.0,
|
||||||
|
|
@ -213,6 +214,118 @@ def test_vat_invariant_holds_with_parking_vat() -> None:
|
||||||
assert abs(sum(cf) - model.net_profit_rub) < 1.0
|
assert abs(sum(cf) - model.net_profit_rub) < 1.0
|
||||||
|
|
||||||
|
|
||||||
|
# ── НЕЖИЛОЕ (коммерция/офисы 1-го этажа) ────────────────────────────────────────
|
||||||
|
|
||||||
|
|
||||||
|
def test_office_revenue_zero_when_no_office_area() -> None:
|
||||||
|
# Нет нежилого (office=0, как у эконома/spot) → revenue_office_rub == 0, выручка =
|
||||||
|
# жильё + паркинг (без нежилого слагаемого).
|
||||||
|
t = _teap(residential=10_000.0, gfa=11_000.0, parking=50, office=0.0)
|
||||||
|
model = financial.compute_financial(
|
||||||
|
teap=t, housing_class="comfort", land_cost_rub=200_000_000.0, development_type="mid_rise"
|
||||||
|
)
|
||||||
|
assert model.revenue_office_rub == 0.0
|
||||||
|
assert model.revenue_rub == pytest.approx(
|
||||||
|
model.revenue_residential_rub + model.revenue_parking_rub, abs=0.01
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_office_revenue_added_when_office_area_present() -> None:
|
||||||
|
# office > 0 → revenue_office_rub = office_area × цена нежилого класса; общая выручка
|
||||||
|
# = жильё + паркинг + нежилое.
|
||||||
|
cls = "comfort"
|
||||||
|
office = 550.0 # ~5% от GFA 11000
|
||||||
|
t = _teap(residential=9_450.0, gfa=11_000.0, parking=50, office=office)
|
||||||
|
model = financial.compute_financial(
|
||||||
|
teap=t, housing_class=cls, land_cost_rub=200_000_000.0, development_type="mid_rise"
|
||||||
|
)
|
||||||
|
expected_office = office * financial._OFFICE_SALE_PRICE_PER_SQM[cls]
|
||||||
|
assert model.revenue_office_rub == pytest.approx(round(expected_office, 2), abs=0.01)
|
||||||
|
assert model.revenue_office_rub > 0.0
|
||||||
|
assert model.revenue_rub == pytest.approx(
|
||||||
|
model.revenue_residential_rub + model.revenue_parking_rub + model.revenue_office_rub,
|
||||||
|
abs=0.01,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_office_construction_not_double_counted() -> None:
|
||||||
|
# СМР нежилого НЕ добавляется отдельной строкой: construction_rub зависит ТОЛЬКО от
|
||||||
|
# total_floor_area (+паркинг), а не от office_area. Два проекта с одинаковым GFA, но
|
||||||
|
# разной долей нежилого → одинаковый construction_rub (нежилое лишь меняет выручку).
|
||||||
|
no_office = _teap(residential=8_580.0, gfa=11_000.0, parking=50, office=0.0)
|
||||||
|
with_office = _teap(residential=8_151.0, gfa=11_000.0, parking=50, office=550.0)
|
||||||
|
m_no = financial.compute_financial(
|
||||||
|
teap=no_office, housing_class="comfort", land_cost_rub=None, development_type="mid_rise"
|
||||||
|
)
|
||||||
|
m_off = financial.compute_financial(
|
||||||
|
teap=with_office, housing_class="comfort", land_cost_rub=None, development_type="mid_rise"
|
||||||
|
)
|
||||||
|
# СМР идентичен (нежилое уже в total_floor_area → double-count'а нет).
|
||||||
|
assert m_off.construction_rub == m_no.construction_rub
|
||||||
|
# Но нежилое даёт дополнительную выручку.
|
||||||
|
assert m_off.revenue_office_rub > 0.0
|
||||||
|
assert m_no.revenue_office_rub == 0.0
|
||||||
|
|
||||||
|
|
||||||
|
def test_office_vat_includes_office_value_added() -> None:
|
||||||
|
# НДС с нежилым = НДС(паркинг value-added) + НДС(office value-added). office_value_added
|
||||||
|
# = revenue_office − office_area × ставка СМР. Проверяем точную формулу.
|
||||||
|
cls = "comfort"
|
||||||
|
parking = 50
|
||||||
|
office = 550.0
|
||||||
|
t = _teap(residential=8_151.0, gfa=11_000.0, parking=parking, office=office)
|
||||||
|
model = financial.compute_financial(
|
||||||
|
teap=t, housing_class=cls, land_cost_rub=200_000_000.0, development_type="mid_rise"
|
||||||
|
)
|
||||||
|
revenue_parking = parking * financial._PARKING_PRICE_PER_SPOT[cls]
|
||||||
|
construction_parking = parking * financial._PARKING_COST_PER_SPOT[cls]
|
||||||
|
parking_va = max(0.0, revenue_parking - construction_parking)
|
||||||
|
revenue_office = office * financial._OFFICE_SALE_PRICE_PER_SQM[cls]
|
||||||
|
office_construction = office * financial._CONSTRUCTION_COST_PER_SQM[cls]
|
||||||
|
office_va = max(0.0, revenue_office - office_construction)
|
||||||
|
expected_vat = (parking_va + office_va) * financial._VAT_RATE / (1.0 + financial._VAT_RATE)
|
||||||
|
assert model.vat_rub == pytest.approx(round(expected_vat, 2), abs=0.01)
|
||||||
|
# НДС с нежилым строго БОЛЬШЕ, чем НДС только паркинга (нежилое добавляет базу).
|
||||||
|
parking_only_vat = parking_va * financial._VAT_RATE / (1.0 + financial._VAT_RATE)
|
||||||
|
assert model.vat_rub > parking_only_vat
|
||||||
|
|
||||||
|
|
||||||
|
def test_office_invariant_cashflow_sum_equals_net_profit() -> None:
|
||||||
|
# Инвариант Σ cashflow == net_profit держится с нежилым (office-выручка течёт по той
|
||||||
|
# же revenue → cashflow дорожке; НДС нежилого прокидывается в построитель потока).
|
||||||
|
t = _teap(residential=8_151.0, gfa=11_000.0, parking=50, office=550.0)
|
||||||
|
model = financial.compute_financial(
|
||||||
|
teap=t, housing_class="comfort", land_cost_rub=200_000_000.0, development_type="mid_rise"
|
||||||
|
)
|
||||||
|
assert model.revenue_office_rub > 0.0
|
||||||
|
cf = _rebuild_cashflow(model, construction_months=36, residential_area_sqm=8_151.0)
|
||||||
|
assert abs(sum(cf) - model.net_profit_rub) < 1.0
|
||||||
|
|
||||||
|
|
||||||
|
def test_office_carveout_shrinks_residential_via_compute_teap() -> None:
|
||||||
|
# РЕАЛИЗМ-ФИКС на уровне compute_teap: нежилое вырезается из GFA → жилая ужимается на
|
||||||
|
# office_area × efficiency, а total_floor_area не меняется. Comfort (share 0.05) vs
|
||||||
|
# econom (share 0.0) при той же геометрии: у comfort office>0, жилая ниже на office-вклад.
|
||||||
|
from shapely.geometry import box
|
||||||
|
|
||||||
|
from app.services.generative import teap as teap_mod
|
||||||
|
|
||||||
|
footprint = box(0, 0, 20, 10) # 200 кв.м пятна
|
||||||
|
comfort = teap_mod.compute_teap(
|
||||||
|
footprints=[footprint], floors=10, site_area_sqm=1000.0, housing_class="comfort"
|
||||||
|
)
|
||||||
|
# GFA 2000; office = 2000 × 0.05 = 100; residential = (2000−100) × 0.78 = 1482.
|
||||||
|
assert comfort.total_floor_area_sqm == 2000.0
|
||||||
|
assert comfort.office_area_sqm == 100.0
|
||||||
|
assert comfort.residential_area_sqm == pytest.approx(1482.0, abs=0.1)
|
||||||
|
# econom: share 0 → office 0, residential по полной GFA × 0.82 = 1640 (unchanged).
|
||||||
|
econom = teap_mod.compute_teap(
|
||||||
|
footprints=[footprint], floors=10, site_area_sqm=1000.0, housing_class="econom"
|
||||||
|
)
|
||||||
|
assert econom.office_area_sqm == 0.0
|
||||||
|
assert econom.residential_area_sqm == pytest.approx(1640.0, abs=0.1)
|
||||||
|
|
||||||
|
|
||||||
# ── Настоящий IRR vs proxy fallback ─────────────────────────────────────────────
|
# ── Настоящий IRR vs proxy fallback ─────────────────────────────────────────────
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -27,10 +27,12 @@ def test_teap_basic_arithmetic() -> None:
|
||||||
assert result.total_floor_area_sqm == 2000.0
|
assert result.total_floor_area_sqm == 2000.0
|
||||||
# FAR = GFA / участок.
|
# FAR = GFA / участок.
|
||||||
assert result.density == 2.0
|
assert result.density == 2.0
|
||||||
# Жилая = GFA * efficiency(comfort=0.78).
|
# Нежилое (comfort share 0.05) вырезается из GFA: office = 2000 * 0.05 = 100.
|
||||||
assert result.residential_area_sqm == 1560.0
|
assert result.office_area_sqm == 100.0
|
||||||
|
# Жилая = (GFA − нежилое) * efficiency(comfort=0.78) = 1900 * 0.78 = 1482.
|
||||||
|
assert result.residential_area_sqm == 1482.0
|
||||||
# Квартир = floor(жилая / avg(comfort=55)).
|
# Квартир = floor(жилая / avg(comfort=55)).
|
||||||
assert result.apartments_count == int(1560.0 // 55.0)
|
assert result.apartments_count == int(1482.0 // 55.0)
|
||||||
# Парковка = ceil(квартир * 1.0).
|
# Парковка = ceil(квартир * 1.0).
|
||||||
assert result.parking_spaces == result.apartments_count
|
assert result.parking_spaces == result.apartments_count
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -58,8 +58,10 @@ def test_teap_from_max_far() -> None:
|
||||||
assert teap is not None
|
assert teap is not None
|
||||||
# GFA = area × far
|
# GFA = area × far
|
||||||
assert teap.total_floor_area_sqm == round(area * far, 1)
|
assert teap.total_floor_area_sqm == round(area * far, 1)
|
||||||
# residential = GFA × efficiency[comfort=0.78]
|
# Нежилое (comfort share 0.05) вырезается из GFA: office = GFA × 0.05.
|
||||||
assert teap.residential_area_sqm == round(area * far * 0.78, 1)
|
assert teap.office_area_sqm == round(area * far * 0.05, 1)
|
||||||
|
# residential = (GFA − office) × efficiency[comfort=0.78]
|
||||||
|
assert teap.residential_area_sqm == round(area * far * (1.0 - 0.05) * 0.78, 1)
|
||||||
# density = FAR
|
# density = FAR
|
||||||
assert teap.density == round(far, 3)
|
assert teap.density == round(far, 3)
|
||||||
assert teap.apartments_count > 0
|
assert teap.apartments_count > 0
|
||||||
|
|
|
||||||
|
|
@ -3550,6 +3550,11 @@ export interface components {
|
||||||
revenue_residential_rub: number;
|
revenue_residential_rub: number;
|
||||||
/** Revenue Parking Rub */
|
/** Revenue Parking Rub */
|
||||||
revenue_parking_rub: number;
|
revenue_parking_rub: number;
|
||||||
|
/**
|
||||||
|
* Revenue Office Rub
|
||||||
|
* @default 0
|
||||||
|
*/
|
||||||
|
revenue_office_rub: number;
|
||||||
/** Construction Rub */
|
/** Construction Rub */
|
||||||
construction_rub: number;
|
construction_rub: number;
|
||||||
/** Pir Rub */
|
/** Pir Rub */
|
||||||
|
|
@ -4541,6 +4546,11 @@ export interface components {
|
||||||
built_area_sqm: number;
|
built_area_sqm: number;
|
||||||
/** Total Floor Area Sqm */
|
/** Total Floor Area Sqm */
|
||||||
total_floor_area_sqm: number;
|
total_floor_area_sqm: number;
|
||||||
|
/**
|
||||||
|
* Office Area Sqm
|
||||||
|
* @default 0
|
||||||
|
*/
|
||||||
|
office_area_sqm: number;
|
||||||
/** Residential Area Sqm */
|
/** Residential Area Sqm */
|
||||||
residential_area_sqm: number;
|
residential_area_sqm: number;
|
||||||
/** Apartments Count */
|
/** Apartments Count */
|
||||||
|
|
|
||||||
|
|
@ -509,6 +509,9 @@ export interface ParcelFinancialEstimate {
|
||||||
margin_pct: number;
|
margin_pct: number;
|
||||||
irr: number;
|
irr: number;
|
||||||
irr_is_proxy: boolean;
|
irr_is_proxy: boolean;
|
||||||
|
// Нежилое (коммерция/офисы 1-го этажа) — additive выручка (СМР уже в construction).
|
||||||
|
// 0 когда нежилое не моделировалось (econom/spot share 0 либо старый кэш).
|
||||||
|
revenue_office_rub: number;
|
||||||
npv_rub: number;
|
npv_rub: number;
|
||||||
payback_months: number | null;
|
payback_months: number | null;
|
||||||
discount_rate_used: number;
|
discount_rate_used: number;
|
||||||
|
|
@ -525,6 +528,9 @@ export interface ParcelFinancialEstimate {
|
||||||
teap_synth: {
|
teap_synth: {
|
||||||
residential_area_sqm: number;
|
residential_area_sqm: number;
|
||||||
total_floor_area_sqm: number;
|
total_floor_area_sqm: number;
|
||||||
|
// Нежилое (коммерция/офисы 1-го этажа), кв.м. Вырезано из GFA до расчёта жилой
|
||||||
|
// (total_floor_area не меняется). 0 когда нежилое не моделировалось.
|
||||||
|
office_area_sqm: number;
|
||||||
parking_spaces: number;
|
parking_spaces: number;
|
||||||
apartments_count: number;
|
apartments_count: number;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue