🔴 [tradein][вторичка][analytics] Asking→Sold bias — estimator завышает ~22% vs реальные ДКП-продажи #648

Closed
opened 2026-05-29 12:40:40 +00:00 by Light1YT · 4 comments
Owner

Приоритет: СУПЕР (per Anton 2026-05-29). Возможно крупнейший систематический accuracy-баг вторички — обнаружен бэктестом против ground truth.

Находка (измерено 2026-05-29)

Estimator считает базу по медиане ЗАПРОСОВ активных листингов. Но trade-in/выкуп интересует цена ПРОДАЖИ. Бэктест на 300 ДКП-сделках (270 с аналогами в 1км, та же комнатность, сделки с 2025-06):

  • median bias = +22% — медиана запросов систематически на 22% выше реальной цены продажи
  • MAPE = 25%
  • City-wide медианы: ДКП-продажи 114 883 ₽/м² vs активные запросы 156 025 ₽/м² (+36%)

Продавцу/выкупу может показываться завышенная цифра — прямой удар по «pixel perfect точность».

Caveats (честно)

  • Time-mismatch: сравнивались ТЕКУЩИЕ активные листинги vs сделки с 2025-06 (не point-in-time). Корректный бэктест — через listing_source_snapshots (#570, копит историю с 2026-05-30) когда накопит. Но смещение крупное и однонаправленное → реально.
  • Бэктест мерил raw asking-median, а estimator применяет свои коэф (repair/class/IQR/tiers). Нужен бэктест фактического estimator-output (см. backtest-скрипт, отдельный PR).
  • «Правильный» таргет зависит от продукта: «за сколько ПРОДАМ» (sold) vs «за сколько ВЫСТАВИТЬ» (asking). Для выкупа — sold.

Что сделать

  1. Backtest actual estimator output (не raw median) per district/rooms → точный bias.
  2. Вывести asking→sold discount (глобальный и/или per-segment/district) из ДКП — спред asking_median/sold_median.
  3. Применить коррекцию в estimator (после analog-median, до выдачи) ИЛИ показывать обе цифры («ориентир запроса» vs «ожидаемая продажа»). Меняет клиентскую цифру → A/B + согласование.
  4. Связано с #647 (district price-index) — общий механизм (квартальные ДКП-медианы). Возможно решать вместе.

Acceptance

  • Backtest фактического estimator-output (MAPE/bias) — baseline зафиксирован
  • asking→sold фактор выведен из ДКП + валидирован (held-out)
  • Коррекция применена ИЛИ dual-display, без регресса (A/B до/после)
  • Документировано (методология + ограничения)

Refs

  • estimator.py, deals (49 781 ДКП с geom+price), FDW gendesign_rosreestr_deals
  • scripts/backtest_estimator.py (reusable harness — отдельный PR)
  • #647 (district price-index — общий ДКП-механизм), #564 (sales↔listings pairing)
**Приоритет: СУПЕР (per Anton 2026-05-29).** Возможно крупнейший систематический accuracy-баг вторички — обнаружен бэктестом против ground truth. ## Находка (измерено 2026-05-29) Estimator считает базу по медиане ЗАПРОСОВ активных листингов. Но trade-in/выкуп интересует цена ПРОДАЖИ. Бэктест на 300 ДКП-сделках (270 с аналогами в 1км, та же комнатность, сделки с 2025-06): - **median bias = +22%** — медиана запросов систематически на 22% выше реальной цены продажи - **MAPE = 25%** - City-wide медианы: ДКП-продажи **114 883 ₽/м²** vs активные запросы **156 025 ₽/м²** (+36%) Продавцу/выкупу может показываться завышенная цифра — прямой удар по «pixel perfect точность». ## Caveats (честно) - **Time-mismatch:** сравнивались ТЕКУЩИЕ активные листинги vs сделки с 2025-06 (не point-in-time). Корректный бэктест — через `listing_source_snapshots` (#570, копит историю с 2026-05-30) когда накопит. Но смещение крупное и однонаправленное → реально. - Бэктест мерил **raw asking-median**, а estimator применяет свои коэф (repair/class/IQR/tiers). Нужен бэктест фактического estimator-output (см. backtest-скрипт, отдельный PR). - «Правильный» таргет зависит от продукта: «за сколько ПРОДАМ» (sold) vs «за сколько ВЫСТАВИТЬ» (asking). Для выкупа — sold. ## Что сделать 1. **Backtest actual estimator output** (не raw median) per district/rooms → точный bias. 2. Вывести **asking→sold discount** (глобальный и/или per-segment/district) из ДКП — спред asking_median/sold_median. 3. Применить коррекцию в estimator (после analog-median, до выдачи) ИЛИ показывать обе цифры («ориентир запроса» vs «ожидаемая продажа»). **Меняет клиентскую цифру → A/B + согласование.** 4. Связано с #647 (district price-index) — общий механизм (квартальные ДКП-медианы). Возможно решать вместе. ## Acceptance - [ ] Backtest фактического estimator-output (MAPE/bias) — baseline зафиксирован - [ ] asking→sold фактор выведен из ДКП + валидирован (held-out) - [ ] Коррекция применена ИЛИ dual-display, без регресса (A/B до/после) - [ ] Документировано (методология + ограничения) ## Refs - `estimator.py`, `deals` (49 781 ДКП с geom+price), FDW `gendesign_rosreestr_deals` - `scripts/backtest_estimator.py` (reusable harness — отдельный PR) - #647 (district price-index — общий ДКП-механизм), #564 (sales↔listings pairing)
Light1YT added the
analytics
tradein
вторичка
labels 2026-05-29 12:40:40 +00:00
Author
Owner

Подтверждено backtest-харнессом (scripts/backtest_estimator.py, реальные estimator-функции _filter_outliers+_percentile, sample 60, prod 2026-05-29):

CITY: SOLD median 127 084 vs ASKING median 158 293 (+24.6%)
OVERALL bias +29.6% · MAPE 30.5% · p25-p75 +4.5%..+49.7%
студия +42% · 1к +30% · 2к +32% · 3к +11.5% · 4+ +37%

С IQR-фильтром смещение ВЫШЕ raw-22% (фильтр срезает нижние выбросы → медиана запросов растёт). 3к — наименьшее смещение (+11.5%), студии/4+ — наибольшее (+37-42%). Харнесс read-only, переиспользуемый — повторять по мере накопления #570-истории (тогда point-in-time).

**Подтверждено backtest-харнессом** (`scripts/backtest_estimator.py`, реальные estimator-функции _filter_outliers+_percentile, sample 60, prod 2026-05-29): ``` CITY: SOLD median 127 084 vs ASKING median 158 293 (+24.6%) OVERALL bias +29.6% · MAPE 30.5% · p25-p75 +4.5%..+49.7% студия +42% · 1к +30% · 2к +32% · 3к +11.5% · 4+ +37% ``` С IQR-фильтром смещение ВЫШЕ raw-22% (фильтр срезает нижние выбросы → медиана запросов растёт). 3к — наименьшее смещение (+11.5%), студии/4+ — наибольшее (+37-42%). Харнесс read-only, переиспользуемый — повторять по мере накопления #570-истории (тогда point-in-time).
Author
Owner

Stage 1 (harness A/B) — механизм доказан out-of-sample. Fit ratios на чётных id, eval на нечётных (--holdout-split, sample 200, since 2025-01):

bias% MAPE%
ASKING (текущий) +20.4 25.4
CORRECTED (×per-rooms ratio) -4.0 20.2

Пер-rooms ratio: 1к=0.73, 2к=0.85, 3к/4+/студия→global 0.81 (sold≈81% asking). Студия остаётся +24.7% (n<20, fallback — известное ограничение, мало данных). Смещение +20→-4% подтверждает: per-rooms asking→sold фактор убирает систематику. → Stage 2 (ratio-таблица) + Stage 3 (additive expected_sold в estimator).

**Stage 1 (harness A/B) — механизм доказан out-of-sample.** Fit ratios на чётных id, eval на нечётных (--holdout-split, sample 200, since 2025-01): | | bias% | MAPE% | |---|---|---| | ASKING (текущий) | **+20.4** | 25.4 | | CORRECTED (×per-rooms ratio) | **-4.0** | 20.2 | Пер-rooms ratio: 1к=0.73, 2к=0.85, 3к/4+/студия→global 0.81 (sold≈81% asking). Студия остаётся +24.7% (n<20, fallback — известное ограничение, мало данных). Смещение +20→-4% подтверждает: per-rooms asking→sold фактор убирает систематику. → Stage 2 (ratio-таблица) + Stage 3 (additive expected_sold в estimator).
Author
Owner

Stage 3 LIVE на проде — smoke оценки (Малышева 125, 55м², 2к, HTTP 200):

  • median_price_rub = 8 539 685 ₽ (headline asking — НЕ изменился)
  • expected_sold_price_rub = 6 829 201 ₽
  • ratio = 0.80 (per_rooms, 2к bucket); es/median = 0.800 = ratio → OK
  • per_m2 155 267 → expected_sold_per_m2 124 167; n_analogs 19, confidence medium

Коррекция применяется additive (headline цел). Осталось: S4 (refresh-джоб), S5 (frontend dual-display — нужно решение по primary-цифре + RU-микрокопии).

**Stage 3 LIVE на проде ✅** — smoke оценки (Малышева 125, 55м², 2к, HTTP 200): - median_price_rub = 8 539 685 ₽ (headline asking — НЕ изменился) - expected_sold_price_rub = 6 829 201 ₽ - ratio = 0.80 (per_rooms, 2к bucket); es/median = 0.800 = ratio → OK - per_m2 155 267 → expected_sold_per_m2 124 167; n_analogs 19, confidence medium Коррекция применяется additive (headline цел). Осталось: S4 (refresh-джоб), S5 (frontend dual-display — нужно решение по primary-цифре + RU-микрокопии).
Author
Owner

ЗАВЕРШЕНО end-to-end (S1-S5). asking→sold коррекция live:

  • S1 #650 харнесс+A/B (bias +20%→−4%)
  • S2 #660 таблица asking_to_sold_ratios (0.72-0.93)
  • S3 #661 estimator expected_sold (live: 8.54М→6.83М)
  • S4 #663 дневной refresh
  • S5a #664 web dual-display (обе равнозначно)
  • S5b #665 PDF dual-display

Оценка теперь показывает запрос И ожидаемую продажу (web+PDF), self-refreshing. Deferred (отдельно): point-in-time backtest через listing_source_snapshots (#570, когда накопит); district-уточнение → #647. Закрываю.

✅ **ЗАВЕРШЕНО end-to-end (S1-S5).** asking→sold коррекция live: - S1 #650 харнесс+A/B (bias +20%→−4%) - S2 #660 таблица asking_to_sold_ratios (0.72-0.93) - S3 #661 estimator expected_sold (live: 8.54М→6.83М) - S4 #663 дневной refresh - S5a #664 web dual-display (обе равнозначно) - S5b #665 PDF dual-display Оценка теперь показывает запрос И ожидаемую продажу (web+PDF), self-refreshing. **Deferred (отдельно):** point-in-time backtest через listing_source_snapshots (#570, когда накопит); district-уточнение → #647. Закрываю.
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#648
No description provided.