fix(generative): исправить устаревший VAT/office caveat в concept PDF (#2445-C1)
All checks were successful
CI Trade-In / changes (pull_request) Successful in 11s
CI / changes (pull_request) Successful in 10s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Successful in 2m8s
CI / backend-tests (pull_request) Successful in 15m1s

Caveat финтаблицы concept-PDF заявлял, что НДС только на паркинг и что
коммерческие/офисные площади не моделируются — прямо противореча ненулевой
строке офисной выручки и «паркинг + коммерция» НДС-строке в таблице выше,
и реальной модели financial.py/teap.py. Обновлён текст под то, что модель
реально считает: офис по нормативной доле от GFA + наценка к цене жилья,
НДС на оба нежилых потока добавленной стоимости, без повторного учёта СМР
нежилого. Только текст disclosure — числа/логика не тронуты.

Refs #2445
This commit is contained in:
bot-backend 2026-07-06 00:19:04 +05:00
parent 82e0b8b0a3
commit b6f9073e7a
2 changed files with 28 additions and 9 deletions

View file

@ -66,9 +66,7 @@ def _strategy_label(strategy: str) -> str:
def _teap_table(variants: Sequence[ConceptVariant]) -> str:
"""HTML-таблица ТЭП по всем вариантам (строки — показатели, колонки — стратегии)."""
headers = "".join(
f"<th>{html.escape(_strategy_label(v.strategy))}</th>" for v in variants
)
headers = "".join(f"<th>{html.escape(_strategy_label(v.strategy))}</th>" for v in variants)
rows: list[tuple[str, list[str]]] = [
("Пятно застройки, кв.м", [_fmt_int(v.teap.built_area_sqm) for v in variants]),
("Общая площадь (GFA), кв.м", [_fmt_int(v.teap.total_floor_area_sqm) for v in variants]),
@ -107,9 +105,7 @@ def _fmt_irr(financial: FinancialModel) -> str:
def _financial_table(variants: Sequence[ConceptVariant]) -> str:
"""HTML-таблица финмодели (деньги в млн руб; полный каскад + БДР + DCF)."""
headers = "".join(
f"<th>{html.escape(_strategy_label(v.strategy))}</th>" for v in variants
)
headers = "".join(f"<th>{html.escape(_strategy_label(v.strategy))}</th>" for v in variants)
rows: list[tuple[str, list[str]]] = [
(
"Выручка — жильё, млн руб",
@ -188,9 +184,13 @@ def _build_html(variants: Sequence[ConceptVariant]) -> str:
"точность метрик зависит от реального графика. Где IRR помечен «оценочный» — поток "
"вырожденный (нет смены знака), показан аннуализированный ROI вместо DCF-IRR. "
"НДС — реализация жилья и услуги застройщика по ДДУ освобождены (ст. 149 НК РФ), "
"входной НДС по СМР встроен в себестоимость; НДС начисляется только на нежилую часть "
"(машиноместа). Налог на прибыль — 25% (с 2025). Цены и себестоимость "
"— рыночные ориентиры. Коммерческие/офисные площади не учитываются (нет в ТЭП).</p>"
"входной НДС по СМР встроен в себестоимость; НДС начисляется на нежилые части — "
"машиноместа и коммерцию/офисы 1-го этажа (встроенный НДС в добавленной стоимости "
"каждой части). Налог на прибыль — 25% (с 2025). Цены и себестоимость — рыночные "
"ориентиры. Коммерция/офисы 1-го этажа учтены по нормативной доле от общей площади "
"и продаются с умеренной наценкой к цене жилья того же класса; себестоимость СМР "
"нежилого — по той же ставке, что и жильё (отдельной строки затрат нет, повторного "
"учёта в затратах нет).</p>"
"</body></html>"
)

View file

@ -119,6 +119,25 @@ def test_pdf_html_build_graceful_on_empty() -> None:
assert "нет вариантов" in html
def test_pdf_caveat_does_not_contradict_office_revenue_row() -> None:
"""Эпик #2445 C1: каскад включает нежилую выручку/НДС — caveat не должен это отрицать.
Payload ``comfort`` (office_share=0.05 в teap.py), поэтому строка "Выручка —
нежилое (1-й этаж)" в таблице ненулевая: caveat ниже НЕ может утверждать, что
коммерция/офисы "не учитываются" или что НДС "только машиноместа".
"""
variants = geometry.generate(_payload())
html = pdf._build_html(variants)
assert "Выручка — нежилое (1-й этаж), млн руб" in html
assert "НДС (нежилое: паркинг + коммерция), млн руб" in html
# Старые (стейл) формулировки, отрицающие модель нежилого, — не должны вернуться.
assert "не учитываются" not in html
assert "только на нежилую часть" not in html
assert "только машиноместа" not in html
# Caveat честно описывает нежилое как учтённое (не «нет в ТЭП»).
assert "нет в ТЭП" not in html
@pytest.mark.skipif(
not _weasyprint_available(),
reason="WeasyPrint native libs unavailable on this host",