fix(tradein/sale-share): дедуп карточки листингов + чистка голых алиасов (#4 + P4a) #2083

Merged
lekss361 merged 1 commit from fix/tradein-clean-bare-aliases into main 2026-06-28 21:23:56 +00:00
Owner

#4 — Дедуп карточки листингов (Искровцев 33)

Карточка дома показывала cross-площадочные дубли: house 11176 «Искровцев 33» — 2 реальные квартиры = 4 строки (2к/49/эт1 на avito+domklik по 4.2М; 3к/63.7/эт1 на avito+cian по 4.99М). Метрика-то правильная (active_secondary=2, дедуп мигр.148), а детальный список — нет.

build_listings_query теперь дедуплит по той же сигнатуре (rooms, round(area_m2), floor), что и view — одна строка на квартиру. Представитель — самый дешёвый листинг; новая колонка sources[] = площадки, где квартира видна (для бейджа «также на cian/domklik»). Гео-фильтр ≤300м (мигр.150) сохранён. NULL-safe join-ключи (IS NOT DISTINCT FROM) — квартиры без rooms/area/floor не выпадают. +опциональное поле BuildingListing.sources в схеме (endpoint pass-through).

P4a — Чистка голых алиасов (мигр.151)

house_address_aliases: 6338/21720 (29%) алиасов — «голые улицы» без номера дома («екатеринбург улица мамина сибиряка»). Это сток, в который любой безномерный листинг (Яндекс-SERP) сваливался через Tier-2b → чужие листинги в одном доме. Матчер захардненен (P1, #2082) — голые больше не создаются/не матчатся. Мигр.151 удаляет накопленный сток.

Регэксп !~ '(^|[[:space:]])[0-9][^[:space:]]*$' идентичен Python-хелперу has_house_number. Алиасы — регенерируемый кэш матчинга; DELETE не трогает listings.house_id_fk/house_sources/houses. Идемпотентно, таблица ~21.7k → мгновенно.

Проверки

  • ruff clean; pytest test_buildings_api26 passed (+ test_listings_query_dedups_by_signature).
  • DISTINCT ON / ORDER BY ключи консистентны с сигнатурой мигр.148 (round(area_m2)).

Контекст (что уже live)

Это #4 в серии sale-share-фиксов: ЖКХ-знаменатель (#2077), гео-фильтр числителя (#2080), матчер-хардненинг (#2082). Остаётся: P4c дубли house-записей (Вайнера 66 ×2 — через merge_duplicate_houses с нормализованным ключом), P4b ре-backfill cemented mis-matches (метрика уже чинится гео-фильтром).

После мержа

Smoke: docker exec tradein-backend curl localhost:8000/api/v1/buildings/11176/listings → 2 строки с sources (["avito","domclick"], ["avito","cian"]). FE-тип sale-share.ts — опц. поле sources добавить отдельно (FE игнорит лишнее, не ломается).

## #4 — Дедуп карточки листингов (Искровцев 33) Карточка дома показывала cross-площадочные дубли: house 11176 «Искровцев 33» — **2 реальные квартиры = 4 строки** (2к/49/эт1 на avito+domklik по 4.2М; 3к/63.7/эт1 на avito+cian по 4.99М). Метрика-то правильная (`active_secondary=2`, дедуп мигр.148), а детальный список — нет. `build_listings_query` теперь дедуплит по **той же сигнатуре** `(rooms, round(area_m2), floor)`, что и view — одна строка на квартиру. Представитель — самый дешёвый листинг; новая колонка **`sources[]`** = площадки, где квартира видна (для бейджа «также на cian/domklik»). Гео-фильтр ≤300м (мигр.150) сохранён. NULL-safe join-ключи (`IS NOT DISTINCT FROM`) — квартиры без rooms/area/floor не выпадают. +опциональное поле `BuildingListing.sources` в схеме (endpoint pass-through). ## P4a — Чистка голых алиасов (мигр.151) `house_address_aliases`: **6338/21720 (29%)** алиасов — «голые улицы» без номера дома («екатеринбург улица мамина сибиряка»). Это сток, в который любой безномерный листинг (Яндекс-SERP) сваливался через Tier-2b → чужие листинги в одном доме. Матчер захардненен (P1, #2082) — голые больше не создаются/не матчатся. Мигр.151 удаляет накопленный сток. Регэксп `!~ '(^|[[:space:]])[0-9][^[:space:]]*$'` идентичен Python-хелперу `has_house_number`. Алиасы — **регенерируемый кэш** матчинга; DELETE не трогает `listings.house_id_fk`/`house_sources`/`houses`. Идемпотентно, таблица ~21.7k → мгновенно. ## Проверки - ruff clean; `pytest test_buildings_api` → **26 passed** (+ `test_listings_query_dedups_by_signature`). - DISTINCT ON / ORDER BY ключи консистентны с сигнатурой мигр.148 (`round(area_m2)`). ## Контекст (что уже live) Это #4 в серии sale-share-фиксов: ✅ ЖКХ-знаменатель (#2077), ✅ гео-фильтр числителя (#2080), ✅ матчер-хардненинг (#2082). Остаётся: **P4c** дубли house-записей (Вайнера 66 ×2 — через `merge_duplicate_houses` с нормализованным ключом), **P4b** ре-backfill cemented mis-matches (метрика уже чинится гео-фильтром). ## После мержа Smoke: `docker exec tradein-backend curl localhost:8000/api/v1/buildings/11176/listings` → 2 строки с `sources` (`["avito","domclick"]`, `["avito","cian"]`). FE-тип `sale-share.ts` — опц. поле `sources` добавить отдельно (FE игнорит лишнее, не ломается).
lekss361 added 1 commit 2026-06-28 21:22:54 +00:00
fix(tradein/sale-share): дедуп карточки листингов + чистка голых алиасов
All checks were successful
CI / changes (pull_request) Successful in 8s
CI / frontend-tests (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
b105e0a500
#4 Карточка дома показывала cross-площадочные дубли (Искровцев 33: 2 квартиры
= 4 строки avito/domklik/cian). build_listings_query теперь дедуплит по сигнатуре
(rooms, round(area_m2), floor) — как v_building_sale_share мигр.148 — одна строка
на квартиру, представитель = самый дешёвый, +колонка sources[] (площадки).
Гео-фильтр ≤300м (мигр.150) сохранён. +BuildingListing.sources в схеме.

P4a Мигр.151: DELETE 6338/21720 голых алиасов house_address_aliases (без номера
дома) — сток, в который сваливались безномерные листинги (Tier-2b). Матчер уже
захардненен (P1 #2082), голые не регенерируют. Алиасы — регенерируемый кэш,
house_id_fk/house_sources не тронуты.

ruff clean, pytest test_buildings_api 26 green.
lekss361 merged commit 1b515f4e94 into main 2026-06-28 21:23:56 +00:00
lekss361 deleted branch fix/tradein-clean-bare-aliases 2026-06-28 21:23:56 +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#2083
No description provided.