feat(tradein): same-building матчинг аналогов по house_id (#6) #613

Merged
Light1YT merged 1 commit from feat/tradein-same-building-match into main 2026-05-28 08:57:00 +00:00
Owner

Summary

Закрывает основную часть #6 (house-match приоритет аналогов) — прямой выигрыш в точности оценки.

Estimator теперь резолвит target-адрес в канонический house_id и подбирает аналоги по listings.house_id_fk — детерминированный Tier S «тот же дом» вместо хрупкого address-string prefix match.

Почему точнее: 99% листингов (18282/18428) слинкованы с houses. Раньше Tier S матчил address ILIKE prefix — ломается между источниками (Avito/Cian/Yandex форматируют адрес по-разному: «ул Малышева, д 125» vs «Малышева 125»). Теперь — по узлу house-графа, точно тот же дом.

Changes

File Что
matching/houses.py match_house_readonly() — read-only target→house резолвер (cadastr_exact → fingerprint → geo 50m), без INSERT / house_sources / advisory-lock. None если нет уверенного match.
estimator.py estimate_quality: резолвит target_house_id после geocode+DaData (graceful). _fetch_analogs: новый param + канонический Tier S (WHERE house_id_fk = target) ПЕРЕД address-prefix Tier S (fallback).
tests/test_same_building_match.py 9 новых тестов

3 файла, +308/-5.

Каскад матчинга (после PR)

Tier S (canonical): listings.house_id_fk = target_house_id   ← НОВОЕ, тот же дом
Tier S (fallback):  address ILIKE prefix                      ← если house_id None / <3
Tier H: PostGIS radius + year±15 + floors±30%
Tier W: wide radius

Прогрессивная точность

Target уже получает house_cadnum от DaData (Q1). match_house_readonly пробует cadastr_exact первым → как только Q3-backfill заполнит houses.cadastral_number, матчинг станет ещё строже без изменения кода.

Tests

tests/test_same_building_match.py — 9 passed
  • match_house_readonly: cadastr / fingerprint / geo hit, no-match→None, never-inserts (assert нет INSERT/UPDATE/advisory в SQL), no-coords→skip geo
  • _fetch_analogs: canonical Tier S fires при target_house_id (assert SQL содержит house_id_fk); fallback на address ILIKE без house_id; <3 в доме → fall-through

Ruff clean. ⚠️ Pre-existing: 4 fail в test_estimator_source_quota.py (unpack 2≠3 values) — красные на main ДО этого PR, не затронуты (есть отдельный chip на фикс).

Deploy

Просто merge → backend redeploy. Миграций нет (использует существующие listings.house_id_fk + houses). Smoke: /estimate на Малышева 125 → в логах analogs tier=S(canonical) house_id=....

Follow-ups

  • #569 — геокодинг ДКП-сделок (NULL coords) → same-building и по сделкам
  • #571 — вернуть rosreestr_deals в actual_deals
## Summary Закрывает основную часть **#6 (house-match приоритет аналогов)** — прямой выигрыш в **точности оценки**. Estimator теперь резолвит target-адрес в **канонический `house_id`** и подбирает аналоги по `listings.house_id_fk` — детерминированный Tier S «тот же дом» вместо хрупкого address-string prefix match. **Почему точнее:** 99% листингов (18282/18428) слинкованы с `houses`. Раньше Tier S матчил `address ILIKE prefix` — ломается между источниками (Avito/Cian/Yandex форматируют адрес по-разному: «ул Малышева, д 125» vs «Малышева 125»). Теперь — по узлу house-графа, точно тот же дом. ## Changes | File | Что | |---|---| | `matching/houses.py` | `match_house_readonly()` — read-only target→house резолвер (cadastr_exact → fingerprint → geo 50m), **без** INSERT / house_sources / advisory-lock. None если нет уверенного match. | | `estimator.py` | `estimate_quality`: резолвит `target_house_id` после geocode+DaData (graceful). `_fetch_analogs`: новый param + канонический Tier S (`WHERE house_id_fk = target`) ПЕРЕД address-prefix Tier S (fallback). | | `tests/test_same_building_match.py` | 9 новых тестов | **3 файла, +308/-5.** ## Каскад матчинга (после PR) ``` Tier S (canonical): listings.house_id_fk = target_house_id ← НОВОЕ, тот же дом Tier S (fallback): address ILIKE prefix ← если house_id None / <3 Tier H: PostGIS radius + year±15 + floors±30% Tier W: wide radius ``` ## Прогрессивная точность Target уже получает `house_cadnum` от DaData (Q1). `match_house_readonly` пробует cadastr_exact первым → как только Q3-backfill заполнит `houses.cadastral_number`, матчинг станет ещё строже **без изменения кода**. ## Tests ``` tests/test_same_building_match.py — 9 passed ``` - `match_house_readonly`: cadastr / fingerprint / geo hit, no-match→None, **never-inserts** (assert нет INSERT/UPDATE/advisory в SQL), no-coords→skip geo - `_fetch_analogs`: canonical Tier S fires при target_house_id (assert SQL содержит house_id_fk); fallback на address ILIKE без house_id; <3 в доме → fall-through Ruff clean. ⚠️ Pre-existing: 4 fail в `test_estimator_source_quota.py` (unpack 2≠3 values) — красные на main ДО этого PR, не затронуты (есть отдельный chip на фикс). ## Deploy Просто merge → backend redeploy. Миграций нет (использует существующие `listings.house_id_fk` + `houses`). Smoke: `/estimate` на Малышева 125 → в логах `analogs tier=S(canonical) house_id=...`. ## Follow-ups - #569 — геокодинг ДКП-сделок (NULL coords) → same-building и по сделкам - #571 — вернуть rosreestr_deals в actual_deals
Light1YT added 1 commit 2026-05-28 08:56:19 +00:00
Estimator теперь резолвит target-адрес в КАНОНИЧЕСКИЙ house_id (read-only,
без создания записи) и матчит аналоги по listings.house_id_fk — детерминированный
Tier S «тот же дом» вместо хрупкого address-string prefix match. 99% листингов
слинкованы с houses, поэтому это срабатывает уже сегодня и точнее работает между
разнородными источниками (Avito/Cian/Yandex форматируют адрес по-разному).

- matching/houses.py: match_house_readonly() — read-only target→house резолвер
  (cadastr_exact → fingerprint → geo_proximity 50m), без INSERT / house_sources /
  advisory lock. Возвращает None если уверенного match нет.
- estimator.estimate_quality: после geocode+DaData резолвит target_house_id
  (cadastr от DaData → cadastr_exact заработает по мере backfill houses), graceful.
- estimator._fetch_analogs: новый param target_house_id + канонический Tier S
  (WHERE house_id_fk = target) ПЕРЕД address-prefix Tier S (теперь fallback).
  tier label 'S' сохранён (frontend уже рендерит).

Эффект: вместо «похожие в радиусе 800-1000м» — «вот что в ЭТОМ доме», прямой
выигрыш в точности оценки. Foundation под cadastr/FIAS exact match (DaData Q1/Q3).

Tests: 9 новых (match_house_readonly tiers + never-inserts + canonical Tier S
fires/fallback/too-few). Ruff clean. Pre-existing 4 fail в test_estimator_source_quota
(unpack 2≠3, красные на main до этого PR) — не затронуты.
Light1YT merged commit 1d296dd77a into main 2026-05-28 08:57:00 +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#613
No description provided.