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-директории).
114 lines
4.2 KiB
Python
114 lines
4.2 KiB
Python
"""Stage 1c tests — ТЭП and financial model arithmetic.
|
|
|
|
Pure-arithmetic unit tests against known footprint geometry: GFA, residential area,
|
|
apartment count, FAR, parking, revenue/cost/margin and the IRR-proxy clamp.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from shapely.geometry import box
|
|
|
|
from app.schemas.concept import TEAP
|
|
from app.services.generative import financial, teap
|
|
|
|
# Один прямоугольник 20 x 10 = 200 кв.м пятна.
|
|
_FOOTPRINT = box(0, 0, 20, 10)
|
|
|
|
|
|
def test_teap_basic_arithmetic() -> None:
|
|
result = teap.compute_teap(
|
|
footprints=[_FOOTPRINT],
|
|
floors=10,
|
|
site_area_sqm=1000.0,
|
|
housing_class="comfort",
|
|
)
|
|
assert result.built_area_sqm == 200.0
|
|
# GFA = пятно * этажность.
|
|
assert result.total_floor_area_sqm == 2000.0
|
|
# FAR = GFA / участок.
|
|
assert result.density == 2.0
|
|
# Жилая = GFA * efficiency(comfort=0.78).
|
|
assert result.residential_area_sqm == 1560.0
|
|
# Квартир = floor(жилая / avg(comfort=55)).
|
|
assert result.apartments_count == int(1560.0 // 55.0)
|
|
# Парковка = ceil(квартир * 1.0).
|
|
assert result.parking_spaces == result.apartments_count
|
|
|
|
|
|
def test_teap_class_changes_efficiency_and_lot() -> None:
|
|
econ = teap.compute_teap(
|
|
footprints=[_FOOTPRINT], floors=10, site_area_sqm=1000.0, housing_class="econom"
|
|
)
|
|
biz = teap.compute_teap(
|
|
footprints=[_FOOTPRINT], floors=10, site_area_sqm=1000.0, housing_class="business"
|
|
)
|
|
# Эконом эффективнее по площади -> больше жилой при той же GFA.
|
|
assert econ.residential_area_sqm > biz.residential_area_sqm
|
|
# Бизнес — крупнее лот -> меньше квартир.
|
|
assert biz.apartments_count < econ.apartments_count
|
|
# Бизнес — выше норма парковки на квартиру.
|
|
assert biz.parking_spaces / max(1, biz.apartments_count) >= 1.4
|
|
|
|
|
|
def test_teap_zero_site_area_no_division_error() -> None:
|
|
result = teap.compute_teap(
|
|
footprints=[_FOOTPRINT], floors=5, site_area_sqm=0.0, housing_class="comfort"
|
|
)
|
|
assert result.density == 0.0
|
|
|
|
|
|
def test_teap_empty_placement_is_zeroed() -> None:
|
|
result = teap.compute_teap(
|
|
footprints=[], floors=9, site_area_sqm=1000.0, housing_class="comfort"
|
|
)
|
|
assert result.built_area_sqm == 0.0
|
|
assert result.total_floor_area_sqm == 0.0
|
|
assert result.apartments_count == 0
|
|
assert result.parking_spaces == 0
|
|
|
|
|
|
def _teap(residential: float, gfa: float) -> TEAP:
|
|
return TEAP(
|
|
built_area_sqm=100.0,
|
|
total_floor_area_sqm=gfa,
|
|
residential_area_sqm=residential,
|
|
apartments_count=10,
|
|
density=1.0,
|
|
parking_spaces=10,
|
|
)
|
|
|
|
|
|
def test_financial_revenue_cost_margin() -> None:
|
|
t = _teap(residential=1000.0, gfa=1300.0)
|
|
model = financial.compute_financial(
|
|
teap=t, housing_class="comfort", land_cost_rub=50_000_000.0
|
|
)
|
|
# revenue = 1000 * 145_000.
|
|
assert model.revenue_rub == 1000.0 * 145_000.0
|
|
# cost = 1300 * 88_000 + land.
|
|
assert model.cost_rub == 1300.0 * 88_000.0 + 50_000_000.0
|
|
assert model.gross_margin_rub == model.revenue_rub - model.cost_rub
|
|
|
|
|
|
def test_financial_land_cost_optional() -> None:
|
|
t = _teap(residential=1000.0, gfa=1300.0)
|
|
no_land = financial.compute_financial(teap=t, housing_class="comfort", land_cost_rub=None)
|
|
with_land = financial.compute_financial(
|
|
teap=t, housing_class="comfort", land_cost_rub=10_000_000.0
|
|
)
|
|
# Земля увеличивает затраты ровно на свою стоимость.
|
|
assert with_land.cost_rub - no_land.cost_rub == 10_000_000.0
|
|
|
|
|
|
def test_financial_irr_proxy_clamped() -> None:
|
|
# Огромная маржа -> irr-proxy зажат в [-1, 1].
|
|
t = _teap(residential=100_000.0, gfa=1.0)
|
|
model = financial.compute_financial(teap=t, housing_class="business", land_cost_rub=None)
|
|
assert -1.0 <= model.irr <= 1.0
|
|
|
|
|
|
def test_financial_zero_cost_no_division_error() -> None:
|
|
t = _teap(residential=0.0, gfa=0.0)
|
|
model = financial.compute_financial(teap=t, housing_class="comfort", land_cost_rub=None)
|
|
assert model.irr == 0.0
|
|
assert model.cost_rub == 0.0
|