fix(tradein/estimator): clamp premium headline к коридору сделок + робастный anchor (#1795) #1807

Merged
lekss361 merged 1 commit from fix/tradein-estimator-premium-clamp-1795 into main 2026-06-19 17:37:58 +00:00
Owner

Проблема (#1795)

Trade-in estimator системно завышает headline для бизнес/премиум — ~2× выше коридора реальных сделок Росреестра (эконом/комфорт сходятся ±5%). Малышева 30: TI 296k ₽/м² vs медиана сделок 138k (n20); Куйбышева 44: 324k vs 129k (n30); Татищева 20: 310k vs 150k (n29).

Корень — три складывающихся механизма: (1) _compute_anchor заменяет радиусную медиану similarity-weighted средним, которое на право-скошенном премиум-распределении тянут элитные комплы из Tier C micro-radius; (2) MAD-clip (k=3.5) слишком мягкий на малых n; (3) ДКП-коридор сделок — только advisory, headline не опускает.

Фикс — 4 шага, все за settings-флагами (дефолты = ВКЛ, OFF восстанавливает старое поведение)

1. Soft-кламп headline к коридору сделок (главное) — новая чистая функция _apply_corridor_clamp (estimator.py:3893-3937), вызов 2665-2691 ДО блока expected_sold (asking↔sold↔range пересчитываются от склампленного headline автоматически). dkp_raw фетч перенесён вверх (2074-2083), advisory-блок переиспользует тот же объект. Кламп при median_ppm2 > high_ppm2×(1+slack) И count>=min_n И tier!="A" → жёсткий кап к high_ppm2×(1+slack), множитель к median_price/range_low/range_high. Флаги: estimate_corridor_clamp_enabled=True, estimate_corridor_clamp_min_n=10, estimate_corridor_clamp_slack=0.25.

2. Ужесточённый MAD-clip на малых выборках в _compute_same_building_anchor (1683-1702): при tier!="A" И n<10 эффективный mad_k=2.5. Флаги: estimate_sb_mad_k_small_n=2.5, estimate_sb_small_n_threshold=10. NB: добавлен guard tier!="A" (отклонение от спека, обосновано) — без него агрессивный clip съедал легитимные топ-юниты того же дома → anchor=None → fallback на заниженную медиану, ломал 13 pre-existing Tier-A тестов. Tier-A exemption консистентен с философией issue.

3. Гейт Tier C (2295-2320): при anchor_tier=="C" И anchor_ppm2 > corridor_high×1.5 → anchor=None (оставляем консервативную радиусную медиану). Флаг: estimate_anchor_tier_c_corridor_mult=1.5.

4. Жёстче Tukey на малых выборках в _filter_outliers (3940-3962): при n<15 k снижается 1.5→1.0. Флаги: estimate_outlier_small_n_threshold=15, estimate_outlier_tukey_k_small=1.0.

Тесты

  • 8 новых unit-тестов в tests/test_estimator_pure_units.py: _apply_corridor_clamp (выше коридора+n>=10+tierC→клампится; в коридоре→no-op; tier A→exempt; n<min→no-op; флаг OFF→no-op; tier None→клампится; zero corridor→no-op) + Tukey малых выборок.
  • Полный pytest: 2166 passed, 1 skipped, 2 failed. Оба failed pre-existing на чистом forgejo/main (проверено git stash): test_cian_valuation::test_cache_hit_returns_cached (KeyError) + test_search_api::test_search_cache_hit (401 env-auth) — к этому фиксу отношения не имеют.
  • ruff + pre-commit green.

DoD

  • Малышева/Куйбышева/Татищева: кламп → headline к corridor_high×1.25 (≈172-190k вместо 296-324k), кроме Tier-A override.
  • Эконом/комфорт без регресса (при median_ppm2 <= high×(1+slack) → no-op, покрыто тестом).
  • Боевая верификация на реальных адресах требует прод-БД с rosreestr deals — логика покрыта unit'ами, итоговая цифра зависит от runtime corridor_high.

Связано: #652 (dkp_corridor), #651 (sb_anchor), #677/#681 (asking→sold).

## Проблема (#1795) Trade-in estimator системно **завышает headline для бизнес/премиум** — ~2× выше коридора реальных сделок Росреестра (эконом/комфорт сходятся ±5%). Малышева 30: TI 296k ₽/м² vs медиана сделок 138k (n20); Куйбышева 44: 324k vs 129k (n30); Татищева 20: 310k vs 150k (n29). Корень — три складывающихся механизма: (1) `_compute_anchor` заменяет радиусную медиану similarity-weighted **средним**, которое на право-скошенном премиум-распределении тянут элитные комплы из Tier C micro-radius; (2) MAD-clip (k=3.5) слишком мягкий на малых n; (3) ДКП-коридор сделок — только **advisory**, headline не опускает. ## Фикс — 4 шага, все за settings-флагами (дефолты = ВКЛ, OFF восстанавливает старое поведение) **1. Soft-кламп headline к коридору сделок** (главное) — новая чистая функция `_apply_corridor_clamp` (`estimator.py:3893-3937`), вызов `2665-2691` ДО блока expected_sold (asking↔sold↔range пересчитываются от склампленного headline автоматически). `dkp_raw` фетч перенесён вверх (`2074-2083`), advisory-блок переиспользует тот же объект. Кламп при `median_ppm2 > high_ppm2×(1+slack)` И `count>=min_n` И `tier!="A"` → жёсткий кап к `high_ppm2×(1+slack)`, множитель к `median_price`/`range_low`/`range_high`. Флаги: `estimate_corridor_clamp_enabled=True`, `estimate_corridor_clamp_min_n=10`, `estimate_corridor_clamp_slack=0.25`. **2. Ужесточённый MAD-clip на малых выборках** в `_compute_same_building_anchor` (`1683-1702`): при `tier!="A"` И `n<10` эффективный `mad_k=2.5`. Флаги: `estimate_sb_mad_k_small_n=2.5`, `estimate_sb_small_n_threshold=10`. **NB:** добавлен guard `tier!="A"` (отклонение от спека, обосновано) — без него агрессивный clip съедал легитимные топ-юниты того же дома → anchor=None → fallback на заниженную медиану, ломал 13 pre-existing Tier-A тестов. Tier-A exemption консистентен с философией issue. **3. Гейт Tier C** (`2295-2320`): при `anchor_tier=="C"` И `anchor_ppm2 > corridor_high×1.5` → anchor=None (оставляем консервативную радиусную медиану). Флаг: `estimate_anchor_tier_c_corridor_mult=1.5`. **4. Жёстче Tukey на малых выборках** в `_filter_outliers` (`3940-3962`): при `n<15` k снижается 1.5→1.0. Флаги: `estimate_outlier_small_n_threshold=15`, `estimate_outlier_tukey_k_small=1.0`. ## Тесты - 8 новых unit-тестов в `tests/test_estimator_pure_units.py`: `_apply_corridor_clamp` (выше коридора+n>=10+tierC→клампится; в коридоре→no-op; tier A→exempt; n<min→no-op; флаг OFF→no-op; tier None→клампится; zero corridor→no-op) + Tukey малых выборок. - Полный pytest: **2166 passed, 1 skipped, 2 failed**. Оба failed pre-existing на чистом `forgejo/main` (проверено `git stash`): `test_cian_valuation::test_cache_hit_returns_cached` (KeyError) + `test_search_api::test_search_cache_hit` (401 env-auth) — к этому фиксу отношения не имеют. - ruff + pre-commit green. ## DoD - Малышева/Куйбышева/Татищева: кламп → headline к `corridor_high×1.25` (≈172-190k вместо 296-324k), кроме Tier-A override. - Эконом/комфорт без регресса (при `median_ppm2 <= high×(1+slack)` → no-op, покрыто тестом). - Боевая верификация на реальных адресах требует прод-БД с rosreestr deals — логика покрыта unit'ами, итоговая цифра зависит от runtime corridor_high. Связано: #652 (dkp_corridor), #651 (sb_anchor), #677/#681 (asking→sold).
lekss361 added 1 commit 2026-06-19 17:36:24 +00:00
fix(tradein/estimator): clamp premium headline к коридору сделок + робастный anchor (#1795)
All checks were successful
CI / changes (pull_request) Successful in 6s
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
494f2e00e0
lekss361 merged commit abbe0b871d into main 2026-06-19 17:37:58 +00:00
lekss361 deleted branch fix/tradein-estimator-premium-clamp-1795 2026-06-19 17:37:59 +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#1807
No description provided.