diff --git a/backend/app/services/generative/exporters/pdf.py b/backend/app/services/generative/exporters/pdf.py index ef3d1414..7ec34e11 100644 --- a/backend/app/services/generative/exporters/pdf.py +++ b/backend/app/services/generative/exporters/pdf.py @@ -66,9 +66,7 @@ def _strategy_label(strategy: str) -> str: def _teap_table(variants: Sequence[ConceptVariant]) -> str: """HTML-таблица ТЭП по всем вариантам (строки — показатели, колонки — стратегии).""" - headers = "".join( - f"{html.escape(_strategy_label(v.strategy))}" for v in variants - ) + headers = "".join(f"{html.escape(_strategy_label(v.strategy))}" 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"{html.escape(_strategy_label(v.strategy))}" for v in variants - ) + headers = "".join(f"{html.escape(_strategy_label(v.strategy))}" 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). Цены и себестоимость " - "— рыночные ориентиры. Коммерческие/офисные площади не учитываются (нет в ТЭП).

" + "входной НДС по СМР встроен в себестоимость; НДС начисляется на нежилые части — " + "машиноместа и коммерцию/офисы 1-го этажа (встроенный НДС в добавленной стоимости " + "каждой части). Налог на прибыль — 25% (с 2025). Цены и себестоимость — рыночные " + "ориентиры. Коммерция/офисы 1-го этажа учтены по нормативной доле от общей площади " + "и продаются с умеренной наценкой к цене жилья того же класса; себестоимость СМР " + "нежилого — по той же ставке, что и жильё (отдельной строки затрат нет, повторного " + "учёта в затратах нет).

" "" ) diff --git a/backend/tests/services/generative/test_exporters.py b/backend/tests/services/generative/test_exporters.py index 5df30a3f..89a4b67c 100644 --- a/backend/tests/services/generative/test_exporters.py +++ b/backend/tests/services/generative/test_exporters.py @@ -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",