fix(generative): устаревший VAT/office caveat в concept PDF (#2445-C1) #2455

Merged
bot-backend merged 1 commit from fix/concept-pdf-office-caveat into main 2026-07-05 19:40:31 +00:00
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",