fix(estimator): low-findings аудита «Мера» — cian sanity, deal floor-edge, price_trend dedup, DEAL_MAX verify #1863

Merged
lekss361 merged 4 commits from fix/estimator-low-findings into main 2026-06-21 06:16:35 +00:00
Owner

Закрывает low-findings аудита «Мера» (последний хвост). 4 покоммитных фикса.

Коммиты

  1. 50a72f03 Cian valuation sanity bounds_is_price_sane: sale_price_rub вне [cian_valuation_min_rub=500k, max_rub=500M] или low>high/отрицательные → warning + None (битый API не становится якорем). Заодно починен pre-existing KeyError в тест-моке.
  2. 81ee864d _is_plausible_deal floor/area/price edgefloor<1 (ловит 0/-5) или >60 → drop; area<=0/price<=0 → drop; None graceful. Защита от битого парсера. Caller _fetch_deals прокидывает area/price.
  3. af1d8827 cross-source dedup price_trendDISTINCT ON (round(area), floor, price, date) с приоритетом source (avito_imv>yandex) → одна сделка на Avito+Yandex не двоится в тренде. Легитимно разные квартиры (разная цена) не схлопываются. Флаг estimate_price_trend_dedup_enabled(True).
  4. 92725c61 DEAL_MAX_PPM2 — verify, НЕ менял — проверено по prod deals (49791 записей): p90=172k, p99=279k, p99.9=500k, выше 800k всего 13 сделок (0.026%) = outlier'ы, не премиум. DEAL_MAX_PPM2=800k адекватен (двойной запас над p99.9) → значение не тронуто, добавлен верификационный комментарий с цифрами+датой.

Новые settings

cian_valuation_min_rub=500_000, cian_valuation_max_rub=500_000_000, estimate_price_trend_dedup_enabled=True.

Тесты

33 новых, ruff clean, 2156 passed (1 pre-existing fail test_search_cache_hit, не связан).

Деплой

Backend Python → recreate tradein-backend. Аддитивно, без миграций, без изменения ценовых порогов (#4 verify-only). Cross-ref аудит audits/Mera_Tradein_Estimate_Audit_0620.md.

После этого PR по «Мере» открытых findings аудита НЕ остаётся (только coverage-левер houses/detail через city_sweep — отдельная операционная задача, не дефект).

Закрывает low-findings аудита «Мера» (последний хвост). 4 покоммитных фикса. ## Коммиты 1. **`50a72f03` Cian valuation sanity bounds** — `_is_price_sane`: `sale_price_rub` вне `[cian_valuation_min_rub=500k, max_rub=500M]` или `low>high`/отрицательные → warning + None (битый API не становится якорем). Заодно починен pre-existing KeyError в тест-моке. 2. **`81ee864d` _is_plausible_deal floor/area/price edge** — `floor<1` (ловит 0/-5) или `>60` → drop; `area<=0`/`price<=0` → drop; None graceful. Защита от битого парсера. Caller `_fetch_deals` прокидывает area/price. 3. **`af1d8827` cross-source dedup price_trend** — `DISTINCT ON (round(area), floor, price, date)` с приоритетом source (avito_imv>yandex) → одна сделка на Avito+Yandex не двоится в тренде. Легитимно разные квартиры (разная цена) не схлопываются. Флаг `estimate_price_trend_dedup_enabled`(True). 4. **`92725c61` DEAL_MAX_PPM2 — verify, НЕ менял** — проверено по prod `deals` (49791 записей): p90=172k, p99=279k, **p99.9=500k**, выше 800k всего **13 сделок (0.026%)** = outlier'ы, не премиум. `DEAL_MAX_PPM2=800k` адекватен (двойной запас над p99.9) → значение не тронуто, добавлен верификационный комментарий с цифрами+датой. ## Новые settings `cian_valuation_min_rub=500_000`, `cian_valuation_max_rub=500_000_000`, `estimate_price_trend_dedup_enabled=True`. ## Тесты 33 новых, ruff clean, **2156 passed** (1 pre-existing fail `test_search_cache_hit`, не связан). ## Деплой Backend Python → recreate tradein-backend. Аддитивно, без миграций, без изменения ценовых порогов (#4 verify-only). Cross-ref аудит `audits/Mera_Tradein_Estimate_Audit_0620.md`. После этого PR по «Мере» открытых findings аудита НЕ остаётся (только coverage-левер houses/detail через city_sweep — отдельная операционная задача, не дефект).
lekss361 added 4 commits 2026-06-21 06:08:07 +00:00
Добавляет _is_price_sane() в cian_valuation.py: если sale_price_rub вне
[cian_valuation_min_rub, cian_valuation_max_rub] (дефолты 500k/500M),
или low_price > high_price, или любой bound отрицательный — результат
не кэшируется и возвращается None (graceful). Защита от garbage-ответов
API (999_999 < min, 9_999_999_999 > max).

Новые settings: cian_valuation_min_rub=500_000, cian_valuation_max_rub=500_000_000.
Тесты: 11 новых тест-кейсов в test_cian_valuation.py. Пофиксен pre-existing
KeyError в test_cache_hit_returns_cached (missing low_price/high_price в mock).
Ужесточает валидацию ДКП-сделок в _is_plausible_deal:
- floor < 1 или floor=0 → drop (floor=-5/0/999 из битого парсера)
- area_m2 задана и <= 0 → drop (нулевая/отрицательная площадь из битого парсера)
- price_rub задана и <= 0 → drop (нерыночная/техническая сделка)
- floor=None → допустимо (graceful, нечем судить)

Все новые параметры area_m2/price_rub keyword-only с дефолтом None →
backward-compatible для существующих вызовов. Caller _fetch_deals обновлён
для передачи area_m2/price_rub из данных сделки.

Тесты: 16 новых тест-кейсов в test_deals_sanitize.py (floor=-5, floor=999,
floor=None ok, area_m2=0 drop, price_rub=-1 drop и пр.).
Один объект на avito_imv + yandex_valuation с разными ext_item_id создаёт
дубликаты в house_placement_history и double-count'ится в помесячной медиане.
Решение: CTE с DISTINCT ON (round(area_m2), floor, price, price_date) с
приоритетом avito_imv через CASE sort. Легитимные разные квартиры (разные
площадь/этаж/цена) не затрагиваются.

За флагом estimate_price_trend_dedup_enabled (дефолт True в settings).
False → точно старое поведение (backward-compat). Graceful: без изменений
в логике min_points и возврата None при недостатке данных.

Тесты: 6 новых тест-кейсов в test_estimator_price_trend_dedup.py (SQL содержит
DISTINCT ON, flag OFF не содержит, Source1 preferred, данные возвращаются корректно).
fix(tradein): Mera-audit fix-4 — верифицировать DEAL_MAX_PPM2 vs prod данных
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
92725c6122
Данные на 2026-06-21 (prod deals table, 49 791 записей):
  p90=172 419 ₽/м², p95=199 583 ₽/м², p99=278 681 ₽/м², p99.9=500 000 ₽/м²
  deals >800k = 13 штук (0.026%) — нерыночные outlier'ы, не легитимный премиум ЕКБ.
  listings p99 (is_active, <800k фильтр) = 392 795 ₽/м².

Вывод: DEAL_MAX_PPM2=800_000 НЕ срезает легитимный ЕКБ-премиум (p99.9 сделок=500k).
Значение NOT изменено — задокументировано в комментарии рядом с константой
для обоснования порога и последующей ревалидации при росте рынка.
lekss361 merged commit 4a2ed52cb7 into main 2026-06-21 06:16:35 +00:00
lekss361 deleted branch fix/estimator-low-findings 2026-06-21 06:16:35 +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#1863
No description provided.