Pre-push review (#2007):
- ceiling_height_m: clamp to plausible 2.0-6.0 m. yandex SERP emits garbage (seen 18 m)
-> out-of-range write to ceiling_height numeric(3,2) raised DataError failing the whole
batch (per-lot SAVEPOINT only catches IntegrityError). Raw value still in raw_payload.
- migration 140: also NULL out legacy 'other' (299 rows) for parity with
normalize_house_type (unknown->None; NULL neutral in estimator vs 'other' false penalty).
house_type-нормализация = реальный фикс estimator soft-penalty: yandex SERP
писал building.buildingType как есть (SCREAMING — MONOLIT/BRICK/PANEL/...),
а estimator штрафует аналог с house_type != target. 'MONOLIT' никогда не равен
каноничному 'monolith' → ~70% yandex-аналогов получали ложный штраф и фактически
выпадали из скоринга. Новый модуль house_type_normalizer.normalize_house_type
приводит yandex SCREAMING и cian camelCase (reuse в #2008) к канону
{panel, brick, monolith, monolith_brick, block, wood}; unknown/'other'/'' → None
(NULL нейтрально для estimator, не штрафуется). Backfill существующих yandex-строк
миграцией 140 (идемпотентный UPDATE под тот же маппинг).
kitchen_area_m2 / ceiling_height_m promote из raw_payload в колонки — для
coverage-дашборда и matching, НЕ valuation-фича (estimator их пока не читает —
отдельный follow-up). ceiling пишется в ОБЕ колонки (ceiling_height 019 — её
читает coverage-дашборд admin.py + yandex_detail/cian_detail; ceiling_height_m
111 — живая avito-колонка) через COALESCE в ON CONFLICT, чтобы SERP re-scrape
без полей не затирал detail-enrichment (avito поведение сохранено).