[P0][tradein][вторичка] #647-4 Backtest gate: MAPE held-out ДКП + canonical A/B (БЛОКИРУЕТ merge integration) #763

Closed
opened 2026-05-30 16:06:59 +00:00 by lekss361 · 4 comments
Owner

Sub-issue 4/4 эпика #647. FULL SEND к 2026-06-01. Это GATE — без зелёного результата #647-3 (estimator integration, client-visible) НЕ мержится.

Зачем

#647 меняет цену, видимую КАЖДЫМ клиентом. Прежде чем влить локационный index в estimator, доказать что он НЕ ухудшает точность.

Задача

  1. Backtest: предсказать held-out ДКП-сделки (train/test split) с quarter_price_index vs без → сравнить MAE/MAPE. Index должен НЕ ухудшать (лучше — улучшать) MAPE.
  2. Canonical A/B: прогнать текущие эталонные оценки до/после index — Малышева 125, Заводская 44а (+ др. из golden-set). Разброс должен быть объясним (центр ↑, окраина ↓), без диких скачков.
  3. No-double-count проверка: если аналоги уже локальны (квартал таргета), index НЕ должен повторно накручивать (см. методологию #647).

DoD (gate-критерии)

  • MAPE на held-out ДКП с index ≤ MAPE без index (не хуже).
  • Canonical-оценки до/после: каждое изменение объяснимо локацией, нет регрессов >X%.
  • Нет double-count на локальных аналогах.
  • Результаты (числа) в комменте к #647-3 + vault.
  • / verdict: можно ли мержить #647-3.

Зависимости

  • Зависит от #647-1 (#740, index данные).
  • ГЕЙТ для #647-3 (integration).

Parent: #647.

> Sub-issue 4/4 эпика #647. FULL SEND к 2026-06-01. **Это GATE — без зелёного результата #647-3 (estimator integration, client-visible) НЕ мержится.** ## Зачем #647 меняет цену, видимую КАЖДЫМ клиентом. Прежде чем влить локационный index в estimator, доказать что он НЕ ухудшает точность. ## Задача 1. **Backtest:** предсказать held-out ДКП-сделки (train/test split) с quarter_price_index vs без → сравнить MAE/MAPE. Index должен НЕ ухудшать (лучше — улучшать) MAPE. 2. **Canonical A/B:** прогнать текущие эталонные оценки до/после index — Малышева 125, Заводская 44а (+ др. из golden-set). Разброс должен быть объясним (центр ↑, окраина ↓), без диких скачков. 3. **No-double-count проверка:** если аналоги уже локальны (квартал таргета), index НЕ должен повторно накручивать (см. методологию #647). ## DoD (gate-критерии) - [ ] MAPE на held-out ДКП с index ≤ MAPE без index (не хуже). - [ ] Canonical-оценки до/после: каждое изменение объяснимо локацией, нет регрессов >X%. - [ ] Нет double-count на локальных аналогах. - [ ] Результаты (числа) в комменте к #647-3 + vault. - [ ] ✅/❌ verdict: можно ли мержить #647-3. ## Зависимости - Зависит от #647-1 (#740, index данные). - ГЕЙТ для #647-3 (integration). Parent: #647.
lekss361 added the
scope/backend
вторичка
status/ready
tradein
priority/p0
labels 2026-05-30 16:06:59 +00:00
bot-backend added
needs-human
and removed
status/ready
labels 2026-05-30 17:17:21 +00:00
Collaborator

needs-human (был status/ready). Это gate, а не code-PR, и автодекомпозиции в чистый воркер он не поддаётся по двум причинам:

  1. Измерять «MAPE с index» нечем без #647-3. Гейт валидирует именно интеграцию index'а в estimator (#647-3), которая ещё не построена (нет в очереди). Backtest «с index» требует реального метода применения index'а из #647-3 — иначе наивный прокси (city_median × index) даст вводящий в заблуждение результат, а это клиентская цена.
  2. DoD = verdict / (можно ли мержить client-visible #647-3) — это человеческое решение, ровно как #755.

Фундамент готов: #760 (mv_quarter_price_index, PR #792 merged) + #762 (FDW foreign table + refresh, PR #797 review). Порядок дальше: (a) человек/tech-analyst определяет метод интеграции index в estimator (#647-3), (b) backtest-харнесс на held-out ДКП по этому методу (zero-risk research-скрипт, можно отдать воркеру когда метод задан), (c) человек выносит verdict по числам. Авто-бот не должен сам апрувить gate на клиентскую цену.

Held-out источник доступен (rosreestr_deals main-БД). Готов написать backtest-скрипт, как только метод применения index из #647-3 будет зафиксирован.

→ `needs-human` (был status/ready). Это **gate**, а не code-PR, и автодекомпозиции в чистый воркер он не поддаётся по двум причинам: 1. **Измерять «MAPE с index» нечем без #647-3.** Гейт валидирует именно интеграцию index'а в estimator (#647-3), которая ещё **не построена** (нет в очереди). Backtest «с index» требует реального метода применения index'а из #647-3 — иначе наивный прокси (`city_median × index`) даст **вводящий в заблуждение** результат, а это **клиентская цена**. 2. **DoD = verdict ✅/❌** (можно ли мержить client-visible #647-3) — это человеческое решение, ровно как #755. Фундамент готов: **#760** (`mv_quarter_price_index`, PR #792 merged) + **#762** (FDW foreign table + refresh, PR #797 review). Порядок дальше: (a) человек/tech-analyst определяет метод интеграции index в estimator (#647-3), (b) backtest-харнесс на held-out ДКП по этому методу (zero-risk research-скрипт, можно отдать воркеру когда метод задан), (c) человек выносит verdict по числам. Авто-бот не должен сам апрувить gate на клиентскую цену. Held-out источник доступен (rosreestr_deals main-БД). Готов написать backtest-скрипт, как только метод применения index из #647-3 будет зафиксирован.
lekss361 added this to the Praktika demo — Mon 2026-06-01 milestone 2026-05-30 17:21:08 +00:00
Author
Owner

🧭 Resolver: решение (Anton, 2026-05-30) — метод интеграции index задаёт человек/tech-analyst; гонка к 06-01.

bot-backend прав: gate нельзя исполнить, пока не зафиксирован МЕТОД применения index в estimator (#647-3). Решение пользователя: метод определяет человек/tech-analyst, затем:

  1. tech-analyst/человек фиксирует метод применения quarter_price_index в estimator (gap-correction между кварталом таргета и кварталами аналогов, no-double-count если аналоги уже локальны) → пишется в #647-3.
  2. #647-3 → status/ready с зафиксированным методом.
  3. backtest-харнесс (этот #763) — zero-risk research-скрипт на held-out ДКП по ЭТОМУ методу, отдаётся воркеру (bot-backend готов написать).
  4. verdict / на client-visible цену — выносит человек.

Фундамент готов: #760 (mv_quarter_price_index, PR #792 merged) + #762 (FDW foreign table + refresh, PR #797).

Оставляю needs-human на #763 — gate ждёт (a) зафиксированного метода #647-3 и (b) человеческого verdict; исполнять backtest пока нечем (нет метода). Это корректное удержание, НЕ блокер capability-типа. Как только метод задан в #647-3 — #763 становится исполнимым (backtest-скрипт → числа → human verdict).

NB resolver: я НЕ снимаю needs-human здесь, т.к. остаток требует решения человека (метод+verdict), не capability-gap.

🧭 **Resolver: решение (Anton, 2026-05-30) — метод интеграции index задаёт человек/tech-analyst; гонка к 06-01.** bot-backend прав: gate нельзя исполнить, пока не зафиксирован МЕТОД применения index в estimator (#647-3). Решение пользователя: **метод определяет человек/tech-analyst**, затем: 1. tech-analyst/человек фиксирует метод применения `quarter_price_index` в estimator (gap-correction между кварталом таргета и кварталами аналогов, no-double-count если аналоги уже локальны) → пишется в #647-3. 2. #647-3 → `status/ready` с зафиксированным методом. 3. backtest-харнесс (этот #763) — zero-risk research-скрипт на held-out ДКП по ЭТОМУ методу, отдаётся воркеру (bot-backend готов написать). 4. verdict ✅/❌ на client-visible цену — выносит человек. Фундамент готов: #760 (`mv_quarter_price_index`, PR #792 merged) + #762 (FDW foreign table + refresh, PR #797). **Оставляю `needs-human` на #763** — gate ждёт (a) зафиксированного метода #647-3 и (b) человеческого verdict; исполнять backtest пока нечем (нет метода). Это корректное удержание, НЕ блокер capability-типа. Как только метод задан в #647-3 — #763 становится исполнимым (backtest-скрипт → числа → human verdict). NB resolver: я НЕ снимаю needs-human здесь, т.к. остаток требует решения человека (метод+verdict), не capability-gap.
Author
Owner

📊 Backtest #763 прогнан (backend-engineer, ZERO-RISK research на реальных ДКП). Числа + caveats. Verdict — за человеком.

Скрипт scripts/backtest_quarter_index.py (worktree-ветка, НЕ мержен), ruff clean. Прогон 2026-05-30 на rosreestr_deals 2024Q1–2025Q4, ЕКБ.

MAPE (реальные числа из БД)

Выборка N MAPE_baseline MAPE_index Δ
Strict (ppm² 50–400k, area 25–200) 38 841 23.0% 18.7% +4.25 pp (+18.5% rel)
Broad 45 177 31.3% 26.7% +4.65 pp

62.4% сделок улучшились (24238/14590 improved/regressed).

Per-segment (guard-2 proxy по n_deals)

Сегмент N MAPE_b MAPE_i Δ
high_n ≥200 (guard-2 зона) 16 652 28.0% 27.1% +0.86 pp (no-regress ✓)
medium 50–199 20 183 31.4% 26.6% +4.82 pp
low 10–49 8 342 37.9% 26.1% +11.78 pp
Индекс помогает БОЛЬШЕ всего где он реально отклоняется от 1.0 (extreme-deviation bucket: +14.88 pp). В near-1.0 зоне регресса нет (+0.29 pp).

Canonical A/B: 23/26 кварталов OK (88%)

2 регрессии — структурная гетерогенность ВНУТРИ квартала, не дефект индекса:

  • Радищева 66:41:0401017 (idx=2.556): бимодальное распределение (дешёвая вторичка 65k + премиум-новостройки 290k). В проде закрывается guard-1 (same-building anchor Tier A).
  • Титова 66:41:0505002 (idx=1.214): две улицы (Монтёрская 149k / Титова 95k) в одном кадастр-квартале.

⚠️ CAVEATS (важно для честного verdict)

  1. Baseline упрощён: backtest сравнивал city_median_ppm² (110 883) × index-correction vs city_median — НЕ полный radius-analog pipeline реального estimator'а (с tier'ами, same-building anchor, cohort). Реальный estimator уже точнее baseline → абсолютный прирост на проде может отличаться. Это сигнал что направление верное, но НЕ замер на проде-estimator.
  2. Guard-1 (same-building) НЕ смоделирован (в gendesign-БД нет listings) — guard-2 проксирован по n_deals.
  3. Источник index в прогоне — выведен из rosreestr_deals напрямую (не FDW-таблица quarter_price_index, что в tradein) — значения близки, но это другой расчётный путь.

Рекомендация backend перед мержем #764

  • Доп. гард: if price_index > 2.0 AND n_deals < 50 → no-op (отсекает ~3-4 номинальных квартала с экстремальным индексом).
  • Guard-1 (same-building anchor) активен в проде → закрывает премиум-бимодальные кварталы.

Verdict — НЕ выношу сам (client-visible pricing = человеческое решение)

Сигнал: index улучшает MAPE (+4.25pp, 62% сделок) без регресса в dense-зоне, 88% canonical OK. Но baseline упрощён → это не финальный замер на проде-estimator. Оставляю needs-human для verdict Anton/lekss361. См. следующий вопрос.

📊 **Backtest #763 прогнан (backend-engineer, ZERO-RISK research на реальных ДКП). Числа + caveats. Verdict — за человеком.** Скрипт `scripts/backtest_quarter_index.py` (worktree-ветка, НЕ мержен), ruff clean. Прогон 2026-05-30 на `rosreestr_deals` 2024Q1–2025Q4, ЕКБ. ## MAPE (реальные числа из БД) | Выборка | N | MAPE_baseline | MAPE_index | Δ | |---|---|---|---|---| | Strict (ppm² 50–400k, area 25–200) | **38 841** | **23.0%** | **18.7%** | **+4.25 pp (+18.5% rel)** | | Broad | 45 177 | 31.3% | 26.7% | +4.65 pp | 62.4% сделок улучшились (24238/14590 improved/regressed). ## Per-segment (guard-2 proxy по n_deals) | Сегмент | N | MAPE_b | MAPE_i | Δ | |---|---|---|---|---| | high_n ≥200 (guard-2 зона) | 16 652 | 28.0% | 27.1% | +0.86 pp (no-regress ✓) | | medium 50–199 | 20 183 | 31.4% | 26.6% | +4.82 pp | | low 10–49 | 8 342 | 37.9% | 26.1% | +11.78 pp | Индекс помогает БОЛЬШЕ всего где он реально отклоняется от 1.0 (extreme-deviation bucket: +14.88 pp). В near-1.0 зоне регресса нет (+0.29 pp). ## Canonical A/B: 23/26 кварталов OK (88%) 2 регрессии — структурная гетерогенность ВНУТРИ квартала, не дефект индекса: - **Радищева 66:41:0401017** (idx=2.556): бимодальное распределение (дешёвая вторичка 65k + премиум-новостройки 290k). В проде закрывается guard-1 (same-building anchor Tier A). - **Титова 66:41:0505002** (idx=1.214): две улицы (Монтёрская 149k / Титова 95k) в одном кадастр-квартале. ## ⚠️ CAVEATS (важно для честного verdict) 1. **Baseline упрощён:** backtest сравнивал `city_median_ppm² (110 883) × index-correction` vs `city_median` — НЕ полный radius-analog pipeline реального estimator'а (с tier'ами, same-building anchor, cohort). Реальный estimator уже точнее baseline → абсолютный прирост на проде может отличаться. Это **сигнал что направление верное**, но НЕ замер на проде-estimator. 2. **Guard-1 (same-building) НЕ смоделирован** (в gendesign-БД нет listings) — guard-2 проксирован по n_deals. 3. Источник index в прогоне — выведен из `rosreestr_deals` напрямую (не FDW-таблица `quarter_price_index`, что в tradein) — значения близки, но это другой расчётный путь. ## Рекомендация backend перед мержем #764 - Доп. гард: `if price_index > 2.0 AND n_deals < 50 → no-op` (отсекает ~3-4 номинальных квартала с экстремальным индексом). - Guard-1 (same-building anchor) активен в проде → закрывает премиум-бимодальные кварталы. ## Verdict — НЕ выношу сам (client-visible pricing = человеческое решение) Сигнал: **index улучшает MAPE (+4.25pp, 62% сделок) без регресса в dense-зоне, 88% canonical OK**. Но baseline упрощён → это не финальный замер на проде-estimator. Оставляю `needs-human` для verdict Anton/lekss361. См. следующий вопрос.
Author
Owner

VERDICT: ЗЕЛЁНЫЙ (Anton/lekss361, 2026-05-30). Gate #647-4 пройден → #764 разблокирован на build+merge.

backtest показал +4.25pp MAPE (23.0%→18.7%, 62% сделок улучшились, no-regress в dense-зоне, 88% canonical OK). Caveat про упрощённый baseline принят, направление подтверждено. Человек дал go на client-visible изменение цены.

Условие переносится в #764 как требование к реализации:

  • доп-гард if price_index > 2.0 AND n_deals < 50 → no-op (бимодальные/номинальные кварталы);
  • guard-1 (same-building anchor) + guard-2 (>0.6 quarter-match) как в методологии 6383.

Gate закрыт . Закрываю #763 (status/done). #764status/ready, merge разрешён после стандартного review (backtest-условие выполнено).

✅ **VERDICT: ЗЕЛЁНЫЙ (Anton/lekss361, 2026-05-30). Gate #647-4 пройден → #764 разблокирован на build+merge.** backtest показал +4.25pp MAPE (23.0%→18.7%, 62% сделок улучшились, no-regress в dense-зоне, 88% canonical OK). Caveat про упрощённый baseline принят, направление подтверждено. Человек дал go на client-visible изменение цены. **Условие переносится в #764 как требование к реализации:** - доп-гард `if price_index > 2.0 AND n_deals < 50 → no-op` (бимодальные/номинальные кварталы); - guard-1 (same-building anchor) + guard-2 (>0.6 quarter-match) как в методологии 6383. Gate закрыт ✅. Закрываю #763 (`status/done`). #764 → `status/ready`, merge разрешён после стандартного review (backtest-условие выполнено).
lekss361 added
status/done
and removed
needs-human
labels 2026-05-30 18:55:45 +00:00
Sign in to join this conversation.
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#763
No description provided.