fix(mera/backtest): свежая фикстура не реплеилась, а разрез по сегментам врал по построению #3254

Merged
lekss361 merged 1 commit from fix/3251-backtest-harness-stale-fixture-and-outcome-bucketing into main 2026-08-29 20:11:12 +00:00
Owner

Summary

Три связанные правки в харнесе бэктеста + замена фикстуры. Пункты 3 и 4 из плана сессии; открывают дорогу #3248.

1. Свежая фикстура не реплеилась вообще

replay_fixture жёстко пинил estimate_dedup_analogs_enabled = False. Это верно только для фикстуры, захваченной до #2173, когда дедуп был выключен по умолчанию. Любая свежая фикстура становилась нереплеибельной: при dedup=True на захвате часть сделок уходит под sufficiency-гейт с median_ppm2<=0, ratio_resolver для них не зовётся, а реплей с dedup=False их оценивает и падает на control flow diverged from capture (331 сделка из 1600). Теперь значение берётся из settings_at_capture; фикстуры без этого поля ведут себя как раньше.

2. per_segment врёт по построению

Разрез группирует ошибки по SOLD ₽/м² — по величине, которую движок и предсказывает. Корзины отобраны по шуму исхода → регрессия к среднему: даже несмещённый предиктор выглядит завышающим в дешёвых корзинах и занижающим в дорогих.

Контрольный опыт (13 074 сделки ЕКБ), предиктор БЕЗ сегментного перекоса по построению:

бэнд по SOLD артефакт наблюдавшийся перекос движка
эконом +22.28% +2.46%
бизнес −17.04% −9.74%
элит −30.06% −18.45%

Артефакт больше «находки». По нему уже один раз откалибровали estimate_segment_multipliers (#2255) — множители обезврежены комментарием в #3249. Блок оставлен для сравнимости с историей прогонов, но помечен предупреждением в докстринге и в печатном выводе.

3. Новый разрез per_area_bucket

Ось независима от исхода, границы совпадают с asking_to_sold_ratios (30/44/62/85) — промах конкретного бакета ratio виден сразу. Аддитивно: area_rows=None у старых вызывающих даёт пустой словарь, форма ответа не меняется.

4. Фикстура заменена на свежую

Прежняя от 2026-07-04 старше перехода ratio на area-бакеты (#2620, 2026-08-05) — она измеряла точность конфигурации, которой в проде уже нет. Новая: 1600 сделок ЕКБ с 2025-06, 1269 оценённых, --resolve-house-id.

было стало
n 269 1269
MAPE 12.63% 13.90%
bias −3.74% −0.78%
coverage 86.13%
unrecorded_lookup_calls 0 0

Сдвиг метрик — смена измерительной выборки, НЕ регресс движка. Выборка больше в 4.7 раза и отражает действующую конфигурацию.

5. Каверза (d) в докстринге модуля

Предсказание идёт из центроида улицы, а не из адреса дома: в открытом датасете Росреестра нет номера дома (deals.address = «Екатеринбург, Краснолесья»), кадастры все вида 66:41:0000000:0 (пригодных 0 из 108 623), и 65 971 сделка за 2025+ садится всего в 3 137 координат. Отсюда же --resolve-house-id резолвит лишь ~40%.

Test plan

  • pytest tests/test_backtest_regression_gate.py tests/test_backtest_fixture_roundtrip.py tests/test_backtest_estimator.py — 77 passed
  • свежая фикстура реплеится, unrecorded_lookup_calls = 0, числа сходятся с прямым прогоном по БД
  • per_area_bucket на старой фикстуре не двигает overall (12.63 / −3.74 байт-в-байт) — блок чисто аддитивный
  • ruff check + ruff format --check чисто
  • CI зелёный
## Summary Три связанные правки в харнесе бэктеста + замена фикстуры. Пункты 3 и 4 из плана сессии; открывают дорогу #3248. ### 1. Свежая фикстура не реплеилась вообще `replay_fixture` жёстко пинил `estimate_dedup_analogs_enabled = False`. Это верно только для фикстуры, захваченной **до #2173**, когда дедуп был выключен по умолчанию. Любая свежая фикстура становилась нереплеибельной: при `dedup=True` на захвате часть сделок уходит под sufficiency-гейт с `median_ppm2<=0`, `ratio_resolver` для них не зовётся, а реплей с `dedup=False` их оценивает и падает на `control flow diverged from capture` (331 сделка из 1600). Теперь значение берётся из `settings_at_capture`; фикстуры без этого поля ведут себя как раньше. ### 2. `per_segment` врёт по построению Разрез группирует ошибки **по SOLD ₽/м²** — по величине, которую движок и предсказывает. Корзины отобраны по шуму исхода → регрессия к среднему: даже несмещённый предиктор выглядит завышающим в дешёвых корзинах и занижающим в дорогих. Контрольный опыт (13 074 сделки ЕКБ), предиктор БЕЗ сегментного перекоса по построению: | бэнд по SOLD | артефакт | наблюдавшийся перекос движка | |---|---|---| | эконом | +22.28% | +2.46% | | бизнес | **−17.04%** | −9.74% | | элит | **−30.06%** | −18.45% | Артефакт больше «находки». По нему уже один раз откалибровали `estimate_segment_multipliers` (#2255) — множители обезврежены комментарием в #3249. Блок оставлен для сравнимости с историей прогонов, но помечен предупреждением в докстринге и в печатном выводе. ### 3. Новый разрез `per_area_bucket` Ось независима от исхода, границы совпадают с `asking_to_sold_ratios` (30/44/62/85) — промах конкретного бакета ratio виден сразу. Аддитивно: `area_rows=None` у старых вызывающих даёт пустой словарь, форма ответа не меняется. ### 4. Фикстура заменена на свежую Прежняя от 2026-07-04 старше перехода ratio на area-бакеты (#2620, 2026-08-05) — она измеряла точность конфигурации, которой в проде уже нет. Новая: 1600 сделок ЕКБ с 2025-06, 1269 оценённых, `--resolve-house-id`. | | было | стало | |---|---|---| | n | 269 | **1269** | | MAPE | 12.63% | 13.90% | | bias | −3.74% | −0.78% | | coverage | — | 86.13% | | `unrecorded_lookup_calls` | 0 | **0** | Сдвиг метрик — смена измерительной выборки, НЕ регресс движка. Выборка больше в 4.7 раза и отражает действующую конфигурацию. ### 5. Каверза (d) в докстринге модуля Предсказание идёт **из центроида улицы**, а не из адреса дома: в открытом датасете Росреестра нет номера дома (`deals.address` = «Екатеринбург, Краснолесья»), кадастры все вида `66:41:0000000:0` (пригодных 0 из 108 623), и 65 971 сделка за 2025+ садится всего в 3 137 координат. Отсюда же `--resolve-house-id` резолвит лишь ~40%. ## Test plan - [x] `pytest tests/test_backtest_regression_gate.py tests/test_backtest_fixture_roundtrip.py tests/test_backtest_estimator.py` — 77 passed - [x] свежая фикстура реплеится, `unrecorded_lookup_calls = 0`, числа сходятся с прямым прогоном по БД - [x] `per_area_bucket` на старой фикстуре не двигает `overall` (12.63 / −3.74 байт-в-байт) — блок чисто аддитивный - [x] `ruff check` + `ruff format --check` чисто - [ ] CI зелёный
lekss361 added 1 commit 2026-08-29 20:05:49 +00:00
fix(mera/backtest): свежая фикстура не реплеилась, а разрез по сегментам врал
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI / changes (pull_request) Successful in 10s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 4m58s
9516d95277
Три связанные правки в харнесе бэктеста.

1. replay_fixture жёстко пинил estimate_dedup_analogs_enabled = False. Это верно
   только для фикстуры, захваченной до #2173, когда дедуп был выключен по
   умолчанию. Любая свежая фикстура при этом становилась нереплеибельной: с
   dedup=True на захвате часть сделок уходит под sufficiency-гейт с
   median_ppm2<=0, ratio_resolver для них не зовётся, а реплей с dedup=False их
   оценивает и падает на «control flow diverged from capture». Теперь значение
   берётся из settings_at_capture; фикстуры без этого поля ведут себя как раньше.

2. Разрез per_segment группирует ошибки по SOLD руб/м2 — по величине, которую
   движок и предсказывает. Корзины отобраны по шуму исхода, отсюда регрессия к
   среднему: даже несмещённый предиктор выглядит завышающим в дешёвых корзинах и
   занижающим в дорогих. На контрольном предикторе без перекоса по построению
   (13074 сделки ЕКБ) этот разрез даёт бизнес -17.04% и элит -30.06% — больше,
   чем наблюдавшийся перекос движка. По этому артефакту уже один раз
   откалибровали estimate_segment_multipliers (#2255). Блок оставлен для
   сравнимости с историей прогонов, но помечен предупреждением в докстринге и в
   печатном выводе.

3. Добавлен per_area_bucket — разрез по площади, независимой от исхода, с
   границами asking_to_sold_ratios (30/44/62/85). Промах конкретного бакета ratio
   виден сразу. Блок аддитивный: area_rows=None у старых вызывающих даёт пустой
   словарь, форма ответа не меняется.

Фикстура заменена на свежую (1600 сделок ЕКБ с 2025-06, 1269 оценённых, захват
после #2620) — прежняя от 2026-07-04 старше перехода ratio на area-бакеты и
показывала точность конфигурации, которой в проде уже нет. Baseline перезахвачен:
n 269 -> 1269, MAPE 12.63 -> 13.90, bias -3.74 -> -0.78, покрытие 86.13%.
Сдвиг метрик — смена измерительной выборки, а не регресс движка.

В каверзы модуля добавлен пункт (d): предсказание идёт из центроида улицы, а не
из адреса дома — в открытом датасете Росреестра нет номера дома, кадастры все
вида 66:41:0000000:0, и 65971 сделка садится в 3137 координат.
lekss361 merged commit e523c8949c into main 2026-08-29 20:11:12 +00:00
lekss361 deleted branch fix/3251-backtest-harness-stale-fixture-and-outcome-bucketing 2026-08-29 20:11:13 +00:00
Sign in to join this conversation.
No reviewers
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#3254
No description provided.