diff --git a/backend/app/services/generative/exporters/pdf.py b/backend/app/services/generative/exporters/pdf.py index 7ec34e11..8703dee5 100644 --- a/backend/app/services/generative/exporters/pdf.py +++ b/backend/app/services/generative/exporters/pdf.py @@ -163,6 +163,29 @@ def _financial_table(variants: Sequence[ConceptVariant]) -> str: ) +def _sales_phrase(financial: FinancialModel) -> str: + """Фраза о сроке распродажи для методической сноски. PURE. + + #2464: срок был зашит числом «30 мес» — при том, что ставка дисконта в той же + строке берётся из расчёта. 30 — это ФОЛБЭК (`financial._SALES_DURATION_MONTHS`), + применяемый только когда рыночная скорость абсорбции не передана. Иначе окно + считается как площадь/скорость и клампится в [6, 120] мес, то есть сноска обещала + читателю не тот срок, по которому посчитан NPV. + + Оба нужных поля уже есть в схеме: `sales_duration_months` (реализованное окно) и + `schedule_is_default` (честный флаг «норматив, а не рынок»). Отчёт Site Finder флаг + уже читает — full_report_html.py:1335 и full_report_docx.py:855; игнорировал его + только этот экспортёр. + + getattr с дефолтом — тот же оборонительный приём, что у соседних полей: старый + сериализованный вариант без новых ключей не должен ронять экспорт. + """ + months = getattr(financial, "sales_duration_months", None) + if getattr(financial, "schedule_is_default", True) or months is None: + return "распродажа 30 мес (нормативный темп)" + return f"распродажа {months:.0f} мес (по рыночной абсорбции)" + + def _build_html(variants: Sequence[ConceptVariant]) -> str: if not variants: return ( @@ -172,6 +195,7 @@ def _build_html(variants: Sequence[ConceptVariant]) -> str: f"
{_DASH} нет вариантов для отображения