feat(concept): каталог типовых домов + program-placement (#1965 Stage 3a) #2029

Merged
bot-backend merged 2 commits from feat/concept-house-catalog-1965-stage3a into main 2026-06-27 21:40:11 +00:00
Collaborator

#1965 Stage 3a (эпик #1953) — backend для выбора типов домов вместо max-FAR-застройки. deep-code-reviewer APPROVE (рефактор подтверждён эквивалентным построчно). Аддитивно, backward-compatible.

Что

  • Каталог catalog.py (NEW): HOUSE_TYPES — 5 типовых RU-секций (панель-эконом 24×15/9эт, монолит-комфорт 21×18/14, башня-бизнес 18×18/25, малоэтажка 30×14/3, таунхаус 12×10/3), footprint+этажи+класс. Документирован как sane-default (промоут в БД позже). get_house_type/available_section_types.
  • Схема: BuildingProgramItem {section_type, floors 1-40, count 1-50} + ConceptInput.building_program: list | None = None (аддитивно; None → существующий greedy, byte-identical).
  • Placement: _Placer (вынесен общий движок collision/STRtree/setback — greedy и program используют его; greedy семантически НЕ изменён) + place_program (раскладывает ровно count секций каждого типа) + place_program_variant (агрегирует ТЭП per-floor-group: GFA=Σ area×floors, квартиры=Σ floor(resid/avg), парковка пересчитана). building_program set → один program-вариант; иначе 3 greedy-стратегии без изменений.
  • Partial-fit: не влезло всё → размещает сколько влезло + placed_count/requested_count (не 422); zero-fit → 422 (не врём). Unknown section_type → 422 до placement.
  • api-types.ts перегенерён (аддитивно, byte-stable).

Качество

  • golden-пин test_greedy_output_matches_golden_pin — замороженные литералы greedy-вывода (features/built_area/apartments per strategy) → любой будущий дрейф placement упадёт (закрыл MEDIUM из ревью).
  • mypy-strict (generative) чисто; ruff ; no :name::type; 158 passed. Blast-radius (dxf/pdf-экспортёры, /recompute, фронт) — не задет (ревью подтвердило).

Stage 3b (следующий, финал #1965): frontend picker «выбери дом» (count/floors по типам) → building_program в create_concept. Refs #1965.

#1965 Stage 3a (эпик #1953) — backend для выбора типов домов вместо max-FAR-застройки. **deep-code-reviewer ✅ APPROVE** (рефактор подтверждён эквивалентным построчно). Аддитивно, backward-compatible. ## Что - **Каталог** `catalog.py` (NEW): `HOUSE_TYPES` — 5 типовых RU-секций (панель-эконом 24×15/9эт, монолит-комфорт 21×18/14, башня-бизнес 18×18/25, малоэтажка 30×14/3, таунхаус 12×10/3), footprint+этажи+класс. Документирован как sane-default (промоут в БД позже). `get_house_type`/`available_section_types`. - **Схема**: `BuildingProgramItem {section_type, floors 1-40, count 1-50}` + `ConceptInput.building_program: list | None = None` (аддитивно; None → существующий greedy, byte-identical). - **Placement**: `_Placer` (вынесен общий движок collision/STRtree/setback — greedy и program используют его; greedy семантически НЕ изменён) + `place_program` (раскладывает ровно `count` секций каждого типа) + `place_program_variant` (агрегирует ТЭП per-floor-group: GFA=Σ area×floors, квартиры=Σ floor(resid/avg), парковка пересчитана). `building_program` set → один program-вариант; иначе 3 greedy-стратегии без изменений. - **Partial-fit**: не влезло всё → размещает сколько влезло + `placed_count`/`requested_count` (не 422); zero-fit → 422 (не врём). Unknown section_type → 422 до placement. - api-types.ts перегенерён (аддитивно, byte-stable). ## Качество - **golden-пин** `test_greedy_output_matches_golden_pin` — замороженные литералы greedy-вывода (features/built_area/apartments per strategy) → любой будущий дрейф placement упадёт (закрыл MEDIUM из ревью). - mypy-strict (generative) чисто; ruff ✅; no `:name::type`; **158 passed**. Blast-radius (dxf/pdf-экспортёры, /recompute, фронт) — не задет (ревью подтвердило). Stage 3b (следующий, финал #1965): frontend picker «выбери дом» (count/floors по типам) → `building_program` в create_concept. Refs #1965.
bot-backend added 2 commits 2026-06-27 21:24:52 +00:00
Extend the concept generator so ConceptInput can carry an optional
building_program (list of typed houses from a catalog). When present,
placement lays out EXACTLY that program — for each item, place `count`
sections of the catalog footprint at the item's floors — instead of the
greedy max-FAR coverage-cap sweep. When absent, the existing greedy
behavior is unchanged (byte-for-byte backward-compatible).

- catalog.py: hardcoded HOUSE_TYPES (panel_econom, monolith_comfort,
  tower_business, lowrise_comfort, townhouse) — sane-default catalog,
  promote to DB later; get_house_type / available_section_types lookups.
- schema: additive BuildingProgramItem {section_type, floors, count} and
  ConceptInput.building_program (default None -> greedy). ConceptVariant
  gains optional placed_count / requested_count (partial-fit signal).
- placement: shared _Placer (collision/STRtree/setback machine extracted
  from greedy sweep, reused — no duplication); place_program +
  place_program_variant; branch in place_all_strategies on
  building_program. Mixed-floor TEAP via exact per-floor-group aggregation
  (GFA = sum(area_i * floors_i), no rounding drift).
- partial fit: when the parcel can't fit all sections, place as many as
  fit and report placed_count < requested_count (no hard-422); zero-fit
  still raises ParcelGeometryError (-> 422).
- API: validate program section_type keys against the catalog (unknown ->
  422) before placement.
- tests: catalog integrity, greedy backward-compat, exact 2-item program +
  TEAP reflection, over-packed partial placement, API program path.
- regenerate frontend api-types.ts (OpenAPI codegen gate stays green).
test(concept): turn greedy backward-compat into a real golden pin (#1965 Stage 3a)
All checks were successful
CI / changes (pull_request) Successful in 7s
CI / frontend-tests (pull_request) Successful in 1m3s
CI / openapi-codegen-check (pull_request) Successful in 1m51s
CI / backend-tests (pull_request) Successful in 14m7s
e8b05d821e
The old test_no_program_reproduces_greedy_output_unchanged only compared
building_program=None (default) vs explicit None — both through the new
_Placer code — so it proved the two None branches agree but did NOT pin
the greedy geometry; it would still pass if the _Placer extraction had
drifted the output. test_placement.py only checks invariants, never
concrete counts/TEAP, so there was no anti-regression guard that the
greedy path is byte-identical after the refactor.

Replace it with two tests:
- test_greedy_output_matches_golden_pin: hard-coded literals per strategy
  on the fixed _BIG_PARCEL — (features, built_area_sqm, total_floor_area_sqm,
  apartments_count) — frozen from the current (== pre-refactor) output, so
  any future deterministic drift in greedy placement FAILS.
- test_explicit_none_program_equals_default_greedy: keeps the None-branch
  equivalence check (default vs explicit None go one greedy path).
bot-backend merged commit 3b84609670 into main 2026-06-27 21:40:11 +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#2029
No description provided.