feat(tradein): recurring house-dedup merge (schedule dormant) (#1772)
Reuses migration 108's proven collision-safe pattern
(cluster → canonical keeper → re-point children with UNIQUE-collision
handling → delete losers → backfill sources/aliases) as a RECURRING,
idempotent scheduled job — dups recur because the matching pipeline keeps
creating them, so a one-time migration cannot fix the steady-state inflow.
- Service app/services/house_dedup_merge.py: merge_duplicate_houses(db, *,
dry_run=False). Clusters houses by EXACT address (cadastral 100% NULL →
address is the real key). Keeper: geom NOT NULL → most listings →
most-populated → min(id). Handles ALL 11 FK children incl. live 6-col
houses_price_dynamics UNIQUE (migration 029 replaced 108's stale 3-col).
One transaction; dry_run computes counts then ROLLBACK; idempotent
(clean table → empty mapping → 0-row no-op). Audit-logs every merge.
- Scheduler: trigger_house_dedup_merge_run (run_in_executor, sync DB-only) +
dispatch branch + run_house_dedup_merge lifecycle wrapper.
- Migration 135: seeds scrape_schedules house_dedup_merge enabled=FALSE
(DORMANT — deploy neutral; enable deliberately after dry-run + manual run),
weekly window, ON CONFLICT DO NOTHING.
- Tests: static SQL-shape (every FK child + collision handling, migration-133
guard), fake-db dry_run/idempotent, scheduler wiring, optional real-DB
end-to-end merge test (self-skips without a DB).