place_strategy не отбраковывает стратегию с нулевым размещением: пустой концепт (revenue=0, margin=-land, IRR=-33%) выдаётся как валидный вариант #1597

Closed
opened 2026-06-16 17:15:36 +00:00 by bot-backend · 0 comments
Collaborator

Severity: medium · Категория: silent-degenerate-output / ложь в отчёте · Файл: backend/app/services/generative/placement.py:190-219 (place_strategy) + 95-162 (_greedy_place)

Файлы: backend/app/services/generative/placement.py:190-219 (place_strategy) + :95-162 (_greedy_place).

Проблема: _greedy_place может вернуть пустой list[Polygon], если ни один footprint стратегии (max_area 24x18=432 м², плюс gap) не помещается целиком в buildable area (проверка buildable.covers на placement.py:133). Единственная upstream-страховка — buildable.area >= 50 м² (MIN_BUILDABLE_AREA_SQM, geometry.py:50,268), что меньше площади секции, поэтому пустое размещение реально на узком/мелком участке. place_strategy не проверяет len(footprints) и безусловно строит TEAP (все нули: teap.py:71-84) и FinancialModel. При переданном land_cost_rub получаем revenue=0, cost=land, gross_margin=-land, irr=-0.3333 (financial.py:65-79). place_all_strategies (placement.py:224) всегда возвращает 3 варианта, generate (geometry.py:305) — без фильтра.

Импакт: девелоперу в одном ответе показываются осмысленные варианты рядом с 'концептом', который ничего не строит, но подписан как реальная стратегия застройки с убытком на всю стоимость земли и IRR -33%. Ложная картина 'max_area убыточен' вместо корректного 'движок не смог разложить секции'. Ошибка молчаливая — нет ни лога-предупреждения уровня варианта, ни сигнала клиенту.

Фикс: в place_strategy после _greedy_place проверять if not footprints и либо помечать вариант флагом/статусом 'no_placement' в контракте (ConceptVariant), либо исключать такую стратегию из place_all_strategies (с логом причины), чтобы UI не сравнивал пустышку с реальными вариантами.

Почему баг: Девелоперу показывается money-losing вариант (маржа = минус вся цена земли, IRR -33%) как равноправный 'концепт застройки', хотя на деле движок просто не смог разложить секции этой стратегии. Это вводящий в заблуждение результат основной фичи: пользователь сравнивает 3 стратегии и видит ложную картину 'max_area убыточен', тогда как правильное поведение — пометить/исключить стратегию без размещения. Нигде выше по стеку (place_all_strategies, create_concept) нет проверки на пустое размещение, поэтому ошибка молчаливая.

Как чинить: В place_strategy после _greedy_place: если not footprints — либо не включать вариант в выдачу (и тогда place_all_strategies может вернуть <3 вариантов с явным флагом), либо помечать вариант признаком 'размещение невозможно' и не подавать revenue=0/margin=-land как достоверную финмодель. Минимум — логировать warning и не подставлять land_cost в cost при нулевом размещении, чтобы не рисовать фиктивный убыток.


deep-audit backend v2 (после PR #1543) · verify+harden, confidence 0.85 · unit B27

**Severity:** medium · **Категория:** silent-degenerate-output / ложь в отчёте · **Файл:** `backend/app/services/generative/placement.py:190-219 (place_strategy) + 95-162 (_greedy_place)` Файлы: backend/app/services/generative/placement.py:190-219 (place_strategy) + :95-162 (_greedy_place). Проблема: _greedy_place может вернуть пустой list[Polygon], если ни один footprint стратегии (max_area 24x18=432 м², плюс gap) не помещается целиком в buildable area (проверка buildable.covers на placement.py:133). Единственная upstream-страховка — buildable.area >= 50 м² (MIN_BUILDABLE_AREA_SQM, geometry.py:50,268), что меньше площади секции, поэтому пустое размещение реально на узком/мелком участке. place_strategy не проверяет len(footprints) и безусловно строит TEAP (все нули: teap.py:71-84) и FinancialModel. При переданном land_cost_rub получаем revenue=0, cost=land, gross_margin=-land, irr=-0.3333 (financial.py:65-79). place_all_strategies (placement.py:224) всегда возвращает 3 варианта, generate (geometry.py:305) — без фильтра. Импакт: девелоперу в одном ответе показываются осмысленные варианты рядом с 'концептом', который ничего не строит, но подписан как реальная стратегия застройки с убытком на всю стоимость земли и IRR -33%. Ложная картина 'max_area убыточен' вместо корректного 'движок не смог разложить секции'. Ошибка молчаливая — нет ни лога-предупреждения уровня варианта, ни сигнала клиенту. Фикс: в place_strategy после _greedy_place проверять `if not footprints` и либо помечать вариант флагом/статусом 'no_placement' в контракте (ConceptVariant), либо исключать такую стратегию из place_all_strategies (с логом причины), чтобы UI не сравнивал пустышку с реальными вариантами. **Почему баг:** Девелоперу показывается money-losing вариант (маржа = минус вся цена земли, IRR -33%) как равноправный 'концепт застройки', хотя на деле движок просто не смог разложить секции этой стратегии. Это вводящий в заблуждение результат основной фичи: пользователь сравнивает 3 стратегии и видит ложную картину 'max_area убыточен', тогда как правильное поведение — пометить/исключить стратегию без размещения. Нигде выше по стеку (place_all_strategies, create_concept) нет проверки на пустое размещение, поэтому ошибка молчаливая. **Как чинить:** В place_strategy после _greedy_place: если not footprints — либо не включать вариант в выдачу (и тогда place_all_strategies может вернуть <3 вариантов с явным флагом), либо помечать вариант признаком 'размещение невозможно' и не подавать revenue=0/margin=-land как достоверную финмодель. Минимум — логировать warning и не подставлять land_cost в cost при нулевом размещении, чтобы не рисовать фиктивный убыток. --- <sub>deep-audit backend v2 (после PR #1543) · verify+harden, confidence 0.85 · unit B27</sub>
bot-backend added the
week ревью 1
label 2026-06-16 17:15:36 +00:00
Sign in to join this conversation.
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#1597
No description provided.