[tradein] Один дом дробится на несколько houses → same-building компы теряются (unify по кадастру) #1772

Open
opened 2026-06-19 08:26:01 +00:00 by bot-backend · 3 comments
Collaborator

Симптом: оценщик по «Евгения Савкова 29» не учитывает объявления В ТОМ ЖЕ ДОМЕ (есть на циане/авито).

Корень: один физический дом существует как НЕСКОЛЬКО записей houses, потому что cadastral_number проставлен НЕ у всех, а адреса по источникам разные («29» vs «29А» vs без номера). match_or_create_house Tier 0 (кадастр, conf 1.0) решил бы это, но кадастра в данных нет → матчинг сваливается на fingerprint/гео и плодит дубли-дома. Эстиматор якорит компы к ОДНОМУ house_id → видит лишь фрагмент.

Фикс: бэкфилл cadastral_number на записи houses (НСПД по координатам) → слить дубли-фрагменты по кадастру/fingerprint. Зависит от прогона НСПД (данные). Родственно #774/#850.


Доказательная база (live prod, cad-кейс ЖК Суходольский квартал, ул. Евгения Савкова 29, ЕКБ; cian 330047129):

  • Один физический дом размазан на несколько houses: 6679=«…Евгения Савкова, 29А» (кадастр 66:41:0313010:11396, сюда матчится cian 330047129), 11110=«ул. Евгения Савкова,29» (12 listings, avito), 7019=«улица Евгения Савкова» (без номера, 42 listings, catch-all). «29»/«29А» не объединены.
  • listings по «Савкова»: avito 330 (41 unmatched), cian 97 (35 unmatched), yandex 48 (4 unmatched).
  • cian 330047129 задвоен: одна копия → house 6679, другая → house_id_fk=NULL.
  • estimator.py:1441 сам фиксирует проблему: «один дом дробится на несколько fk (Хохрякова 48 = 7085/9878/12797)».
**Симптом:** оценщик по «Евгения Савкова 29» не учитывает объявления В ТОМ ЖЕ ДОМЕ (есть на циане/авито). **Корень:** один физический дом существует как НЕСКОЛЬКО записей `houses`, потому что `cadastral_number` проставлен НЕ у всех, а адреса по источникам разные («29» vs «29А» vs без номера). `match_or_create_house` Tier 0 (кадастр, conf 1.0) решил бы это, но кадастра в данных нет → матчинг сваливается на fingerprint/гео и плодит дубли-дома. Эстиматор якорит компы к ОДНОМУ house_id → видит лишь фрагмент. **Фикс:** бэкфилл `cadastral_number` на записи houses (НСПД по координатам) → слить дубли-фрагменты по кадастру/fingerprint. **Зависит от прогона НСПД (данные).** Родственно #774/#850. --- **Доказательная база (live prod, cad-кейс ЖК Суходольский квартал, ул. Евгения Савкова 29, ЕКБ; cian 330047129):** - Один физический дом размазан на несколько `houses`: `6679`=«…Евгения Савкова, **29А**» (кадастр 66:41:0313010:11396, сюда матчится cian 330047129), `11110`=«ул. Евгения Савкова,**29**» (12 listings, avito), `7019`=«улица Евгения Савкова» (**без номера**, 42 listings, catch-all). «29»/«29А» не объединены. - listings по «Савкова»: avito 330 (41 unmatched), cian 97 (35 unmatched), yandex 48 (4 unmatched). - cian 330047129 **задвоен**: одна копия → house 6679, другая → `house_id_fk=NULL`. - `estimator.py:1441` сам фиксирует проблему: «один дом дробится на несколько fk (Хохрякова 48 = 7085/9878/12797)».
bot-backend added the
bug
needs-human
tradein
scope/backend
labels 2026-06-19 08:26:01 +00:00
Author
Collaborator

Вердикт по фисибилити (read-only замер на проде, tradein-postgres): кадастровая унификация в текущем состоянии данных не делается безопасно — остаётся needs-human / blocked-on-data.

Замеры:

  • Кадастр заполнен на 19 из 13104 домов (~0.1%). 8483 дома с координатами, ~50k активных листингов на бескадастровых домах.
  • Единственный источник кадастра через FDW (gendesign_cad_buildings via gendesign_remote) — без геометрии: только колонки lat, lon, cad_num, readable_address, floors, area_m2, year_built, purpose. geom НЕТ → чистый point-in-parcel (ST_Contains) невозможен.
  • Координатная близость (≤~40 м) резолвит лишь 32/60 ≈ 53% домов, и БЕЗ надёжной сверки улицы (половина домов в выборке без улицы в address: «р-н Железнодорожный», «мкр. Автовокзал»; формат readable_address иной). В плотной первичке в 40 м попадает несколько парцелл → риск присвоить кадастр СОСЕДНЕГО дома.

Гнать на этом деструктивный merge ~13k домов = риск испортить корпус матчинга (то, что защищает #1534). Что нужно от Лехи / данных, чтобы разблокировать:

  1. Либо экспонировать в FDW таблицу gendesign с реальной геометрией парцелл (полигоны) для point-in-parcel, либо нарастить покрытие cadastral_number на домах (NSPD/Росреестр backfill).
  2. Тогда: идемпотентный backfill кадастра + merge-фрагментов как код, dry-run сначала.

Практический рычаг для жалобы «не учитывает тот же дом» (Евгения Савкова 29) уже закрыт без унификации — через #1774 (PR #1776): Tier A резолвит дом по адресу, а не по house_id, поэтому фрагментация его не ломает.

**Вердикт по фисибилити (read-only замер на проде, tradein-postgres):** кадастровая унификация в текущем состоянии данных **не делается безопасно** — остаётся `needs-human` / blocked-on-data. Замеры: - Кадастр заполнен на **19 из 13104 домов (~0.1%)**. 8483 дома с координатами, ~50k активных листингов на бескадастровых домах. - Единственный источник кадастра через FDW (`gendesign_cad_buildings` via `gendesign_remote`) — **без геометрии**: только колонки lat, lon, cad_num, readable_address, floors, area_m2, year_built, purpose. `geom` НЕТ → чистый point-in-parcel (`ST_Contains`) невозможен. - Координатная близость (≤~40 м) резолвит лишь **32/60 ≈ 53%** домов, и БЕЗ надёжной сверки улицы (половина домов в выборке без улицы в `address`: «р-н Железнодорожный», «мкр. Автовокзал»; формат `readable_address` иной). В плотной первичке в 40 м попадает несколько парцелл → риск присвоить кадастр СОСЕДНЕГО дома. Гнать на этом деструктивный merge ~13k домов = риск испортить корпус матчинга (то, что защищает #1534). **Что нужно от Лехи / данных, чтобы разблокировать:** 1. Либо экспонировать в FDW таблицу gendesign с реальной геометрией парцелл (полигоны) для point-in-parcel, либо нарастить покрытие `cadastral_number` на домах (NSPD/Росреестр backfill). 2. Тогда: идемпотентный backfill кадастра + merge-фрагментов как код, dry-run сначала. Практический рычаг для жалобы «не учитывает тот же дом» (Евгения Савкова 29) уже закрыт без унификации — через #1774 (PR #1776): Tier A резолвит дом по адресу, а не по house_id, поэтому фрагментация его не ломает.
Author
Collaborator

Разобрал (2026-06-27) — масштаб маленький, merge-логика уже есть

  • Кадастр-merge неприменим: houses.cadastral_number почти не заполнен (19 distinct на 13759 домов) — дублей по кадастру 0. Реальный дубль-ключ — address (точное совпадение).
  • Текущий масштаб: 25 групп / 50 строк (не «много»). Дубли возникают из matching-пайплайна: Tier 3 geo-fallback (>30м джиттер геокодеров) + разные ext_id у одного дома из разных источников (avito/cian/derived). Топ: «Сибирский тракт…7» — 2 дома, 14 листингов теряются.
  • Merge-миграция УЖЕ есть: 108_merge_duplicate_houses.sql (кластеризация cadastre|address|geo → canonical → re-point всех FK-детей с обработкой UNIQUE-коллизий, идемпотентна) — применена на проде. Но 50 дублей остались = рекуррентность: 108 был one-time backfill, а пайплайн продолжает плодить новые после него.

Вывод

Корень — не «нет merge-логики» (она есть), а рекуррентность. Варианты:

  1. Recurring merge (наиболее эффективно): вынести 108-логику в scheduled-таск (еженедельно) → дубли само-лечатся навсегда. Medium-effort (108 — это SQL, надо обернуть в callable+beat).
  2. Matching-tier hardening: поднять Tier 3 порог / лучше нормализовать адрес — рискованно (может сломать матчинг шире).
  3. One-off re-merge 50 сейчас — низкая ценность (рекурнёт).

При масштабе 50 дублей (low-impact) рекомендую (1) когда дойдут руки, либо принять как есть. Не band-aid'ю one-off'ом. Жду решения по приоритету — если «делай recurring merge», возьму.

## Разобрал (2026-06-27) — масштаб маленький, merge-логика уже есть - **Кадастр-merge неприменим:** `houses.cadastral_number` почти не заполнен (19 distinct на 13759 домов) — дублей по кадастру **0**. Реальный дубль-ключ — **address** (точное совпадение). - **Текущий масштаб:** **25 групп / 50 строк** (не «много»). Дубли возникают из matching-пайплайна: Tier 3 geo-fallback (>30м джиттер геокодеров) + разные ext_id у одного дома из разных источников (avito/cian/derived). Топ: «Сибирский тракт…7» — 2 дома, 14 листингов теряются. - **Merge-миграция УЖЕ есть:** `108_merge_duplicate_houses.sql` (кластеризация cadastre|address|geo → canonical → re-point всех FK-детей с обработкой UNIQUE-коллизий, идемпотентна) — **применена на проде**. Но 50 дублей остались = **рекуррентность**: 108 был one-time backfill, а пайплайн продолжает плодить новые после него. ### Вывод Корень — не «нет merge-логики» (она есть), а **рекуррентность**. Варианты: 1. **Recurring merge** (наиболее эффективно): вынести 108-логику в scheduled-таск (еженедельно) → дубли само-лечатся навсегда. Medium-effort (108 — это SQL, надо обернуть в callable+beat). 2. **Matching-tier hardening**: поднять Tier 3 порог / лучше нормализовать адрес — рискованно (может сломать матчинг шире). 3. One-off re-merge 50 сейчас — низкая ценность (рекурнёт). При масштабе 50 дублей (low-impact) рекомендую (1) когда дойдут руки, либо принять как есть. Не band-aid'ю one-off'ом. Жду решения по приоритету — если «делай recurring merge», возьму.
Author
Collaborator

Прогресс: слой 1 (точные дубли-дома) автоматизирован и запущен на проде

Задача распадается на два слоя, и закрыть её целиком пока нельзя — починен только первый.

Слой 1 — точные дубли houses по нормализованному адресу (РЕШЕНО, рекуррентно)

Появился сервис house_dedup_merge (PR #1933, мигр. 135):

  • кластеризует houses по lower(trim(address)), выбирает keeper (geom NOT NULL → больше listings → населённее → min id), перецепляет/дедуплицирует все 11 FK-детей в одной транзакции, идемпотентен, аудит-логирует каждый merge;
  • боевой прогон на проде сегодня: houses 13766 → 13739 (−27 дублей), listings_repointed=67, children_repointed=106, children_deleted=0; повторный dry-run → clusters_merged=0 (дублей не осталось);
  • поставлен на еженедельное расписание scrape_schedules.house_dedup_merge (enabled=true, окно 04:00–05:00 UTC, next_run_at=2026-06-27 04:00Z) — дубли с одинаковым адресом теперь будут схлопываться автоматически.

Слой 2 — фрагменты-варианты адреса (НЕ решено, упирается в данные)

Конкретный кейс из тикета — «Евгения Савкова 29» (house 11110) vs «29А» (6679) vs «улица Евгения Савкова без номера» (7019) — это разные строки адреса, поэтому address-merge их НЕ объединяет (и не должен — «29»/«29А» могут быть разными корпусами). Их можно надёжно слить только по кадастру, а кадастра в данных нет → нужен бэкфилл cadastral_number через прогон НСПД по координатам (зависимость, которую тикет сам же фиксирует; родственно #774/#850).

Что осталось для закрытия: прогон НСПД-бэкфилла кадастра на houses, после чего тот же house_dedup_merge можно расширить вторым ключом кластеризации (кадастр), и слой 2 схлопнётся автоматически. Оставляю открытым с меткой needs-human по этой причине.

## Прогресс: слой 1 (точные дубли-дома) автоматизирован и запущен на проде Задача распадается на **два слоя**, и закрыть её целиком пока нельзя — починен только первый. ### ✅ Слой 1 — точные дубли houses по нормализованному адресу (РЕШЕНО, рекуррентно) Появился сервис `house_dedup_merge` (PR #1933, мигр. `135`): - кластеризует houses по `lower(trim(address))`, выбирает keeper (geom NOT NULL → больше listings → населённее → min id), перецепляет/дедуплицирует все 11 FK-детей в одной транзакции, идемпотентен, аудит-логирует каждый merge; - **боевой прогон на проде сегодня:** `houses 13766 → 13739` (−27 дублей), `listings_repointed=67`, `children_repointed=106`, `children_deleted=0`; повторный dry-run → `clusters_merged=0` (дублей не осталось); - поставлен на **еженедельное расписание** `scrape_schedules.house_dedup_merge` (`enabled=true`, окно 04:00–05:00 UTC, `next_run_at=2026-06-27 04:00Z`) — дубли с одинаковым адресом теперь будут схлопываться автоматически. ### ⛔ Слой 2 — фрагменты-варианты адреса (НЕ решено, упирается в данные) Конкретный кейс из тикета — «Евгения Савкова **29**» (house 11110) vs «**29А**» (6679) vs «улица Евгения Савкова **без номера**» (7019) — это **разные строки адреса**, поэтому address-merge их НЕ объединяет (и не должен — «29»/«29А» могут быть разными корпусами). Их можно надёжно слить только по **кадастру**, а кадастра в данных нет → нужен бэкфилл `cadastral_number` через прогон **НСПД по координатам** (зависимость, которую тикет сам же фиксирует; родственно #774/#850). **Что осталось для закрытия:** прогон НСПД-бэкфилла кадастра на houses, после чего тот же `house_dedup_merge` можно расширить вторым ключом кластеризации (кадастр), и слой 2 схлопнётся автоматически. Оставляю открытым с меткой `needs-human` по этой причине.
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#1772
No description provided.