feat(tradein/estimator): кросс-source физдедуп аналогов за флагом (#2087 H4) #2173

Merged
lekss361 merged 2 commits from fix/tradein-analog-dedup-cv into main 2026-07-02 18:04:39 +00:00
Owner

Summary

  • #2087 H4 — кросс-source физический дедуп аналогов. Один физлот, кросс-запостенный на avito+cian+domklik, имеет разные source/source_id → existing radius-дедуп по (source, source_id) его НЕ ловит → n_analogs и source_counts раздуты кросс-постами.
  • _dedup_cross_source схлопывает дубли по физическому ключу building_cadastral | нормализованный address + floor + area-bucket round(m²) + price-bucket round(₽/100k) ДО подсчёта n_analogs/median/cv, оставляя свежайшего (scraped_at) представителя. «Лот считается один раз».
  • За флагом estimate_dedup_analogs_enabled (default OFF = байт-идентично; регресс-гейт зелёный).
  • out-рычаг (post-weight MAD-clip, estimate_sb_clip_after_weight) — УЖЕ default ON в проде, ничего менять не потребовалось.

Бэктест #1966 (400 ДКП, full spine, OFF vs ON, тот же детерминир. сэмпл, прод-контейнер)

Метрика OFF ON
overall MAPE 13.89% 13.89%
coverage 83.33% 83.33%
bias −3.83% −3.83%
median width 0.743 0.743
median cv 0.0988 0.0988
avg n_analogs 27.64 27.57

per-rooms MAPE идентичны (студия 18.93 / 1к 11.95 / 2к 17.4 / 3к 12.27). Дедуп отработал 107× на 335 оценках (OFF: 0×).

Вывод: кросс-посты имеют identical price → нулевой вклад в дисперсию → cv/коридор НЕ сужаются. Это фикс честности счёта (n_analogs не раздут ×3, source_counts по физлотам), accuracy-нейтральный, а НЕ рычаг cv-сужения. Рычаг cv→коридор — estimate_sb_clip_after_weight (уже ON).

Рекомендация default: OFF — narrowing не продемонстрирован бэктестом (гейт «держит accuracy И сужает коридор» выполнен только наполовину); флаг готов к canary/монкипатч-бэктесту; включение = решение продукта (меняет user-visible n_analogs).

Test plan

  • ruff check зелёно, pre-commit passed
  • tests/test_estimator_dedup_cross_source_2087.py (10 юнитов: физключ, представитель, флаг OFF=no-op, дубли схлопываются / не-дубли нет)
  • 175 estimator+backtest тестов зелёно, вкл. test_backtest_regression_gate (флаг OFF байт-идентичен)

Refs #2087

## Summary - **#2087 H4** — кросс-source физический дедуп аналогов. Один физлот, кросс-запостенный на avito+cian+domklik, имеет разные `source`/`source_id` → existing radius-дедуп по `(source, source_id)` его НЕ ловит → `n_analogs` и `source_counts` раздуты кросс-постами. - `_dedup_cross_source` схлопывает дубли по физическому ключу `building_cadastral | нормализованный address + floor + area-bucket round(m²) + price-bucket round(₽/100k)` ДО подсчёта `n_analogs/median/cv`, оставляя свежайшего (`scraped_at`) представителя. «Лот считается один раз». - За флагом `estimate_dedup_analogs_enabled` (**default OFF** = байт-идентично; регресс-гейт зелёный). - `out`-рычаг (post-weight MAD-clip, `estimate_sb_clip_after_weight`) — УЖЕ default ON в проде, ничего менять не потребовалось. ## Бэктест #1966 (400 ДКП, full spine, OFF vs ON, тот же детерминир. сэмпл, прод-контейнер) | Метрика | OFF | ON | |---|---|---| | overall MAPE | 13.89% | 13.89% | | coverage | 83.33% | 83.33% | | bias | −3.83% | −3.83% | | median width | 0.743 | 0.743 | | median cv | 0.0988 | 0.0988 | | avg n_analogs | 27.64 | 27.57 | per-rooms MAPE идентичны (студия 18.93 / 1к 11.95 / 2к 17.4 / 3к 12.27). Дедуп отработал **107×** на 335 оценках (OFF: 0×). **Вывод:** кросс-посты имеют identical price → нулевой вклад в дисперсию → cv/коридор НЕ сужаются. Это фикс **честности счёта** (n_analogs не раздут ×3, source_counts по физлотам), **accuracy-нейтральный**, а НЕ рычаг cv-сужения. Рычаг cv→коридор — `estimate_sb_clip_after_weight` (уже ON). **Рекомендация default:** OFF — narrowing не продемонстрирован бэктестом (гейт «держит accuracy И сужает коридор» выполнен только наполовину); флаг готов к canary/монкипатч-бэктесту; включение = решение продукта (меняет user-visible n_analogs). ## Test plan - [x] `ruff check` зелёно, pre-commit passed - [x] `tests/test_estimator_dedup_cross_source_2087.py` (10 юнитов: физключ, представитель, флаг OFF=no-op, дубли схлопываются / не-дубли нет) - [x] 175 estimator+backtest тестов зелёно, вкл. `test_backtest_regression_gate` (флаг OFF байт-идентичен) Refs #2087
lekss361 added 1 commit 2026-07-02 17:24:00 +00:00
feat(tradein/estimator): кросс-source физдедуп аналогов за флагом (#2087 H4)
All checks were successful
CI / changes (pull_request) Successful in 7s
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
3ad1550363
Один физический лот кросс-постится на avito+cian+domklik (разные source и
source_id) → existing radius-дедуп по (source, source_id) его НЕ ловит →
n_analogs И source_counts раздуты кросс-постами (аудит #2087: лот
80м²/265000₽/м² = N1+Домклик+Циан ×3; «14 аналогов» → ~6-7 уникальных).

_dedup_cross_source схлопывает дубли по физическому ключу (building_cadastral
| нормализованный address + floor + area-bucket round(m²) + price-bucket
round(₽/100k)) ДО подсчёта n_analogs/median/cv, оставляя свежайшего (scraped_at)
представителя. За флагом estimate_dedup_analogs_enabled (default OFF =
байт-идентично; регресс-гейт зелёный).

Бэктест #1966 (400 ДКП, full spine, OFF vs ON, тот же сэмпл): MAPE 13.89%→13.89%,
coverage 83.33%→83.33%, bias −3.83%→−3.83%, median width 0.743→0.743, median cv
0.0988→0.0988, avg n_analogs 27.64→27.57 (дедуп отработал 107× на 335 оценках).
Кросс-посты имеют identical price → нулевой вклад в дисперсию → cv/коридор НЕ
сужаются: это фикс ЧЕСТНОСТИ СЧЁТА (n_analogs не раздут, source_counts по
физлотам), accuracy-нейтральный, а НЕ рычаг cv-сужения (рычаг cv→коридор —
estimate_sb_clip_after_weight, уже default ON). Default OFF — narrowing не
продемонстрирован; флаг готов к canary/монкипатч-бэктесту.

Refs #2087
bot-backend added 1 commit 2026-07-02 17:56:52 +00:00
feat(tradein/estimator): включить кросс-source физдедуп аналогов по умолчанию (#2087 H4, #2173)
All checks were successful
CI / frontend-tests (pull_request) Has been skipped
CI / changes (pull_request) Successful in 9s
CI / backend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
3505a4e3e5
estimate_dedup_analogs_enabled: False → True. Бэктест #1966 OFF vs ON
accuracy-идентичен (MAPE 13.89%, coverage 83.33%, bias −3.83%, median
width/cv без изменений) — включение меняет только user-visible n_analogs:
перестаёт быть раздутым кросс-постингом одного физлота на avito+cian+domklik
×3. Откат — ENV ESTIMATE_DEDUP_ANALOGS_ENABLED=false.

Тесты разведены default vs explicit (не ослаблены):
- test_estimator_dedup_cross_source_2087: добавлен test_dedup_default_is_on
  (без monkeypatch — дедуп активен по умолчанию); test_dedup_flag_off_is_noop
  оставляет ЯВНЫЙ OFF-override.
- test_backtest_regression_gate: пиним флаг OFF — гейт это байт-идентичный
  replay frozen OFF-capture (recorded call-sequence фикстуры без дедупа); с
  ON _dedup_cross_source триммит listings до quarter_indexes_lookup и
  control-flow расходится с записью.
- test_estimator_n_analogs_priced (autouse) + test_radius_path_n_analogs_unchanged:
  пиним OFF — инвариант «n_analogs = число priced-аналогов»/radius-passthrough
  ортогонален дедупу, а синтетические аналоги делят адрес/этаж/площадь и
  схлопнулись бы как кросс-посты.

Refs #2087, #2173
lekss361 merged commit b2d794587f into main 2026-07-02 18:04:39 +00:00
lekss361 deleted branch fix/tradein-analog-dedup-cv 2026-07-02 18:04:40 +00:00
Sign in to join this conversation.
No reviewers
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#2173
No description provided.