[P3] Эстиматор: сегментная калибровка по классу жилья — за фичефлагом (follow-up #2209) #2255

Closed
opened 2026-07-03 06:04:47 +00:00 by lekss361 · 2 comments
Owner

Follow-up из ремедиации #2209 (эпик #2200). Floor ±12% (#2230) и UI-раскрытие покрытия (#2232) сделаны; остаётся систематический сегментный bias.

Данные (live-бэктест, длинное окно, n=561, 2026-07-03 — канон)

Сегмент Bias n
эконом +3.2%
комфорт −4.5%
бизнес −16.3% 76
элит −25.7% 16

Общий live-бэктест на тот же момент: bias −2.29%, MAPE 14.78%, range-coverage 81.5% (n=276, engine=full).

Проблема

Эстиматор систематически занижает верхние сегменты: чем дороже класс, тем сильнее недооценка (бизнес −16%, элит −26%). Для «продать быстро» это терпимо, но для trade-in-оффера по бизнес/элит-объекту — существенная ошибка не в пользу клиента.

Смежное: разбор «только-вверх» лифтов — IMV blend поднимает оценку только вверх (asymmetric lift), что маскирует часть занижения на части выборки и делает сегментный bias неоднородным. Разобрать взаимодействие сегментной поправки с IMV-blend до включения.

Acceptance

  • Сегментная поправка (мультипликатор по классу жилья) — строго за фичефлагом (env/config), по умолчанию OFF.
  • Backtest-гейт обязателен: scripts/backtest_estimator.py (локально через туннель :35432) до/после на длинном окне; включение только если MAPE/coverage не деградируют, сегментные bias сжимаются.
  • Осторожность с малыми n: элит n=16, бизнес n=76 — поправку для элит либо capped, либо не вводить до накопления выборки.
  • Разбор asymmetric IMV-lift («только вверх») — зафиксировать вывод в issue/vault: оставить как есть или симметризовать.
  • Frozen baseline не трогать.

Refs #2209, #2200

Follow-up из ремедиации #2209 (эпик #2200). Floor ±12% (#2230) и UI-раскрытие покрытия (#2232) сделаны; остаётся систематический сегментный bias. ## Данные (live-бэктест, длинное окно, n=561, 2026-07-03 — канон) | Сегмент | Bias | n | |---|---|---| | эконом | +3.2% | — | | комфорт | −4.5% | — | | бизнес | −16.3% | 76 | | элит | −25.7% | 16 | Общий live-бэктест на тот же момент: bias −2.29%, MAPE 14.78%, range-coverage 81.5% (n=276, engine=full). ## Проблема Эстиматор систематически занижает верхние сегменты: чем дороже класс, тем сильнее недооценка (бизнес −16%, элит −26%). Для «продать быстро» это терпимо, но для trade-in-оффера по бизнес/элит-объекту — существенная ошибка не в пользу клиента. Смежное: разбор «только-вверх» лифтов — IMV blend поднимает оценку только вверх (asymmetric lift), что маскирует часть занижения на части выборки и делает сегментный bias неоднородным. Разобрать взаимодействие сегментной поправки с IMV-blend до включения. ## Acceptance - [ ] Сегментная поправка (мультипликатор по классу жилья) — строго за фичефлагом (env/config), по умолчанию OFF. - [ ] Backtest-гейт обязателен: `scripts/backtest_estimator.py` (локально через туннель :35432) до/после на длинном окне; включение только если MAPE/coverage не деградируют, сегментные bias сжимаются. - [ ] Осторожность с малыми n: элит n=16, бизнес n=76 — поправку для элит либо capped, либо не вводить до накопления выборки. - [ ] Разбор asymmetric IMV-lift («только вверх») — зафиксировать вывод в issue/vault: оставить как есть или симметризовать. - [ ] Frozen baseline не трогать. Refs #2209, #2200
Collaborator

Разбор asymmetric IMV-lift выполнен (2026-07-03, read-only прод) — acceptance-пункт закрыт

Вердикт по IMV: оставить asymmetric как есть, НЕ симметризовать. На живом трафике lift МЁРТВ: 0 срабатываний за 773 прод-оценки (грep логов 720ч: imv_blend: — 0; фраза «скорректирована по оценке Avito IMV» — 0/773; 21 упоминание avito_imv в sources_used — display-only от house-anchor, не lift). Gate anchor_tier is None отсекает same-building/micro-пути, на радиусных порог median×1.15 не пробивается. Гипотеза «lift маскирует занижение верхних сегментов» на данных НЕ воспроизводится — сегментный bias живёт в основном спайне (радиусная медиана → ratio → hedonic → PI). Асимметрия — осознанный дизайн (#651/#652, комментарий в коде), симметризация была бы регрессом (тянула бы честные оценки вниз по чужому сигналу).

Критичная находка по «классу жилья»: building_class = curated-таблица на 88 домов (спам-фильтр премиума, читается ПОСЛЕ прайсинга как метаданные) и отсутствует в frozen backtest-фикстуре → class-driven поправка была бы no-op в regression-gate (неизмерима). Сегменты issue (эконом/комфорт/бизнес/элит) — это price-band по ₽/м² (PRICE_SEGMENTS_PPM2 бэктеста). Решение: мультипликатор по ценовому бэнду точки (median_ppm2), не по классу.

Дизайн (shrinkage √n к 1.0 + cap): эконом ×1.00 (переоценку +3.2% не трогаем), комфорт ×1.03, бизнес ×1.12 (полу-shrinkage от raw 1.195, n=76), элит ×1.10 capped (n=16 — полный 1.346 гадание, ноль оставляет −25.7%; забираем половину bias с контролем риска), премиум — без множителя. Значения в config-dict с датой замера/n; пересчёт — режимом --calibrate-segments в backtest.

Точка вживления: _price_from_inputs, ПОСЛЕ IMV-blend/corridor-clamp/floor'ов, СРАЗУ ПЕРЕД _apply_range_floor; множитель к median_price/median_ppm2/range пропорционально (паттерн _apply_quarter_index). Флаг estimate_segment_multiplier_enabled=FalseOFF на мерж, включение отдельным решением после backtest-прогона с туннелем (:35432, --resolve-house-id). Frozen baseline при OFF не двигается.

Отдельно (database-expert, не этот PR): миграция логирующих колонок в trade_in_estimates (imv_blend_applied, segment_multiplier_applied, building_class, pre_correction_median) — чтобы сегмент×IMV разрез стал измеримым на живых оценках.

PR пойдёт после #2269 (оба трогают estimator.py — последовательно).

## Разбор asymmetric IMV-lift выполнен (2026-07-03, read-only прод) — acceptance-пункт закрыт **Вердикт по IMV: оставить asymmetric как есть, НЕ симметризовать.** На живом трафике lift МЁРТВ: 0 срабатываний за 773 прод-оценки (грep логов 720ч: `imv_blend:` — 0; фраза «скорректирована по оценке Avito IMV» — 0/773; 21 упоминание avito_imv в sources_used — display-only от house-anchor, не lift). Gate `anchor_tier is None` отсекает same-building/micro-пути, на радиусных порог `median×1.15` не пробивается. Гипотеза «lift маскирует занижение верхних сегментов» на данных НЕ воспроизводится — сегментный bias живёт в основном спайне (радиусная медиана → ratio → hedonic → PI). Асимметрия — осознанный дизайн (#651/#652, комментарий в коде), симметризация была бы регрессом (тянула бы честные оценки вниз по чужому сигналу). **Критичная находка по «классу жилья»:** `building_class` = curated-таблица на **88 домов** (спам-фильтр премиума, читается ПОСЛЕ прайсинга как метаданные) и **отсутствует в frozen backtest-фикстуре** → class-driven поправка была бы no-op в regression-gate (неизмерима). Сегменты issue (эконом/комфорт/бизнес/элит) — это price-band по ₽/м² (`PRICE_SEGMENTS_PPM2` бэктеста). **Решение: мультипликатор по ценовому бэнду точки (median_ppm2), не по классу.** **Дизайн (shrinkage √n к 1.0 + cap):** эконом ×1.00 (переоценку +3.2% не трогаем), комфорт ×1.03, бизнес ×1.12 (полу-shrinkage от raw 1.195, n=76), **элит ×1.10 capped** (n=16 — полный 1.346 гадание, ноль оставляет −25.7%; забираем половину bias с контролем риска), премиум — без множителя. Значения в config-dict с датой замера/n; пересчёт — режимом `--calibrate-segments` в backtest. **Точка вживления:** `_price_from_inputs`, ПОСЛЕ IMV-blend/corridor-clamp/floor'ов, СРАЗУ ПЕРЕД `_apply_range_floor`; множитель к median_price/median_ppm2/range пропорционально (паттерн `_apply_quarter_index`). Флаг `estimate_segment_multiplier_enabled=False` — **OFF на мерж**, включение отдельным решением после backtest-прогона с туннелем (:35432, `--resolve-house-id`). Frozen baseline при OFF не двигается. **Отдельно (database-expert, не этот PR):** миграция логирующих колонок в trade_in_estimates (`imv_blend_applied`, `segment_multiplier_applied`, `building_class`, `pre_correction_median`) — чтобы сегмент×IMV разрез стал измеримым на живых оценках. PR пойдёт после #2269 (оба трогают estimator.py — последовательно).
Collaborator

Готово (PR #2293 смержен, флаг OFF) — все acceptance-пункты закрыты

  • Сегментная поправка за фичефлагом (estimate_segment_multiplier_enabled, default OFF, путь байт-в-байт, gate зелёный без регенерации).
  • Backtest-гейт: три конфигурации на одном детерминированном sample 300; V2 (бизнес ×1.08 / элит ×1.06) выбран как лучший трейд-офф.
  • Малые n: элит capped (по факту ещё и усажен до 1.06), премиум без множителя; эконом/комфорт ×1.00 намеренно (утечка band-by-predicted).
  • Разбор asymmetric IMV-lift: lift в проде мёртв (0/773) — оставлен как есть, bias живёт в основном спайне. Зафиксировано выше.
  • Frozen baseline не тронут.

Решение о включении — за Anton. Трейд-офф при ON (замер OFF/ON, sample 300):

OFF ON (V2)
бизнес bias (n=46) −10.35% −3.18%
элит bias (n=6) −30.2% −24.6%
эконом +7.7% / MAPE 17.9% intact (+9.4% / 18.0%)
overall MAPE 13.55% 15.42% (+1.87 п.п.)
coverage 83.1% 82.0%

Т.е. +1.87 п.п. общего MAPE в обмен на существенно более честные офферы бизнес/элит (сейчас занижены на 10-30% не в пользу клиента). Включение — один env-флаг, обратимо. Порог acceptance «MAPE не деградирует» строго не достигается ни одной конфигурацией — это фундаментальное свойство band-by-predicted (истинный сегмент при оценке неизвестен); дальнейшее сжатие bias без роста MAPE — только через confidence-гейт (follow-up issue #2294 будет заведён).

## Готово (PR #2293 смержен, флаг OFF) — все acceptance-пункты закрыты - ✅ Сегментная поправка за фичефлагом (`estimate_segment_multiplier_enabled`, default OFF, путь байт-в-байт, gate зелёный без регенерации). - ✅ Backtest-гейт: три конфигурации на одном детерминированном sample 300; V2 (бизнес ×1.08 / элит ×1.06) выбран как лучший трейд-офф. - ✅ Малые n: элит capped (по факту ещё и усажен до 1.06), премиум без множителя; эконом/комфорт ×1.00 намеренно (утечка band-by-predicted). - ✅ Разбор asymmetric IMV-lift: **lift в проде мёртв (0/773)** — оставлен как есть, bias живёт в основном спайне. Зафиксировано выше. - ✅ Frozen baseline не тронут. **Решение о включении — за Anton.** Трейд-офф при ON (замер OFF/ON, sample 300): | | OFF | ON (V2) | |---|---|---| | бизнес bias (n=46) | −10.35% | **−3.18%** | | элит bias (n=6) | −30.2% | **−24.6%** | | эконом | +7.7% / MAPE 17.9% | intact (+9.4% / 18.0%) | | overall MAPE | 13.55% | **15.42% (+1.87 п.п.)** | | coverage | 83.1% | 82.0% | Т.е. +1.87 п.п. общего MAPE в обмен на существенно более честные офферы бизнес/элит (сейчас занижены на 10-30% не в пользу клиента). Включение — один env-флаг, обратимо. Порог acceptance «MAPE не деградирует» строго не достигается ни одной конфигурацией — это фундаментальное свойство band-by-predicted (истинный сегмент при оценке неизвестен); дальнейшее сжатие bias без роста MAPE — только через confidence-гейт (follow-up issue #2294 будет заведён).
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#2255
No description provided.