§4.2 «Анализ планировок»: покрытие 17% при 100% данных в Объективе — дедуп ЖК + расширение mapping + lots-fallback #2177

Closed
opened 2026-07-02 17:41:00 +00:00 by bot-backend · 3 comments
Collaborator

Фидбек Anton 2026-07-02 (участок 66:41:0204016:10): «покрытие 17% (2 из 12 ЖК) — в Объективе 100% покрытие новостроек ЕКБ».

Диагноз (разведка по коду и прод-БД)

GET /parcels/{cad}/best-layouts (backend/app/services/site_finder/best_layouts.py:693-714) считает coverage = доля ЖК радиуса с velocity-строками из objective_corpus_room_month через objective_complex_mapping.

Разложение «12 ЖК» из domrf_kn_objects вокруг участка:

  • Дубли раздувают знаменатель: Симфония ×3 (obj_id 3346/43063/43064), Квартет ×2, Квартал Депо ×2, 3 безымянных → уникальных комплексов реально ~6-8, не 12.
  • Mapping неполон: objective_complex_mapping покрывает только 3 obj_id из 12 (25%); лоты в objective_lots есть у всех 3 mapped (snapshot 2026-06-30, 100% mapped).
  • Из 3 mapped velocity в выбранном окне — у 2 → «2 из 12» = 17%.

План

  1. Дедуп знаменателя: группировать ЖК радиуса по уникальному комплексу (имя+гео кластер), а не по obj_id → честный знаменатель.
  2. Расширить mapping: авто-матчинг domrf_kn_objects ↔ objective complex (нормализация имени + гео-близость), покрыть дубли и безымянные.
  3. Fallback-источник планировок: при отсутствии velocity у комплекса брать структуру планировок (room_bucket × площадь × цена) из objective_lots (актуальный snapshot) с пометкой «по предложению, без темпа продаж».
  4. Пересчитать пороги confidence после дедупа.

Ключевые файлы: best_layouts.py:88-105 (радиус-SQL), :128-199 (velocity SQL), :693-714 (coverage), frontend/src/components/site-finder/BestLayoutsBlock.tsx:64-116.

Фидбек Anton 2026-07-02 (участок 66:41:0204016:10): «покрытие 17% (2 из 12 ЖК) — в Объективе 100% покрытие новостроек ЕКБ». ## Диагноз (разведка по коду и прод-БД) `GET /parcels/{cad}/best-layouts` (`backend/app/services/site_finder/best_layouts.py:693-714`) считает coverage = доля ЖК радиуса с velocity-строками из `objective_corpus_room_month` через `objective_complex_mapping`. Разложение «12 ЖК» из `domrf_kn_objects` вокруг участка: - **Дубли раздувают знаменатель**: Симфония ×3 (obj_id 3346/43063/43064), Квартет ×2, Квартал Депо ×2, 3 безымянных → уникальных комплексов реально ~6-8, не 12. - **Mapping неполон**: `objective_complex_mapping` покрывает только 3 obj_id из 12 (25%); лоты в `objective_lots` есть у всех 3 mapped (snapshot 2026-06-30, 100% mapped). - Из 3 mapped velocity в выбранном окне — у 2 → «2 из 12» = 17%. ## План 1. **Дедуп знаменателя**: группировать ЖК радиуса по уникальному комплексу (имя+гео кластер), а не по obj_id → честный знаменатель. 2. **Расширить mapping**: авто-матчинг domrf_kn_objects ↔ objective complex (нормализация имени + гео-близость), покрыть дубли и безымянные. 3. **Fallback-источник планировок**: при отсутствии velocity у комплекса брать структуру планировок (room_bucket × площадь × цена) из `objective_lots` (актуальный snapshot) с пометкой «по предложению, без темпа продаж». 4. Пересчитать пороги confidence после дедупа. Ключевые файлы: `best_layouts.py:88-105` (радиус-SQL), `:128-199` (velocity SQL), `:693-714` (coverage), `frontend/src/components/site-finder/BestLayoutsBlock.tsx:64-116`.
Author
Collaborator

Шаг 1/3 live и прод-верифицирован (PR #2198, qa-скриншот): знаменатель в комплексах — на 66:41:0204016:10 теперь «Среднее · покрытие 33% (2 из 6 комплексов)» вместо «Низкое · 17% (2 из 12 ЖК)»; raw_objects_total в data_quality для прозрачности; api-types regen.

Остаётся:

  • Шаг 2 — авто-расширение mapping (главный рычаг покрытия): в objective_complex_mapping (303 строки) уже есть поля match_method/match_score/is_reviewed под авто-матчинг; objective-сторона — 875 уникальных project_name с developer и address; несопоставленных domrf obj_id — 1253. План: скрипт fuzzy-матчинга (нормализованное ядро имени из шага 1 + застройщик; адрес/DaData-геокод как tie-breaker), автозаписи с is_reviewed=false + score, отчёт по покрытию до/после.
  • Шаг 3 — lots-fallback: для комплексов без velocity показывать структуру планировок из objective_lots с пометкой «по предложению, без темпа продаж».
Шаг 1/3 live и прод-верифицирован (PR #2198, qa-скриншот): знаменатель в комплексах — на 66:41:0204016:10 теперь «Среднее · покрытие 33% (2 из 6 комплексов)» вместо «Низкое · 17% (2 из 12 ЖК)»; `raw_objects_total` в data_quality для прозрачности; api-types regen. Остаётся: - **Шаг 2 — авто-расширение mapping** (главный рычаг покрытия): в `objective_complex_mapping` (303 строки) уже есть поля `match_method`/`match_score`/`is_reviewed` под авто-матчинг; objective-сторона — 875 уникальных project_name с developer и address; несопоставленных domrf obj_id — 1253. План: скрипт fuzzy-матчинга (нормализованное ядро имени из шага 1 + застройщик; адрес/DaData-геокод как tie-breaker), автозаписи с `is_reviewed=false` + score, отчёт по покрытию до/после. - **Шаг 3 — lots-fallback**: для комплексов без velocity показывать структуру планировок из `objective_lots` с пометкой «по предложению, без темпа продаж».
Author
Collaborator

Шаги 2 + 2b — LIVE, покрытие на тест-участке дошло до «Высокое»

Шаг 2 (PR #2237, applied): auto_core_dev_v5 — dry-run 25 tier_a пар (все глазами чистые: 7 Ключей→ГК Эфес, Аксиома→НКС, Шолохов→Астра, К100→Практика, Курчатов→Талан, Тихий центр→Синара…), apply → +8 комплексов в mapping (25 схлопнулись по UNIQUE — одна строка на комплекс). tier_b=31 (кандидаты DaData-гео прохода), ambiguous=14.
Прод-грабли: is_ekb проставлен только на старых снапшотах — WHERE is_ekb=true внутри DISTINCT ON отдавал устаревшие имена; фильтр «имел флаг хоть на одном снапшоте» + строка с latest (935 несопоставленных со свежими именами).

Шаг 2b (PR #2239): UNIQUE-констрейнт mapping бьёт один obj_id на комплекс → 2-я очередь «7 Ключей» в радиусе не резолвила сделки 1-й. Атрибуция velocity переведена на ядро имени комплекса (UNION с mapping-путём, без двойного счёта; developers-запрос scoped: 99 мс вместо 3.8 с).

Замер на 66:41:0204016:10: утром «Низкое · 17% (2 из 12 ЖК)» → после шага 1 «Среднее · 33% (2 из 6 комплексов)» → сейчас «Высокое · 50% (3 из 6)», groups_matched_by_core=1, сделки «7 Ключей» (162 корпус-месяца, свежие 2026-06) вошли в top_layouts (velocity 2-комн 4.0→5.0/мес).

Остаётся шаг 3 — lots-fallback планировок для комплексов без velocity (в работе).

## Шаги 2 + 2b — LIVE, покрытие на тест-участке дошло до «Высокое» **Шаг 2 (PR #2237, applied):** `auto_core_dev_v5` — dry-run 25 tier_a пар (все глазами чистые: 7 Ключей→ГК Эфес, Аксиома→НКС, Шолохов→Астра, К100→Практика, Курчатов→Талан, Тихий центр→Синара…), apply → **+8 комплексов** в mapping (25 схлопнулись по UNIQUE — одна строка на комплекс). tier_b=31 (кандидаты DaData-гео прохода), ambiguous=14. Прод-грабли: `is_ekb` проставлен только на старых снапшотах — `WHERE is_ekb=true` внутри DISTINCT ON отдавал устаревшие имена; фильтр «имел флаг хоть на одном снапшоте» + строка с latest (935 несопоставленных со свежими именами). **Шаг 2b (PR #2239):** UNIQUE-констрейнт mapping бьёт один obj_id на комплекс → 2-я очередь «7 Ключей» в радиусе не резолвила сделки 1-й. Атрибуция velocity переведена на **ядро имени комплекса** (UNION с mapping-путём, без двойного счёта; developers-запрос scoped: 99 мс вместо 3.8 с). **Замер на 66:41:0204016:10:** утром «Низкое · 17% (2 из 12 ЖК)» → после шага 1 «Среднее · 33% (2 из 6 комплексов)» → **сейчас «Высокое · 50% (3 из 6)», groups_matched_by_core=1**, сделки «7 Ключей» (162 корпус-месяца, свежие 2026-06) вошли в top_layouts (velocity 2-комн 4.0→5.0/мес). Остаётся шаг 3 — lots-fallback планировок для комплексов без velocity (в работе).
Author
Collaborator

Шаг 3 — LIVE, все три шага issue доставлены. Закрываю.

Шаг 3 (PR #2240 + фикс пула #2241): комплексы без сделок теперь показывают структуру предложения из objective_lots — блок «По предложению (без темпа продаж)» под топ-планировками (физлот-дедуп против ре-листинг ×2.91, «в продаже»+price-sanity, дизъюнктность с velocity-блоком).
Прод-smoke нашёл дыру первой версии: пул имён матчинга был только из corpus (263 проекта со сделками) — lots-only проекты («Основа» 3.7k лотов) не могли сматчиться. Фикс #2241: пул = corpus-имена (приоритет по ядру) + lots-имена с новыми ядрами.
Прод-verify (участок 66:41:0702033:20): groups_with_supply_only=1, блок наполнен (проект «Июль»: 2к 60-80 · 140 641 ₽/м², 3к 80-100 · 162 000 ₽/м²).

Итог по issue (все на проде, verified):

Шаг PR Эффект на 66:41:0204016:10
1. Знаменатель в комплексах #2198 «17% (2 из 12 ЖК)» → «33% (2 из 6 комплексов)», Среднее
2. auto_core_dev_v5 (+8 комплексов) #2237 mapping 303→311
2b. Атрибуция velocity по ядру #2239 «50% (3 из 6)», Высокое; сделки «7 Ключей» в агрегатах
3. Supply-only fallback #2240+#2241 комплексы без сделок — структура предложения

Остатки вне scope (заведу при следующем заходе): tier_b=31 + ambiguous=14 → DaData-гео проход; INCLUDE price_per_m2_rub в physflat-covering индекс (worst-case 500мс→55мс).

## Шаг 3 — LIVE, все три шага issue доставлены. Закрываю. **Шаг 3 (PR #2240 + фикс пула #2241):** комплексы без сделок теперь показывают структуру предложения из `objective_lots` — блок «По предложению (без темпа продаж)» под топ-планировками (физлот-дедуп против ре-листинг ×2.91, «в продаже»+price-sanity, дизъюнктность с velocity-блоком). Прод-smoke нашёл дыру первой версии: пул имён матчинга был только из corpus (263 проекта **со сделками**) — lots-only проекты («Основа» 3.7k лотов) не могли сматчиться. Фикс #2241: пул = corpus-имена (приоритет по ядру) + lots-имена с новыми ядрами. Прод-verify (участок 66:41:0702033:20): `groups_with_supply_only=1`, блок наполнен (проект «Июль»: 2к 60-80 · 140 641 ₽/м², 3к 80-100 · 162 000 ₽/м²). **Итог по issue (все на проде, verified):** | Шаг | PR | Эффект на 66:41:0204016:10 | |---|---|---| | 1. Знаменатель в комплексах | #2198 | «17% (2 из 12 ЖК)» → «33% (2 из 6 комплексов)», Среднее | | 2. auto_core_dev_v5 (+8 комплексов) | #2237 | mapping 303→311 | | 2b. Атрибуция velocity по ядру | #2239 | **«50% (3 из 6)», Высокое**; сделки «7 Ключей» в агрегатах | | 3. Supply-only fallback | #2240+#2241 | комплексы без сделок — структура предложения | Остатки вне scope (заведу при следующем заходе): tier_b=31 + ambiguous=14 → DaData-гео проход; INCLUDE price_per_m2_rub в physflat-covering индекс (worst-case 500мс→55мс).
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#2177
No description provided.