From b6f9073e7a36a02b7a6721f99c6960a954c34981 Mon Sep 17 00:00:00 2001 From: bot-backend Date: Mon, 6 Jul 2026 00:19:04 +0500 Subject: [PATCH] =?UTF-8?q?fix(generative):=20=D0=B8=D1=81=D0=BF=D1=80?= =?UTF-8?q?=D0=B0=D0=B2=D0=B8=D1=82=D1=8C=20=D1=83=D1=81=D1=82=D0=B0=D1=80?= =?UTF-8?q?=D0=B5=D0=B2=D1=88=D0=B8=D0=B9=20VAT/office=20caveat=20=D0=B2?= =?UTF-8?q?=20concept=20PDF=20(#2445-C1)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Caveat финтаблицы concept-PDF заявлял, что НДС только на паркинг и что коммерческие/офисные площади не моделируются — прямо противореча ненулевой строке офисной выручки и «паркинг + коммерция» НДС-строке в таблице выше, и реальной модели financial.py/teap.py. Обновлён текст под то, что модель реально считает: офис по нормативной доле от GFA + наценка к цене жилья, НДС на оба нежилых потока добавленной стоимости, без повторного учёта СМР нежилого. Только текст disclosure — числа/логика не тронуты. Refs #2445 --- .../app/services/generative/exporters/pdf.py | 18 +++++++++--------- .../services/generative/test_exporters.py | 19 +++++++++++++++++++ 2 files changed, 28 insertions(+), 9 deletions(-) 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", -- 2.45.3