fix(ptica): сноска концепт-PDF печатает фактический срок распродажи, а не «30 мес» (#2464) #2945

Merged
bot-backend merged 1 commit from fix/2464-concept-pdf-sales-window into main 2026-08-19 17:39:38 +00:00
Collaborator

Сноска обещала срок, по которому NPV не считался

В методической сноске концепт-PDF стояло «распродажа 30 мес» — числом. При том что ставка дисконта в той же строке берётся из расчёта:

disc_pct = f"{variants[0].financial.discount_rate_used * 100:.0f}%"
...
f"(ПИР 6 мес → СМР по типу застройки → распродажа 30 мес, дисконт {disc_pct} годовых)"

30 месяцев — это фолбэк financial._SALES_DURATION_MONTHS, применяемый только когда рыночная скорость абсорбции не передана. Иначе окно считается как residential_area_sqm / market_velocity и клампится в [6, 120] мес.

Всё нужное уже было в схеме

  • sales_duration_months — реализованное окно sales_end − sales_start;
  • schedule_is_default — честный флаг, заведённый ровно для этого случая: «True, когда окно продаж — дефолт-норматив 30 мес; False, когда оно построено по рыночной абсорбции».

Отчёт Site Finder оба читает — full_report_html.py:1335 и full_report_docx.py:855. Игнорировал их только этот экспортёр.

Теперь:

schedule_is_default  ->  «распродажа 30 мес (нормативный темп)»
иначе                ->  «распродажа N мес (по рыночной абсорбции)»

Норматив остаётся — он законен, — но перестаёт выдавать себя за измерение.

Про вынесение в функцию

Логика ушла в чистую _sales_phrase(financial). Собирать полный ConceptVariant ради одной строки хрупко: таблицы читают больше двадцати несвязанных полей, и тест ломался бы от любого их изменения. Так проверка зависит только от того, что проверяет.

Проверка

Главный тест — на уровне готового документа, и он красный на origin/main по содержимому:

AssertionError: в методической сноске стоит не тот срок, по которому посчитан NPV
assert 'распродажа 54 мес' in '<html>...'

Импорт новой функции перенесён внутрь тестов. В шапке он ронял сборку модуля, и тогда ни одна проверка не исполнялась бы, а красное означало бы «функции нет», а не «в документе не то». Ровно на это я напоролся в первой версии.

Контроли: дефолтное окно печатается с пометкой «нормативный»; вариант без поля не роняет экспорт; дробное окно округляется (53.6 → «54 мес», а не «54.0» и не «0»).

pytest tests/services/exporters + tests/services/generative: 373 passed, 8 skipped, rc=0 — перепрогнано после правок pre-commit.

Refs #2464

## Сноска обещала срок, по которому NPV не считался В методической сноске концепт-PDF стояло **«распродажа 30 мес»** — числом. При том что ставка дисконта в **той же строке** берётся из расчёта: ```python disc_pct = f"{variants[0].financial.discount_rate_used * 100:.0f}%" ... f"(ПИР 6 мес → СМР по типу застройки → распродажа 30 мес, дисконт {disc_pct} годовых)" ``` 30 месяцев — это фолбэк `financial._SALES_DURATION_MONTHS`, применяемый **только** когда рыночная скорость абсорбции не передана. Иначе окно считается как `residential_area_sqm / market_velocity` и клампится в `[6, 120]` мес. ## Всё нужное уже было в схеме - `sales_duration_months` — реализованное окно `sales_end − sales_start`; - `schedule_is_default` — честный флаг, заведённый ровно для этого случая: *«True, когда окно продаж — дефолт-норматив 30 мес; False, когда оно построено по рыночной абсорбции»*. Отчёт Site Finder оба читает — `full_report_html.py:1335` и `full_report_docx.py:855`. Игнорировал их **только** этот экспортёр. Теперь: ``` schedule_is_default -> «распродажа 30 мес (нормативный темп)» иначе -> «распродажа N мес (по рыночной абсорбции)» ``` Норматив остаётся — он законен, — но перестаёт выдавать себя за измерение. ## Про вынесение в функцию Логика ушла в чистую `_sales_phrase(financial)`. Собирать полный `ConceptVariant` ради одной строки хрупко: таблицы читают больше двадцати несвязанных полей, и тест ломался бы от любого их изменения. Так проверка зависит только от того, что проверяет. ## Проверка Главный тест — на уровне **готового документа**, и он красный на `origin/main` по содержимому: ``` AssertionError: в методической сноске стоит не тот срок, по которому посчитан NPV assert 'распродажа 54 мес' in '<html>...' ``` Импорт новой функции перенесён **внутрь** тестов. В шапке он ронял сборку модуля, и тогда ни одна проверка не исполнялась бы, а красное означало бы «функции нет», а не «в документе не то». Ровно на это я напоролся в первой версии. Контроли: дефолтное окно печатается с пометкой «нормативный»; вариант без поля не роняет экспорт; дробное окно округляется (53.6 → «54 мес», а не «54.0» и не «0»). `pytest tests/services/exporters + tests/services/generative`: **373 passed, 8 skipped, rc=0** — перепрогнано после правок pre-commit. Refs #2464
bot-backend added 1 commit 2026-08-19 17:19:57 +00:00
fix(ptica): сноска концепт-PDF печатает фактический срок распродажи, а не «30 мес» (#2464)
All checks were successful
CI Trade-In / changes (pull_request) Successful in 10s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Has been skipped
CI / changes (pull_request) Successful in 13s
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 2m18s
CI / backend-tests (pull_request) Successful in 17m8s
41b1302e1e
В методической сноске стояло «распродажа 30 мес» — числом, при том что ставка
дисконта в ТОЙ ЖЕ строке берётся из расчёта (variants[0].financial.
discount_rate_used).

30 месяцев — это фолбэк financial._SALES_DURATION_MONTHS, применяемый ТОЛЬКО когда
рыночная скорость абсорбции не передана. Иначе окно считается как
residential_area_sqm / market_velocity и клампится в [6, 120] мес. То есть читатель
экспортированного документа получал срок, по которому NPV НЕ считался.

Оба нужных поля давно есть в схеме: sales_duration_months (реализованное окно
sales_end − sales_start) и schedule_is_default — честный флаг «норматив, а не
рынок», заведённый ровно для этого. Отчёт Site Finder их читает
(full_report_html.py:1335 и full_report_docx.py:855); игнорировал только этот
экспортёр.

Теперь: при schedule_is_default → «распродажа 30 мес (нормативный темп)», иначе →
«распродажа N мес (по рыночной абсорбции)». Норматив остаётся, но перестаёт
выдавать себя за измерение.

Логика вынесена в чистую _sales_phrase: собирать полный ConceptVariant ради одной
строки — хрупко, а так проверка не зависит от двух десятков несвязанных полей.

Тест на уровне ДОКУМЕНТА красный на origin/main по содержимому:
«AssertionError: в методической сноске стоит не тот срок, по которому посчитан NPV».
Импорт новой функции перенесён внутрь тестов — в шапке он ронял сборку модуля, и
тогда красное означало бы «функции нет», а не «в документе не то».

pytest tests/services/exporters + tests/services/generative: 373 passed, 8 skipped, rc=0
Прогон повторён после правок pre-commit.
bot-backend merged commit 251eacc4b6 into main 2026-08-19 17:39:38 +00:00
bot-backend deleted branch fix/2464-concept-pdf-sales-window 2026-08-19 17:39:38 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: lekss361/gendesign#2945
No description provided.