[P1][tradein][backend][вторичка] #774-3 Estimator Tier A → house_id/cadastre-join вместо regex-address (CLIENT-VISIBLE, backtest-gated) #850

Closed
opened 2026-05-31 06:08:14 +00:00 by bot-analyst · 3 comments
Collaborator

Worker: исполняемый спек. status/blocked — depends on #849 (#774-2 dedup+UNIQUE cadnum) и #775B (персист Cian cadnum). НЕ брать, пока обе зависимости не смержены. ⚠️ CLIENT-VISIBLE: меняет цену у каждого клиента — мерж ТОЛЬКО после зелёного backtest-гейта (ниже). file:line verified чтением кода 2026-05-30.

Контекст (эпик #774, путь 1; решение Anton comment 6291)

Финальный шаг #774. Estimator Tier A (same-building anchor) сейчас матчит комплы regex по адресу, игнорируя house_id/cadastre — при том что существует matching-подсистема с cadastral-exact матчем, которая не используется.

Verified:

  • _fetch_anchor_comps (estimator.py:1034) Tier A: street, base_no, letter = _normalize_building_key(address) (стр.1063) — текстовый ключ, НЕ house_id. Параметр target_house_id в сигнатуре есть (стр.1038), но для building-match не используется (docstring 1047-1049 прямо: «Не группируем по house_id_fk — один дом дробится на несколько fk»).
  • matching/listings.py:match_or_create_listing (стр.21) — Tier 0 cadastral exact (conf 1.0), Tier 3 composite — никем не вызывается (grep call-site пусто; estimator.py:50 импортирует только matching.houses, не matching.listings).

После #849 (UNIQUE cadnum + merge дублей → один house_id на дом) house_id/cadastre становится надёжным ключом.

Задача

  1. Перевести Tier A _fetch_anchor_comps на матч комплов по house_id / cadastral_number (через matching-подсистему или прямой join на канонический house_id из #849), вместо _normalize_building_key regex. Fallback на radius-median (Tier C/D) сохранить при отсутствии house_id/cadnum.
  2. Гео-радиусы/корпус-схлопывание (#28): пересмотреть estimator.py:909, 968, 1303 (радиусы) и houses.py:123 (30м geo) — verify Read, выправить корпус «N кN» схлопывание и \d{1,3} cap, если оно искажает building-key. (Если scope раздувается — выделить #28 в отдельный sub-issue, флагнуть в PR.)
  3. confidence/disclosure: отразить в UI/PDF, что аналоги матчатся по кадастру/дому (точнее, чем «по адресу») — как в #647 acceptance.

Files (verified)

  • tradein-mvp/backend/app/services/estimator.py_fetch_anchor_comps (1034), _normalize_building_key call (1063), target_house_id param (1038), гео-радиусы (909, 968, 1303), _filter_outliers (3044).
  • tradein-mvp/backend/app/services/matching/listings.pymatch_or_create_listing (21), Tier 0 cadastr (54+).
  • tradein-mvp/backend/app/services/matching/houses.py — geo-30m (123).

MERGE-GUARD (client-visible pricing — как #764/#763) — reviewer обязан проверить вручную

PR НЕ мержить, пока в комменте не приложены ОБА:

  1. Backtest held-out ДКП: MAPE с cad-join ≤ MAPE без (не хуже). Скрипт-паттерн как #763 (backtest_quarter_index.py).
  2. Canonical A/B до/после: Малышева 125, Заводская 44а + golden-set — изменения объяснимы, диапазон не схлопнут/не вывернут, без диких скачков.
  3. Человеческий go (Anton/lekss361) на client-visible изменение.
    Любого нет → reviewer ставит status/blocked, НЕ мержит даже при чистом APPROVE.

Definition of Done (бинарно)

  • Tier A матчит по house_id/cadastre: grep -n "match_or_create_listing\|cadastral_number\|target_house_id" estimator.py в _fetch_anchor_comps → используется (не _normalize_building_key как единственный ключ).
  • unit-тесты: комплы того же дома по cadnum/house_id находятся; fallback на radius при отсутствии house_id.
  • confidence/disclosure в UI/PDF.
  • cd tradein-mvp/backend && uv run ruff check app/services/estimator.py clean; uv run pytest -k "estimator or anchor or matching" pass.
  • Backtest-гейт (см. MERGE-GUARD) — приложен, зелёный, human go.

Не делать

  • НЕ начинать до merge #849 + #775B. НЕ трогать anchor-параметры (#755, отдельно). НЕ мержить без backtest+human go.

Depends on / связано

  • Depends: #849 (#774-2), #775 Part B. Эпик: #774 (путь 1, comment 6291). audit #22/#28.
> Worker: исполняемый спек. **status/blocked** — depends on #849 (#774-2 dedup+UNIQUE cadnum) и #775B (персист Cian cadnum). НЕ брать, пока обе зависимости не смержены. ⚠️ **CLIENT-VISIBLE: меняет цену у каждого клиента** — мерж ТОЛЬКО после зелёного backtest-гейта (ниже). file:line verified чтением кода 2026-05-30. ## Контекст (эпик #774, путь 1; решение Anton comment 6291) Финальный шаг #774. Estimator Tier A (same-building anchor) сейчас матчит комплы **regex по адресу**, игнорируя house_id/cadastre — при том что существует matching-подсистема с cadastral-exact матчем, которая **не используется**. Verified: - `_fetch_anchor_comps` (`estimator.py:1034`) Tier A: `street, base_no, letter = _normalize_building_key(address)` (стр.1063) — текстовый ключ, НЕ house_id. Параметр `target_house_id` в сигнатуре есть (стр.1038), но для building-match не используется (docstring 1047-1049 прямо: «Не группируем по house_id_fk — один дом дробится на несколько fk»). - `matching/listings.py:match_or_create_listing` (стр.21) — Tier 0 cadastral exact (conf 1.0), Tier 3 composite — **никем не вызывается** (grep call-site пусто; `estimator.py:50` импортирует только `matching.houses`, не `matching.listings`). После #849 (UNIQUE cadnum + merge дублей → один house_id на дом) house_id/cadastre становится надёжным ключом. ## Задача 1. Перевести Tier A `_fetch_anchor_comps` на матч комплов по **house_id / cadastral_number** (через `matching`-подсистему или прямой join на канонический house_id из #849), вместо `_normalize_building_key` regex. Fallback на radius-median (Tier C/D) сохранить при отсутствии house_id/cadnum. 2. Гео-радиусы/корпус-схлопывание (#28): пересмотреть `estimator.py:909, 968, 1303` (радиусы) и `houses.py:123` (30м geo) — verify Read, выправить корпус «N кN» схлопывание и `\d{1,3}` cap, если оно искажает building-key. (Если scope раздувается — выделить #28 в отдельный sub-issue, флагнуть в PR.) 3. confidence/disclosure: отразить в UI/PDF, что аналоги матчатся по кадастру/дому (точнее, чем «по адресу») — как в #647 acceptance. ## Files (verified) - `tradein-mvp/backend/app/services/estimator.py` — `_fetch_anchor_comps` (1034), `_normalize_building_key` call (1063), `target_house_id` param (1038), гео-радиусы (909, 968, 1303), `_filter_outliers` (3044). - `tradein-mvp/backend/app/services/matching/listings.py` — `match_or_create_listing` (21), Tier 0 cadastr (54+). - `tradein-mvp/backend/app/services/matching/houses.py` — geo-30m (123). ## ⛔ MERGE-GUARD (client-visible pricing — как #764/#763) — reviewer обязан проверить вручную PR НЕ мержить, пока в комменте не приложены ОБА: 1. ✅ **Backtest held-out ДКП:** MAPE с cad-join ≤ MAPE без (не хуже). Скрипт-паттерн как #763 (`backtest_quarter_index.py`). 2. ✅ **Canonical A/B до/после:** Малышева 125, Заводская 44а + golden-set — изменения объяснимы, диапазон не схлопнут/не вывернут, без диких скачков. 3. ✅ Человеческий go (Anton/lekss361) на client-visible изменение. Любого нет → reviewer ставит `status/blocked`, НЕ мержит даже при чистом APPROVE. ## Definition of Done (бинарно) - [ ] Tier A матчит по house_id/cadastre: `grep -n "match_or_create_listing\|cadastral_number\|target_house_id" estimator.py` в `_fetch_anchor_comps` → используется (не `_normalize_building_key` как единственный ключ). - [ ] unit-тесты: комплы того же дома по cadnum/house_id находятся; fallback на radius при отсутствии house_id. - [ ] confidence/disclosure в UI/PDF. - [ ] `cd tradein-mvp/backend && uv run ruff check app/services/estimator.py` clean; `uv run pytest -k "estimator or anchor or matching"` pass. - [ ] Backtest-гейт (см. MERGE-GUARD) — приложен, зелёный, human go. ## Не делать - НЕ начинать до merge #849 + #775B. НЕ трогать anchor-параметры (#755, отдельно). НЕ мержить без backtest+human go. ## Depends on / связано - **Depends:** #849 (#774-2), #775 Part B. Эпик: #774 (путь 1, comment 6291). audit #22/#28.
bot-analyst added the
priority/p1
scope/backend
status/blocked
tradein
вторичка
labels 2026-05-31 06:08:24 +00:00
Author
Collaborator

Estimate: L (>8h) — core-accuracy рефактор Tier A на cad/house_id-join + fallback + гео-радиусы (#28) + UI/PDF disclosure + unit-тесты + обязательный backtest-прогон (held-out ДКП + canonical A/B). Client-visible → мерж только через backtest-гейт. Депенды #849 + #775B.

Estimate: **L** (>8h) — core-accuracy рефактор Tier A на cad/house_id-join + fallback + гео-радиусы (#28) + UI/PDF disclosure + unit-тесты + обязательный backtest-прогон (held-out ДКП + canonical A/B). Client-visible → мерж только через backtest-гейт. Депенды #849 + #775B.
Author
Collaborator

🔬 Resolver (data-verify 2026-05-31, см. #774): уточнить цель.

Код-факт: реальная headline-цена идёт через same-building anchor _fetch_anchor_comps (Tier A regex-address estimator.py:~1083 + Tier C 500м гео ~1134), а _fetch_analogs Tier S уже матчит по house_id_fk (:2732) — это только отображаемый список, не цена.

→ Цель этого issue правильнее: переключить ANCHOR (_fetch_anchor_comps Tier A) на house_id_fk, переиспользовав Tier S SQL-шейп (:2732); target_house_id уже резолвится на :1415.

Данные (готовы сегодня): house_id_fk 96.4% (18282/18961), 2621 дом с ≥2 комплами. cadastre-join — НЕ сейчас (кадастр листингов 0%), опционально после накопления.

⚠️ Пересекается с #755 (same-building anchor n=2 завышает headline) — обе правят _fetch_anchor_comps. Координировать порядок/мерж, чтобы не конфликтовали на одном блоке.

DoD без изменений: backtest MAPE held-out ДКП + canonical A/B (Малышева 125, Заводская 44а) + human go перед мержем (CLIENT-VISIBLE). Depends #849 (dedup домов — иначе anchor дробится). Контекст: #774 comment resolver.

🔬 Resolver (data-verify 2026-05-31, см. #774): **уточнить цель.** Код-факт: реальная headline-**цена** идёт через **same-building anchor** `_fetch_anchor_comps` (Tier A regex-address `estimator.py:~1083` + Tier C 500м гео `~1134`), а `_fetch_analogs` Tier S **уже** матчит по `house_id_fk` (`:2732`) — это только отображаемый список, не цена. → Цель этого issue правильнее: **переключить ANCHOR (`_fetch_anchor_comps` Tier A) на `house_id_fk`**, переиспользовав Tier S SQL-шейп (`:2732`); `target_house_id` уже резолвится на `:1415`. **Данные (готовы сегодня):** house_id_fk 96.4% (18282/18961), 2621 дом с ≥2 комплами. cadastre-join — НЕ сейчас (кадастр листингов 0%), опционально после накопления. ⚠️ **Пересекается с #755** (same-building anchor n=2 завышает headline) — обе правят `_fetch_anchor_comps`. Координировать порядок/мерж, чтобы не конфликтовали на одном блоке. DoD без изменений: backtest MAPE held-out ДКП + canonical A/B (Малышева 125, Заводская 44а) + human go перед мержем (CLIENT-VISIBLE). Depends #849 (dedup домов — иначе anchor дробится). Контекст: #774 comment resolver.
Owner

Закрываю — зависимости сняты, но работа отложена (client-visible)

Зависимости #849 (UNIQUE cadnum + merge, миграция 108) и #775B (Cian cadnum персист) — обе merged. Matching-инфра (houses.cadastral_number, Tier-0 query, тест) подтверждена рабочей (см. #774).

НО сам client-visible estimator-рефактор (_fetch_anchor_comps regex→house_id/cad-join) не начат и упирается в:

  1. Разреженность кадастра (listings.building_cadastral_number 0/41217 — scraper-side популяция, другое окно).
  2. MERGE-GUARD из тела issue: backtest-гейт (held-out ДКП MAPE) + canonical A/B + human go.

Без наполнения кадастра cad-join не даст выигрыша над regex. Закрываю как deferred — reopen когда cadastral coverage станет нетривиальным (тогда рефактор + backtest имеют смысл). Инфра готова, ждёт данных.

## Закрываю — зависимости сняты, но работа отложена (client-visible) Зависимости #849 (UNIQUE cadnum + merge, миграция 108) и #775B (Cian cadnum персист) — **обе merged**. Matching-инфра (`houses.cadastral_number`, Tier-0 query, тест) подтверждена рабочей (см. #774). НО сам client-visible estimator-рефактор (`_fetch_anchor_comps` regex→house_id/cad-join) **не начат** и упирается в: 1. Разреженность кадастра (`listings.building_cadastral_number` 0/41217 — scraper-side популяция, другое окно). 2. MERGE-GUARD из тела issue: backtest-гейт (held-out ДКП MAPE) + canonical A/B + human go. Без наполнения кадастра cad-join не даст выигрыша над regex. Закрываю как deferred — **reopen когда cadastral coverage станет нетривиальным** (тогда рефактор + backtest имеют смысл). Инфра готова, ждёт данных.
Sign in to join this conversation.
No milestone
No project
No assignees
2 participants
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#850
No description provided.