feat(tradein): backfill deals.lat/lon из house street-центроидов (#569 Step 2) #614

Merged
Light1YT merged 1 commit from feat/tradein-geocode-deals-from-houses into main 2026-05-28 09:26:01 +00:00
Owner

Summary

Закрывает основную часть #569 — делает реальные ДКП-сделки видимыми для эстиматора.

Проблема: deals (49,791 ДКП Росреестра) на 100% без координат (geom NULL). _fetch_deals матчит через ST_DWithin(geom) → ни одна сделка не находится → actual_deals всегда пустой. Это корень #571 («deals disabled») и потеря ground-truth для точности.

Решение (street-centroid join, zero внешних вызовов): строим per-street центроиды из ~8.6k уже геокодированных houses, проставляем lat/lon каждой сделке по нормализованному ключу улицы. geom авто-заполняется триггером deals_set_geom_trg. Street-level точности достаточно (радиус эстиматора 1-2км).

Changes

File Что
scripts/geocode_deals_from_houses.py backfill-скрипт: _street_key нормализация, per-street центроиды (AVG), per-row SAVEPOINT, --dry-run/--limit, resume через geocode_tried_at, топ-10 unmatched улиц
tests/scripts/test_geocode_deals_from_houses.py 26 тестов

+1031 LOC.

Pre-push review (code-reviewer) — поймал data-corruption баг

_APT_SUFFIX_RE без правого якоря жрал реальные улицы на кв/корп/оф/пом/стр («Строителей» → 'ул.' garbage key → неверный центроид → неверные координаты в прод). Fixed: требовать цифру после суффикс-токена (\d.*$) + регресс-тест на «Строителей/Офицеров/Корпусная/Помолова/Стрелочников». Verdict после фикса: .

Tests

tests/scripts/test_geocode_deals_from_houses.py — 26 passed

street-key (3-variant collapse, numeric streets «8 Марта», region/house-number strip, ё→е, apt-suffix regression), centroid AVG, matched UPDATE, no_street_match, --dry-run no-op, SAVEPOINT isolation, geom-not-manual. Ruff clean. (6 pre-existing unrelated fails: WeasyPrint/auth/cache/quota — не затронуты.)

Deploy plan

Миграций нет (использует существующие deals.lat/lon + deals_set_geom_trg + geocode_tried_at). После merge:

  1. --dry-run на проде → замерить projected coverage % + глянуть топ-10 unmatched улиц (убедиться что 'ул.'/'пр.' НЕ среди matched-ключей)
  2. Реальный прогон → deals WHERE lat IS NULL должно упасть существенно
  3. По остатку решить нужен ли Step 3 (Nominatim для непокрытых улиц)

Follow-ups (план #569)

  • Step 3: Nominatim batch для остатка (если coverage < 95%)
  • Step 4: incremental geocode после ДКП-импорта (≤24h freshness)

Closes часть #569 (Step 2 of 4).

## Summary Закрывает основную часть **#569** — делает реальные ДКП-сделки видимыми для эстиматора. **Проблема:** `deals` (49,791 ДКП Росреестра) на **100% без координат** (`geom NULL`). `_fetch_deals` матчит через `ST_DWithin(geom)` → ни одна сделка не находится → `actual_deals` всегда пустой. Это корень #571 («deals disabled») и потеря ground-truth для точности. **Решение (street-centroid join, zero внешних вызовов):** строим per-street центроиды из ~8.6k уже геокодированных `houses`, проставляем `lat/lon` каждой сделке по нормализованному ключу улицы. `geom` авто-заполняется триггером `deals_set_geom_trg`. Street-level точности достаточно (радиус эстиматора 1-2км). ## Changes | File | Что | |---|---| | `scripts/geocode_deals_from_houses.py` | backfill-скрипт: `_street_key` нормализация, per-street центроиды (AVG), per-row SAVEPOINT, `--dry-run`/`--limit`, resume через `geocode_tried_at`, топ-10 unmatched улиц | | `tests/scripts/test_geocode_deals_from_houses.py` | 26 тестов | **+1031 LOC.** ## Pre-push review (code-reviewer) — поймал data-corruption баг `_APT_SUFFIX_RE` без правого якоря жрал реальные улицы на кв/корп/оф/пом/стр («Строителей» → `'ул.'` garbage key → неверный центроид → **неверные координаты в прод**). **Fixed:** требовать цифру после суффикс-токена (`\d.*$`) + регресс-тест на «Строителей/Офицеров/Корпусная/Помолова/Стрелочников». Verdict после фикса: ✅. ## Tests ``` tests/scripts/test_geocode_deals_from_houses.py — 26 passed ``` street-key (3-variant collapse, numeric streets «8 Марта», region/house-number strip, ё→е, **apt-suffix regression**), centroid AVG, matched UPDATE, no_street_match, --dry-run no-op, SAVEPOINT isolation, geom-not-manual. Ruff clean. (6 pre-existing unrelated fails: WeasyPrint/auth/cache/quota — не затронуты.) ## Deploy plan Миграций нет (использует существующие `deals.lat/lon` + `deals_set_geom_trg` + `geocode_tried_at`). После merge: 1. `--dry-run` на проде → замерить projected coverage % + глянуть топ-10 unmatched улиц (убедиться что `'ул.'`/`'пр.'` НЕ среди matched-ключей) 2. Реальный прогон → `deals WHERE lat IS NULL` должно упасть существенно 3. По остатку решить нужен ли Step 3 (Nominatim для непокрытых улиц) ## Follow-ups (план #569) - Step 3: Nominatim batch для остатка (если coverage < 95%) - Step 4: incremental geocode после ДКП-импорта (≤24h freshness) Closes часть #569 (Step 2 of 4).
Light1YT added 1 commit 2026-05-28 09:25:59 +00:00
deals (49,791 ДКП-сделок Росреестра) на 100% NULL geom → estimator
_fetch_deals (ST_DWithin) никогда не матчит, actual_deals всегда пустой
(корень #571 «deals disabled»). Добавляет scripts/geocode_deals_from_houses.py:
строит per-street центроиды из ~8.6k геокодированных houses и проставляет
lat/lon каждой сделке по нормализованному ключу улицы (geom авто-заполняется
триггером deals_set_geom_trg). Zero внешних geocoder-вызовов; --dry-run
проецирует coverage и топ-10 unmatched улиц.

Pre-push review поймал data-corruption баг: _APT_SUFFIX_RE без правого якоря
жрал реальные улицы на кв/корп/оф/пом/стр («Строителей»→'ул.' garbage key →
неверный центроид). Fix: требовать цифру после суффикс-токена + регресс-тест.

Tests: 26 (street-key нормализация вкл. regression, centroid AVG, matched UPDATE,
no_street_match, --dry-run no-op, SAVEPOINT isolation, geom-not-manual). Ruff clean.
Light1YT merged commit aeaba42ed3 into main 2026-05-28 09:26:01 +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#614
No description provided.