fix(sf-08): room_bucket euro-1/euro-2 classification #281

Merged
lekss361 merged 1 commit from fix/sf-08-room-bucket-euro into main 2026-05-17 11:42:42 +00:00
Owner

Summary

SF FixList #8 (Wave 2) — rooms=2 за 26м² фактически euro-формат. Audit подтвердил: DOM.РФ API сам маркирует euro как rooms=2, scraper корректен. Добавлены buckets euro-1 / euro-2.

Audit (postgres MCP)

SELECT ... FROM domrf_kn_flats WHERE rooms=2 GROUP BY area_bucket:

Bucket Count
<35 (→ euro-1) 4 140
35-50 (→ euro-2) 25 450
50-70 (normal 2-к) 84 349
>=70 (large 2-к) 12 161

rooms=3 + area<35: всего 40 единиц (0.09%) — не требует отдельного bucket.

Изменения

  • backend/app/services/site_finder/best_layouts.py_SUPPLY_BATCH_SQL CASE добавлены две ветки перед rooms IN (1,2,3):
    • WHEN f.rooms = 2 AND f.total_area < 35 THEN 'euro-1'
    • WHEN f.rooms = 2 AND f.total_area < 50 THEN 'euro-2'
  • backend/app/services/site_finder/layout_signature.pyRoomBucket Literal расширен ("euro-1", "euro-2"); room_bucket_from_flat() принимает total_area и применяет ту же логику.
  • Vault: fixes/Fix_SF_Room_Bucket_Euro_May17.md

Test plan

  • mv_layout_velocity REFRESH после deploy — теперь по room_bucket в ('studio','1k','euro-1','euro-2','2k','3k')
  • /analyze для test_cad 66:41:0702048:27 → Top Layouts показывает euro buckets отдельно
  • Recommendation pipeline получит более точный mix (без слипания 26м²-«2-к» с настоящими 2-к)

Related: epic #271 item #8 · Wave 2

## Summary SF FixList #8 (Wave 2) — `rooms=2` за 26м² фактически euro-формат. Audit подтвердил: DOM.РФ API сам маркирует euro как `rooms=2`, scraper корректен. Добавлены buckets `euro-1` / `euro-2`. ## Audit (postgres MCP) `SELECT ... FROM domrf_kn_flats WHERE rooms=2 GROUP BY area_bucket`: | Bucket | Count | |---|---| | `<35` (→ euro-1) | **4 140** | | `35-50` (→ euro-2) | **25 450** | | `50-70` (normal 2-к) | 84 349 | | `>=70` (large 2-к) | 12 161 | rooms=3 + area<35: всего 40 единиц (0.09%) — не требует отдельного bucket. ## Изменения - `backend/app/services/site_finder/best_layouts.py` — `_SUPPLY_BATCH_SQL` CASE добавлены две ветки перед `rooms IN (1,2,3)`: - `WHEN f.rooms = 2 AND f.total_area < 35 THEN 'euro-1'` - `WHEN f.rooms = 2 AND f.total_area < 50 THEN 'euro-2'` - `backend/app/services/site_finder/layout_signature.py` — `RoomBucket` Literal расширен (`"euro-1"`, `"euro-2"`); `room_bucket_from_flat()` принимает `total_area` и применяет ту же логику. - Vault: `fixes/Fix_SF_Room_Bucket_Euro_May17.md` ## Test plan - [ ] `mv_layout_velocity` REFRESH после deploy — теперь по `room_bucket` в `('studio','1k','euro-1','euro-2','2k','3k')` - [ ] `/analyze` для test_cad `66:41:0702048:27` → Top Layouts показывает euro buckets отдельно - [ ] Recommendation pipeline получит более точный mix (без слипания 26м²-«2-к» с настоящими 2-к) Related: epic #271 item #8 · Wave 2
lekss361 added 1 commit 2026-05-17 11:39:57 +00:00
DOM.РФ API classifies euro-format flats (26-50м²) as rooms=2.
_SUPPLY_BATCH_SQL now maps:
  rooms=2 + area<35 → 'euro-1'
  rooms=2 + area<50 → 'euro-2'
before the generic rooms IN (1,2,3) branch.

Audit: 4 140 units rooms=2 + area<35 in domrf_kn_flats were inflating
the supply count for genuine 2-room apartments (median 57м²).
euro buckets have no velocity counterpart in objective_corpus_room_month
so they are filtered out of top_layouts by min_velocity.

Also extends RoomBucket Literal and room_bucket_from_flat() in
layout_signature.py with the new euro-1/euro-2 values.

Closes #271 item 8
lekss361 merged commit d6a9824473 into main 2026-05-17 11:42:42 +00:00
Author
Owner

Merged via deep-code-reviewer — verdict APPROVE.

Анализ:

  • SQL CASE order корректен: studiorooms=0 studioeuro-1 (rooms=2 AND area<35) → euro-2 (rooms=2 AND area<50) → rooms IN (1,2,3)4+. Mutually exclusive, first-match.
  • Boundary < strict: area=35.0 идёт в euro-2, area=50.0 идёт в '2'. SQL ↔ Python mirror точный.
  • NULL safety: total_area IS NULL + rooms=2 → '2' (default). В Python та же логика (skip euro branch).
  • Backward-compatible Python signature (total_area: float | None = None kwarg) — existing tests test_layout_signature.py не сломаны.

Post-deploy actions:

  1. REFRESH MATERIALIZED VIEW CONCURRENTLY mv_layout_velocity; (опционально — MV definition не менялась, но per author test plan).
  2. Smoke: /analyze для 66:41:0702048:27 → проверить supply_units_in_radius для bucket='2' снизится на ~29.5k euro-формат строк.

Known limitations (follow-up PRs нужны):

  • objective_corpus_room_month.room_bucket (upstream ETL) НЕ знает euro — velocity-side в _INLINE_VELOCITY_SQL / mv_layout_velocity будет выдавать '2' для всех euro. Следствие: top_layouts пока не покажет euro строки (supply_map ключи euro-1/euro-2 будут «висеть» неиспользованными). Это более точно для bucket='2', но требует ETL update чтобы выделить euro в velocity.
  • frontend/src/types/best-layouts.ts RoomBucket Literal — out of sync (нет "euro-1"/"euro-2"); runtime safe т.к. TopLayoutRow.room_bucket: string.
  • BestLayoutsBlock.tsx ROOM_BUCKET_LABELS — нет «Евро-1»/«Евро-2», fallback покажет raw "euro-1". Cosmetic.

Все три — отдельным PR (ETL + frontend labels + codegen).

Merged via deep-code-reviewer — verdict APPROVE. **Анализ:** - SQL CASE order корректен: `studio` → `rooms=0 studio` → `euro-1` (rooms=2 AND area<35) → `euro-2` (rooms=2 AND area<50) → `rooms IN (1,2,3)` → `4+`. Mutually exclusive, first-match. - Boundary `<` strict: area=35.0 идёт в `euro-2`, area=50.0 идёт в `'2'`. SQL ↔ Python mirror точный. - NULL safety: `total_area IS NULL` + rooms=2 → `'2'` (default). В Python та же логика (skip euro branch). - Backward-compatible Python signature (`total_area: float | None = None` kwarg) — existing tests `test_layout_signature.py` не сломаны. **Post-deploy actions:** 1. `REFRESH MATERIALIZED VIEW CONCURRENTLY mv_layout_velocity;` (опционально — MV definition не менялась, но per author test plan). 2. Smoke: `/analyze` для `66:41:0702048:27` → проверить `supply_units_in_radius` для bucket=`'2'` снизится на ~29.5k euro-формат строк. **Known limitations (follow-up PRs нужны):** - `objective_corpus_room_month.room_bucket` (upstream ETL) НЕ знает euro — velocity-side в `_INLINE_VELOCITY_SQL` / `mv_layout_velocity` будет выдавать `'2'` для всех euro. Следствие: top_layouts пока не покажет euro строки (supply_map ключи euro-1/euro-2 будут «висеть» неиспользованными). Это **более точно** для bucket=`'2'`, но требует ETL update чтобы выделить euro в velocity. - `frontend/src/types/best-layouts.ts` `RoomBucket` Literal — out of sync (нет `"euro-1"`/`"euro-2"`); runtime safe т.к. `TopLayoutRow.room_bucket: string`. - `BestLayoutsBlock.tsx ROOM_BUCKET_LABELS` — нет «Евро-1»/«Евро-2», fallback покажет raw `"euro-1"`. Cosmetic. Все три — отдельным PR (ETL + frontend labels + codegen).
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#281
No description provided.