fix(tradein): backfill house_id_fk via source-identity path (#1781) #1936

Merged
bot-backend merged 1 commit from fix/1781-house-fk-source-identity into main 2026-06-27 00:05:15 +00:00
Collaborator

Что

Идемпотентный бэкфилл listings.house_id_fk по per-listing-identity пути (house_sources по ext_source=source, ext_id=source_id) — то, что миграция 130 НЕ покрыла.

Почему (продолжение #1781 / migration 130)

Realtime FK-mirror (base.py:704-750) резолвит дом для каждого listing: если скрейпер вытащил house-catalog id — матчит по (house_source, house_ext_id); иначе падает на identity самого listing:

match_or_create_house(ext_source=lot.source, ext_id=lot.source_id or dedup_hash)

Tier-1 матчера (matching/houses.py:96-113, confidence 1.0) = SELECT house_id FROM house_sources WHERE ext_source=:s AND ext_id=:e.

Прод-замер (2026-06-27, read-only): осталось 1884 active NULL-FK, из них 0 с house_source/house_ext_id → re-run 130 = no-op. 744 из них резолвятся по (source, source_id). С учётом inactive — путь (source, source_id) резолвит 4608 NULL-FK всего.

Что делает 136

Повторяет ровно этот Tier-1 lookup офлайн:

UPDATE listings l SET house_id_fk = hs.house_id
FROM house_sources hs
WHERE l.house_id_fk IS NULL AND l.source_id IS NOT NULL
  AND hs.ext_source = l.source AND hs.ext_id = l.source_id AND hs.house_id IS NOT NULL;

Восстанавливает каноническую группировку «тот же дом» (estimator Tier-S, JOIN houses ON house_id_fk=id).

Безопасность

  • Faithful replay доверенного live-mirror'а (тот же lookup каждый скрейп) — нулевой доп-риск.
  • Идемпотентно: WHERE house_id_fk IS NULL → повторный прогон strict no-op.
  • Детерминированно: только existing canonical match, без fuzzy/geo/создания домов.
  • Collision-safe: house-catalog id'шники под отдельными ext_source (cian_newbuilding, yandex_realty_nb); UNIQUE(ext_source,ext_id) → ≤1 house_id на (source,source_id).
  • Dry-run на проде (BEGIN; UPDATE; ROLLBACK): fills 4608, откатан. SQL валиден против live-схемы.
  • Inactive тоже заполняются (нет is_active-фильтра): корректная история для backtest #667, на live-скоринг не влияет (estimator фильтрует is_active).

Остаётся (НЕ в этом PR — зона Лёхи)

~1140 active NULL-FK без canonical source-id матча → нужен re-scrape/re-match (расширение охвата cian_city_sweep + обновление cian-cookies, #1781). И freshness заморо­женной cian-первички (корень #1781) — тоже scraper-зона.

Refs #1781

## Что Идемпотентный бэкфилл `listings.house_id_fk` по **per-listing-identity** пути (`house_sources` по `ext_source=source, ext_id=source_id`) — то, что миграция 130 НЕ покрыла. ## Почему (продолжение #1781 / migration 130) Realtime FK-mirror (`base.py:704-750`) резолвит дом для каждого listing: если скрейпер вытащил house-catalog id — матчит по `(house_source, house_ext_id)`; **иначе** падает на identity самого listing: ``` match_or_create_house(ext_source=lot.source, ext_id=lot.source_id or dedup_hash) ``` Tier-1 матчера (`matching/houses.py:96-113`, confidence 1.0) = `SELECT house_id FROM house_sources WHERE ext_source=:s AND ext_id=:e`. **Прод-замер (2026-06-27, read-only):** осталось **1884 active** NULL-FK, из них **0** с `house_source/house_ext_id` → re-run 130 = no-op. **744** из них резолвятся по `(source, source_id)`. С учётом inactive — путь `(source, source_id)` резолвит **4608** NULL-FK всего. ## Что делает 136 Повторяет ровно этот Tier-1 lookup офлайн: ```sql UPDATE listings l SET house_id_fk = hs.house_id FROM house_sources hs WHERE l.house_id_fk IS NULL AND l.source_id IS NOT NULL AND hs.ext_source = l.source AND hs.ext_id = l.source_id AND hs.house_id IS NOT NULL; ``` Восстанавливает каноническую группировку «тот же дом» (estimator Tier-S, `JOIN houses ON house_id_fk=id`). ## Безопасность - **Faithful replay** доверенного live-mirror'а (тот же lookup каждый скрейп) — нулевой доп-риск. - **Идемпотентно**: `WHERE house_id_fk IS NULL` → повторный прогон strict no-op. - **Детерминированно**: только existing canonical match, без fuzzy/geo/создания домов. - **Collision-safe**: house-catalog id'шники под отдельными `ext_source` (`cian_newbuilding`, `yandex_realty_nb`); `UNIQUE(ext_source,ext_id)` → ≤1 house_id на `(source,source_id)`. - **Dry-run** на проде (BEGIN; UPDATE; ROLLBACK): fills 4608, откатан. SQL валиден против live-схемы. - Inactive тоже заполняются (нет is_active-фильтра): корректная история для backtest #667, на live-скоринг не влияет (estimator фильтрует is_active). ## Остаётся (НЕ в этом PR — зона Лёхи) ~1140 active NULL-FK без canonical source-id матча → нужен re-scrape/re-match (расширение охвата `cian_city_sweep` + обновление cian-cookies, #1781). И freshness заморо­женной cian-первички (корень #1781) — тоже scraper-зона. Refs #1781
bot-backend added 1 commit 2026-06-27 00:01:34 +00:00
fix(tradein): backfill house_id_fk via source-identity path (#1781)
All checks were successful
CI / changes (pull_request) Successful in 7s
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
6ce60a0d8c
Migration 130 only filled house_id_fk for listings carrying
house_source/house_ext_id. Prod (2026-06-27): 1884 active NULL-FK rows
remain, 0 with house_source/ext_id (130 re-run = no-op). They were
matched via the per-listing-identity path the realtime mirror uses when
no house catalog id exists: match_or_create_house(ext_source=source,
ext_id=source_id) -> Tier-1 house_sources lookup (confidence 1.0).

136 replays that exact lookup offline: UPDATE listings SET house_id_fk
= house_sources.house_id WHERE (ext_source=source, ext_id=source_id) and
house_id_fk IS NULL. Faithful to base.py:704-750; idempotent (NULL
guard); deterministic (no fuzzy/geo); collision-safe (catalog ids live
under distinct ext_source like 'cian_newbuilding'). Dry-run BEGIN/ROLLBACK
fills 4608 NULL-FK rows (744 active), restoring estimator Tier-S
"same-building" grouping. The remaining ~1140 active need re-scrape/
re-match (scraper coverage — #1781 Лёха).

Refs #1781
bot-backend merged commit a246044583 into main 2026-06-27 00:05:15 +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#1936
No description provided.