fix(tradein/estimate): не применять хедонику к неправдоподобному году постройки #2624
No reviewers
Labels
No labels
Fable 5 ревью
GG-форсайт
admin
analytics
auth
automation
bug
business
chore
ci
compliance
data
data-moat
docs
duplicate
dx
enhancement
feedback/max
generative
needs-discussion
needs-human
observability
pause-bots
performance
priority/p0
priority/p1
priority/p2
priority/p3
scope/backend
scope/db
scope/devops
scope/frontend
scope/qa
scrapers
security
site-finder
stage/1
stage/2
status/blocked
status/done
status/needs-analysis
status/needs-fix
status/qa
status/ready
status/review
status/wip
tech-debt
tradein
ux
week ревью 1
wontfix
ИРД
вторичка
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: lekss361/gendesign#2624
Loading…
Add table
Reference in a new issue
No description provided.
Delete branch "fix/tradein-implausible-build-year"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Проблема
house_metadata(OSM Overpass +cad_buildings/кадастр, приоритетный источник) иTradeInEstimateInput.year_built(payload, схема допускаетge=1800) отдаютyear_builtбез валидации. На проде встретился 1829 — попал в хедонический year+area фактор (_price_from_inputs, #2002), тот экстраполировал regression fit далеко за пределы обучающей выборки и упёрся в нижний клампestimate_hedonic_factor_min=0.75, порезав выкупную цену на фиксированные −25% без физического смысла.Фикс
Единая точка входа
_sanitize_build_year()(app/services/estimator.py) вызывается вestimate_quality()сразу после того какtarget_yearопределён (из payload либоhouse_meta), ДО cohort-фильтра /_fetch_analogshouse-match scoring / хедонического фактора. Год вне[1917, текущий_год+3]логируется на WARNING (house_id + адрес) и заменяется наNone— трактуется как «неизвестен», а НЕ клампится к границе.Noneуже был честным нейтральным входом для хедоники (year-term = 0, эквивалент year=2000) — теперь мусорный год ведёт себя так же, вместо уверенной −25% поправки.Диапазон: нижняя граница 1917 — массовая многоквартирная застройка в РФ/СССР началась после революции, что раньше почти гарантированно ошибка источника (OSM/кадастр иногда путают год постройки дома с годом основания места на тех же координатах). Верхняя граница текущий+3 — допуск на цели trade-in со строящимся домом (год сдачи по ДДУ известен заранее, но не на десятилетия вперёд).
Механизм хедоники и её коэффициенты НЕ тронуты — только валидация входа.
Вывод по диапазону валидности хедоники (п.3 задачи)
COHORTSвestimator.py(cohort hard-filter для Tier 0 аналогов) вообще не определяет когорту раньше 1955 (khrushchev: 1955-1969— самая ранняя). На проде (read-only,deals— таблица-источник ДКП-сделок вtradeinБД): из 94834 сделок с известным годом лишь 1611 (1.7%) относятся к домам < 1950, а < 1917 — всего 138 (0.15%), причём эти 138 включают заведомый мусор (min year в таблице = 1800). Это означает: даже если исходный held-out-фит на n=2366 сделок (см. комментарий#2002вconfig.py) брал репрезентативную подвыборку этой популяции, эффективная обучающая поддержка для домов старше ~1955 практически отсутствует. Применение фактора к 1829 (или любому году < ~1950) — не интерполяция, а неконтролируемая экстраполяция экспоненты.estimate_hedonic_factor_min=0.75в этой зоне не защита, а маскировка выхода за диапазон под видом уверенной −25% поправки — этот PR меняет это на честное «не знаем» (None) для однозначно неправдоподобных лет; годы 1917-1954 формально проходят guard (не мусор), но остаются известной зоной тонкой обучающей поддержки — это отдельный calibration follow-up, не трогается в этом PR (не входит в scope: «не трогать саму хедонику»).Замеры на проде (read-only, НЕ писал)
house_metadata: 49 строк, 10 с годом, 1 неправдоподобная (year=1829, единственная — совпадает с описанием бага).listings(per-analog год, НЕ участвует в этом фиксе — отдельный вход,target_yearне трогает): 88953 строк, 55325 с годом, 138 неправдоподобных (year < 1917: 1010/1072/1695/1866 по 1 разу; year > 2029: 2030×108, 2031×26 — вероятно легитимные ожидаемые сроки сдачи строящихся домов, порог +3 года не покрывает;listings.year_builtне фидит вtarget_year/хедонику, только в per-analog house-match scoring — вне scope этого PR).trade_in_estimates(сохранённые результаты оценок — реальные прошлые прогоны с этим багом): 608 строк с годом, 6 неправдоподобных (min=1800, max=2026) — соответствует «два прогона аудита + два admin-теста» из описания задачи плюс ещё несколько.Воспроизведённый кейс: цена до/после (год=1829, area=50м², ppm2=100000₽, ratio=0.85)
Год 1960 (валидная хрущёвка, cohort
khrushchev) — фактор 0.7738 (НЕ на клампе), поведение байт-в-байт как до фикса — guard не трогает легитимные старые дома.Тесты
tests/test_estimator_implausible_year.py— 9 тестов, каждый падает на pre-fix коде. Falsification (git stashреализации, тесты оставлены):Сценарии: 1829→None (не режет цену,
test_price_not_cut_after_sanitizing_1829), 1960→1960 (не меняется,test_year_1960_hedonic_unaffected_by_guard), текущий+10→None (test_future_year_beyond_lead_dropped), текущий+3 (граница)→сохраняется (test_near_future_year_within_lead_kept), None→None (test_none_year_unchanged), плюс граничные 1917/1916 (test_boundary_year_*) и WARNING-лог с house_id (test_implausible_low_year_dropped_to_none).Полный
pytest -q --deselect "tests/test_search_api.py::test_search_cache_hit"(известный pre-existing fail, не трогал): 3090 passed, 9 skipped, 1 deselected — ноль новых фейлов.Test plan
ruff check— чистоRefs: не привязан к конкретному issue — найдено сквозным прогоном оценок по проду (Mera-audit 2026-08-02).