[tradein][yandex] SERP _entity_to_lot: писать ceiling_height + kitchen_area_m2 в колонки + нормализовать house_type #2007

Closed
opened 2026-06-27 16:56:38 +00:00 by lekss361 · 4 comments
Owner

Из аудита покрытия полей скрапперов #2011 (код + живой замер прода).

⚠️ Два РАЗНЫХ класса фиксов в одном файле — не путать

Estimator при отборе/скоринге аналогов читает house_type (soft-penalty), но НЕ читает kitchen_area_m2 / ceiling_height. Поэтому:

  • house_type (п.3) = реальный фикс точности оценки — нормализация чинит estimator soft-penalty.
  • kitchen/ceiling (п.1-2) = промоут для дашборда покрытия + matching (conflict_resolution) + будущих фич, НЕ valuation-фича сейчас. Подключение их к estimator — отдельный follow-up с backtest: #2012.

Прод-замер (active yandex, 2026-06-27)

метрика в raw_payload в колонке
kitchen_area_m2 88% 1%
ceiling_height 88% 1%
house_type заполнен 70% (но 0% в каноне)

house_type у yandex заполнен у 70% строк, НО сырым upstream-энумом (MONOLIT/BRICK/PANEL/MONOLIT_BRICK/BLOCK/WOOD) вместо канона panel/brick/monolith/monolith_brick/block/wood (= ключи estimator._IMV_HOUSE_TYPE_MAP, estimator.py:146). Estimator soft-penalty (house_type <> :target_house_type, estimator.py:3905-3925/4026-4047) никогда не матчит target → ~70% yandex-аналогов получают ложный штраф. Прод подтверждает: 2765 MONOLIT, 1044 BRICK, 495 PANEL, 461 MONOLIT_BRICK — ни одно не в каноне.

Корни (file:line)

  1. ceiling_heightyandex_realty.py:325: entity.ceilingHeight → только raw_payload['ceiling_height']. (промоут → колонка ceiling_height_m; дашборд/матчинг)
  2. kitchen_area_m2yandex_realty.py:308-309: kitchenSpace.value → только raw_payload['kitchen_area_m2']. (промоут → колонка; дашборд/матчинг)
  3. house_type НЕ нормализованyandex_realty.py:330: building.buildingType пишется сырым в listings.house_type. (РЕАЛЬНЫЙ фикс estimator)

DoD

  • В _entity_to_lot мапить ceilingHeightScrapedLot.ceiling_height_m, kitchenSpace.valueScrapedLot.kitchen_area_m2. NB: этих полей в ScrapedLot (base.py) сейчас НЕТ — добавить + провести через save_listings INSERT. Колонка-таргет для ceiling = ceiling_height_m (живая, в неё пишет avito_detail); ceiling_height (миграция 019) мёртвая ~0% — сверить, какую читает дашборд.
  • Прогнать buildingType через канон-нормализатор house_type (новый shared house_type_normalizer, канон сверен с _IMV_HOUSE_TYPE_MAP). Неизвестное/other → NULL (не «other»: в soft-penalty NULL нейтрально, а не-канон значение всегда даёт ложный штраф).
  • Backfill-миграция существующих yandex-строк под канон (без неё фикс действует только на новые скрейпы — старые 4848 строк остаются вне скоринга).
  • Замер до/после: kitchen_area_m2/ceiling_height fill-rate active yandex ~1% → ~85%+; house_type — доля строк в каноне (0% → ~70%).

Схема: только backfill-миграция (колонки kitchen_area_m2/ceiling_height_m уже есть). scope/backend, scrapers, tradein. house_type — priority/p1; kitchen/ceiling promote — по сути p2 (не влияет на оценку до #2012).

Из аудита покрытия полей скрапперов #2011 (код + живой замер прода). ## ⚠️ Два РАЗНЫХ класса фиксов в одном файле — не путать Estimator при отборе/скоринге аналогов читает `house_type` (soft-penalty), но **НЕ читает** `kitchen_area_m2` / `ceiling_height`. Поэтому: - **house_type (п.3) = реальный фикс точности оценки** — нормализация чинит estimator soft-penalty. - **kitchen/ceiling (п.1-2) = промоут для дашборда покрытия + matching (conflict_resolution) + будущих фич**, НЕ valuation-фича сейчас. Подключение их к estimator — отдельный follow-up с backtest: #2012. ## Прод-замер (active yandex, 2026-06-27) | метрика | в `raw_payload` | в колонке | |---|:--:|:--:| | `kitchen_area_m2` | 88% | 1% | | `ceiling_height` | 88% | 1% | | `house_type` заполнен | — | 70% (но **0% в каноне**) | house_type у yandex заполнен у 70% строк, НО **сырым upstream-энумом** (`MONOLIT`/`BRICK`/`PANEL`/`MONOLIT_BRICK`/`BLOCK`/`WOOD`) вместо канона `panel/brick/monolith/monolith_brick/block/wood` (= ключи `estimator._IMV_HOUSE_TYPE_MAP`, estimator.py:146). Estimator soft-penalty (`house_type <> :target_house_type`, estimator.py:3905-3925/4026-4047) **никогда не матчит** target → ~70% yandex-аналогов получают ложный штраф. Прод подтверждает: 2765 `MONOLIT`, 1044 `BRICK`, 495 `PANEL`, 461 `MONOLIT_BRICK` — ни одно не в каноне. ## Корни (file:line) 1. **ceiling_height** — `yandex_realty.py:325`: `entity.ceilingHeight` → только `raw_payload['ceiling_height']`. *(промоут → колонка `ceiling_height_m`; дашборд/матчинг)* 2. **kitchen_area_m2** — `yandex_realty.py:308-309`: `kitchenSpace.value` → только `raw_payload['kitchen_area_m2']`. *(промоут → колонка; дашборд/матчинг)* 3. **house_type НЕ нормализован** — `yandex_realty.py:330`: `building.buildingType` пишется сырым в `listings.house_type`. *(РЕАЛЬНЫЙ фикс estimator)* ## DoD - В `_entity_to_lot` мапить `ceilingHeight` → `ScrapedLot.ceiling_height_m`, `kitchenSpace.value` → `ScrapedLot.kitchen_area_m2`. **NB:** этих полей в `ScrapedLot` (base.py) сейчас НЕТ — добавить + провести через `save_listings` INSERT. Колонка-таргет для ceiling = `ceiling_height_m` (живая, в неё пишет avito_detail); `ceiling_height` (миграция 019) мёртвая ~0% — сверить, какую читает дашборд. - Прогнать `buildingType` через канон-нормализатор `house_type` (новый shared `house_type_normalizer`, канон сверен с `_IMV_HOUSE_TYPE_MAP`). Неизвестное/`other` → NULL (не «other»: в soft-penalty NULL нейтрально, а не-канон значение всегда даёт ложный штраф). - **Backfill-миграция** существующих yandex-строк под канон (без неё фикс действует только на новые скрейпы — старые 4848 строк остаются вне скоринга). - Замер до/после: `kitchen_area_m2`/`ceiling_height` fill-rate active yandex ~1% → ~85%+; house_type — доля строк в каноне (0% → ~70%). Схема: только backfill-миграция (колонки `kitchen_area_m2`/`ceiling_height_m` уже есть). scope/backend, scrapers, tradein. **house_type — priority/p1; kitchen/ceiling promote — по сути p2** (не влияет на оценку до #2012).
lekss361 added the
priority/p1
scope/backend
scrapers
status/ready
tradein
labels 2026-06-27 16:58:04 +00:00
Author
Owner

Working on this in PR #2016. code-reviewer (2 minor исправлены: ceiling cap 2.0–6.0 м против overflow numeric(3,2); backfill 'other'(299)→NULL для паритета с нормализатором). Включает backfill-миграцию 140 (SCREAMING→канон существующих строк).

Working on this in PR #2016. code-reviewer ✅ (2 minor исправлены: ceiling cap 2.0–6.0 м против overflow `numeric(3,2)`; backfill `'other'`(299)→NULL для паритета с нормализатором). Включает backfill-миграцию 140 (SCREAMING→канон существующих строк).
Author
Owner

PR #2016 merged + deploy success; миграция 140 применена (есть в _schema_migrations).

A1 house_type (РЕАЛЬНЫЙ фикс оценки) — замер ДО/ПОСЛЕ (прод, source='yandex' active)

house_type ДО ПОСЛЕ (миграция 140)
MONOLIT → monolith 2765 (SCREAMING) 2765 monolith
BRICK → brick 1044 + 26 lc 1070 brick
PANEL → panel 495 + 26 lc 521 panel
MONOLIT_BRICK → monolith_brick 461 (SCREAMING) 461 monolith_brick
BLOCK → block 81 81 block
WOOD → wood 2 2 wood
other 299 → NULL (нейтрально для estimator) ✓

Итог: 0 SCREAMING-значений, 0 other — 100% непустых yandex house_type теперь в каноне. ~4900 yandex-аналогов, которые в estimator soft-penalty (house_type <> :target) раньше ВСЕГДА мимо → ложный штраф, теперь матчатся корректно. Новые скрейпы пишут канон через normalize_house_type на ингесте.

CLASS B promote (kitchen/ceiling) — растёт по новым скрейпам

Код задеплоен; колонки заполняются только при scraped_at > деплой (upsert не перезаписывает старые detail-поля — COALESCE). Baseline сейчас: kitchen 1.0%, ceiling 0.9%. Поднимется к ~85%+ по мере прогона yandex SERP. Wiring покрыт тестами (kitchen_area_m2==12.7, ceiling_height_m==2.7, dual-column ceiling + cap). Дашборд/матчинг, не valuation → estimator-фичи в #2012.

✅ PR #2016 merged + deploy success; миграция `140` применена (есть в `_schema_migrations`). ## A1 house_type (РЕАЛЬНЫЙ фикс оценки) — замер ДО/ПОСЛЕ (прод, `source='yandex'` active) | house_type | ДО | ПОСЛЕ (миграция 140) | |---|--:|--:| | MONOLIT → monolith | 2765 (SCREAMING) | **2765 monolith** ✓ | | BRICK → brick | 1044 + 26 lc | **1070 brick** ✓ | | PANEL → panel | 495 + 26 lc | **521 panel** ✓ | | MONOLIT_BRICK → monolith_brick | 461 (SCREAMING) | **461 monolith_brick** ✓ | | BLOCK → block | 81 | **81 block** ✓ | | WOOD → wood | 2 | **2 wood** ✓ | | other | 299 | **→ NULL** (нейтрально для estimator) ✓ | **Итог: 0 SCREAMING-значений, 0 `other` — 100% непустых yandex `house_type` теперь в каноне.** ~4900 yandex-аналогов, которые в estimator soft-penalty (`house_type <> :target`) раньше ВСЕГДА мимо → ложный штраф, теперь матчатся корректно. Новые скрейпы пишут канон через `normalize_house_type` на ингесте. ## CLASS B promote (kitchen/ceiling) — растёт по новым скрейпам Код задеплоен; колонки заполняются только при `scraped_at > деплой` (upsert не перезаписывает старые detail-поля — COALESCE). Baseline сейчас: kitchen 1.0%, ceiling 0.9%. Поднимется к ~85%+ по мере прогона yandex SERP. Wiring покрыт тестами (`kitchen_area_m2==12.7`, `ceiling_height_m==2.7`, dual-column ceiling + cap). Дашборд/матчинг, не valuation → estimator-фичи в #2012.
Author
Owner

Working on this (автономный проход по backlog) — ветка fix/tradein-yandex-field-promotion. Заодно создаю shared house_type_normalizer, переиспользуемый в #2008 (cian) и #2009 (avito) — оба идут следом.

Working on this (автономный проход по backlog) — ветка `fix/tradein-yandex-field-promotion`. Заодно создаю shared `house_type_normalizer`, переиспользуемый в #2008 (cian) и #2009 (avito) — оба идут следом.
Author
Owner

Перепроверил живьём (независимый recheck-раунд, code-reviewer + ручной grep/git log) — issue уже полностью закрыт PR #2016 (коммиты 440bb2ca/a1ee06ef/024fadac, 2026-06-27), просто не был закрыт в трекере.

Все 4 пункта DoD на месте: ceiling_height_m/kitchen_area_m2 промоучены в колонки (yandex_realty.py:331-333,349-357), house_type идёт через shared house_type_normalizer.normalize_house_type() (канон сверен с estimator._IMV_HOUSE_TYPE_MAP), backfill-миграция 140_yandex_house_type_backfill.sql применена. Живой прод-замер: house_type канон 100% (0 raw-значений). kitchen/ceiling fill-rate растёт по мере re-scrape (34.8%/34.9% overall, 62.9%/63.7% на свежих за 3 дня — механизм COALESCE-upsert, не ретроактивный backfill, задокументировано как намеренное решение).

Закрываю.

Перепроверил живьём (независимый recheck-раунд, code-reviewer + ручной grep/git log) — issue уже полностью закрыт PR #2016 (коммиты `440bb2ca`/`a1ee06ef`/`024fadac`, 2026-06-27), просто не был закрыт в трекере. Все 4 пункта DoD на месте: `ceiling_height_m`/`kitchen_area_m2` промоучены в колонки (`yandex_realty.py:331-333,349-357`), `house_type` идёт через shared `house_type_normalizer.normalize_house_type()` (канон сверен с `estimator._IMV_HOUSE_TYPE_MAP`), backfill-миграция `140_yandex_house_type_backfill.sql` применена. Живой прод-замер: house_type канон 100% (0 raw-значений). kitchen/ceiling fill-rate растёт по мере re-scrape (34.8%/34.9% overall, 62.9%/63.7% на свежих за 3 дня — механизм COALESCE-upsert, не ретроактивный backfill, задокументировано как намеренное решение). Закрываю.
Sign in to join this conversation.
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#2007
No description provided.