feat(parcels): layout signature schemas + extractor (#113 PR A) #193

Merged
lekss361 merged 1 commit from feat/113-layout-signature-schemas into main 2026-05-16 07:52:35 +00:00
Owner

Summary

Foundation PR (A из 4) для Issue #113 «Layout Analysis → ТЗ на проектирование» (Макс Phase 2 after #112).

Phase 2.1 minimal: layout signature = (room_bucket × area_bin) = 5×6 = 30 классов. layout_type (евро/классика) и balcony_count отсутствуют в domrf_kn_flats — ждут B2B Объектив (#52, отдельный issue).

Что в этом PR

  • 7 Pydantic v2 schemas в backend/app/schemas/parcel.py:
    • LayoutSignature, BestLayoutsRequest, TopLayoutRow, LayoutTzMixRow, LayoutTzRecommendation, LayoutDataQuality, BestLayoutsResponse
  • backend/app/services/site_finder/layout_signature.py (NEW, 56 LOC, no DB) — 3 pure-Python helpers:
    • room_bucket_from_flat(rooms, flat_type, is_studio) → "studio"|"1"|"2"|"3"|"4+"
    • area_bin(area_m2) → "<25"|"25-40"|"40-60"|"60-80"|"80-100"|"100+"
    • layout_signature(room_bucket, area_bin) → "1__25-40" (deterministic)
  • 27 unit tests в backend/tests/test_layout_signature.py (parametrized — studio detection, edge boundaries, fallbacks)

Что НЕ в этом PR (sequenced)

  • PR B feat/113-mv-layout-velocity (database-expert) — data/sql/94_mv_layout_velocity.sql materialized view
  • PR C feat/113-best-layouts-endpoint (backend-engineer) — POST /api/v1/parcels/{cad_num}/best-layouts
  • PR D feat/113-pdf-tz-and-ui (backend + frontend) — WeasyPrint PDF ТЗ + BestLayoutsBlock.tsx

Carryover bug to flag (НЕ в скоупе этого PR)

backend/app/services/site_finder/competitors.py:138 — фильтр WHERE f.status = 'sold' всегда возвращает 0 (поле 99.8% NULL на проде в domrf_kn_flats). avg_price_per_m2 в Competitor never works. PR C будет использовать objective_corpus_room_month вместо. Follow-up issue для фикса #112 рекомендуется.

Test plan

  • cd backend && uv run pytest tests/test_layout_signature.py -v → 27/27 pass
  • cd backend && uv run pytest tests/api/v1/test_parcel_competitors.py tests/api/v1/test_parcel_connection_points.py -q → 16/16 pass (no regression)
  • cd backend && uv run ruff check app/services/site_finder/layout_signature.py app/schemas/parcel.py → clean
  • cd backend && uv run ruff format --check app/services/site_finder/layout_signature.py → clean
  • pre-push code-reviewer: APPROVE (1 non-blocking style note re docstring ordering)

Closes part of #113 (full close after PR B/C/D).

## Summary Foundation PR (A из 4) для Issue #113 «Layout Analysis → ТЗ на проектирование» (Макс Phase 2 after #112). **Phase 2.1 minimal:** layout signature = `(room_bucket × area_bin)` = 5×6 = 30 классов. `layout_type` (евро/классика) и `balcony_count` отсутствуют в `domrf_kn_flats` — ждут B2B Объектив (#52, отдельный issue). ### Что в этом PR - **7 Pydantic v2 schemas** в `backend/app/schemas/parcel.py`: - `LayoutSignature`, `BestLayoutsRequest`, `TopLayoutRow`, `LayoutTzMixRow`, `LayoutTzRecommendation`, `LayoutDataQuality`, `BestLayoutsResponse` - **`backend/app/services/site_finder/layout_signature.py`** (NEW, 56 LOC, no DB) — 3 pure-Python helpers: - `room_bucket_from_flat(rooms, flat_type, is_studio) → "studio"|"1"|"2"|"3"|"4+"` - `area_bin(area_m2) → "<25"|"25-40"|"40-60"|"60-80"|"80-100"|"100+"` - `layout_signature(room_bucket, area_bin) → "1__25-40"` (deterministic) - **27 unit tests** в `backend/tests/test_layout_signature.py` (parametrized — studio detection, edge boundaries, fallbacks) ### Что НЕ в этом PR (sequenced) - **PR B** `feat/113-mv-layout-velocity` (database-expert) — `data/sql/94_mv_layout_velocity.sql` materialized view - **PR C** `feat/113-best-layouts-endpoint` (backend-engineer) — `POST /api/v1/parcels/{cad_num}/best-layouts` - **PR D** `feat/113-pdf-tz-and-ui` (backend + frontend) — WeasyPrint PDF ТЗ + `BestLayoutsBlock.tsx` ### Carryover bug to flag (НЕ в скоупе этого PR) `backend/app/services/site_finder/competitors.py:138` — фильтр `WHERE f.status = 'sold'` всегда возвращает 0 (поле 99.8% NULL на проде в `domrf_kn_flats`). `avg_price_per_m2` в Competitor never works. PR C будет использовать `objective_corpus_room_month` вместо. Follow-up issue для фикса #112 рекомендуется. ## Test plan - [x] `cd backend && uv run pytest tests/test_layout_signature.py -v` → 27/27 pass - [x] `cd backend && uv run pytest tests/api/v1/test_parcel_competitors.py tests/api/v1/test_parcel_connection_points.py -q` → 16/16 pass (no regression) - [x] `cd backend && uv run ruff check app/services/site_finder/layout_signature.py app/schemas/parcel.py` → clean - [x] `cd backend && uv run ruff format --check app/services/site_finder/layout_signature.py` → clean - [ ] pre-push code-reviewer: ✅ APPROVE (1 non-blocking style note re docstring ordering) Closes part of #113 (full close after PR B/C/D).
lekss361 added 1 commit 2026-05-16 07:49:46 +00:00
Phase 2.1 minimal foundation for #113 Layout Analysis (Макс feedback 12.05).
Без layout_type/balcony_count (отсутствуют в БД, ждут B2B Объектив #52).

Added:
- 7 Pydantic schemas в schemas/parcel.py: LayoutSignature, BestLayoutsRequest,
  TopLayoutRow, LayoutTzMixRow, LayoutTzRecommendation, LayoutDataQuality,
  BestLayoutsResponse
- services/site_finder/layout_signature.py: room_bucket_from_flat /
  area_bin / layout_signature (3 pure-Python helpers, no DB)
- tests/test_layout_signature.py: 27 unit tests (parametrized)

No DB / API changes — foundation для PR B (mv_layout_velocity) и PR C
(POST /best-layouts endpoint).
Author
Owner

Auto-review (SHA 0e2d976f) — verdict: approve

Foundation PR (1 of 4 для Issue #113). Pure-Python additions, zero DB/SQL/auth surface.

Verified

  • 7 Pydantic v2 schemas с разумными Field bounds (radius_km 0.1-1.5, min_velocity_per_month 0-100, target_total_flats 1-10000)
  • 3 pure-Python helpers (room_bucket_from_flat, area_bin, layout_signature) — deterministic, без side effects
  • 27 parametrized unit tests покрывают boundary conditions ([25,40), [40,60)... — inclusive слева), studio detection (is_studio | flat_type='Квартира-студия'), fallbacks (rooms=None → "1", rooms=0 → "studio"), large rooms (>=4 → "4+")
  • No regression — author confirms 16/16 existing tests pass
  • ruff clean (per PR body)

Minor (non-blocking, можно fix потом)

  • layout_signature.py:31return str(rooms) # type: ignore[return-value] acknowledges Literal narrowing issue. Альтернатива: cast(RoomBucket, str(rooms)) или match rooms: case 1 | 2 | 3: return ... — но это nit, не блокер.
  • Carryover bug отмечен в PR body (competitors.py:138 status='sold' всегда 0) — follow-up issue рекомендуется отдельно.

Merge

CI: 0 checks total (workflow или ещё не запустился, или не триггерится для этого PR type). Per rule v3 (auto-merge при approve + ci ∈ {success, none}) → squash merge after this comment.


## Auto-review (SHA `0e2d976f`) — verdict: approve ✅ **Foundation PR (1 of 4 для Issue #113).** Pure-Python additions, zero DB/SQL/auth surface. ### Verified - **7 Pydantic v2 schemas** с разумными Field bounds (radius_km 0.1-1.5, min_velocity_per_month 0-100, target_total_flats 1-10000) - **3 pure-Python helpers** (`room_bucket_from_flat`, `area_bin`, `layout_signature`) — deterministic, без side effects - **27 parametrized unit tests** покрывают boundary conditions ([25,40), [40,60)... — inclusive слева), studio detection (is_studio | flat_type='Квартира-студия'), fallbacks (rooms=None → "1", rooms=0 → "studio"), large rooms (>=4 → "4+") - **No regression** — author confirms 16/16 existing tests pass - **ruff clean** (per PR body) ### Minor (non-blocking, можно fix потом) - `layout_signature.py:31` — `return str(rooms) # type: ignore[return-value]` acknowledges Literal narrowing issue. Альтернатива: `cast(RoomBucket, str(rooms))` или `match rooms: case 1 | 2 | 3: return ...` — но это nit, не блокер. - Carryover bug отмечен в PR body (`competitors.py:138 status='sold'` всегда 0) — follow-up issue рекомендуется отдельно. ### Merge CI: 0 checks total (workflow или ещё не запустился, или не триггерится для этого PR type). Per rule v3 (auto-merge при approve + ci ∈ {success, none}) → squash merge after this comment. --- <!-- gendesign-review-bot: sha=0e2d976 verdict=approve ci=none -->
lekss361 merged commit e673811a20 into main 2026-05-16 07:52:35 +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#193
No description provided.