fix(tradein/estimate): нет индекса у аналогов — нет квартальной поправки (#2583 C3) #2836
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#2836
Loading…
Add table
Reference in a new issue
No description provided.
Delete branch "fix/2583-c3-quarter-index-fallback"
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?
Что не так
estimator.py(было :2759, сейчас :3201):price_indexнормирован на медиану Екатеринбурга (99a_quarter_price_index.sql), так что1.0— это не «нейтраль», а «аналоги стоят как в ЕКБ». Утверждение делается ровно там, где про аналоги неизвестно ничего.factor = target_index / avg_analog_indexсхлопывается вfactor = target_qi, а индексы области — 0.28–0.82. Пользователю это уходит подписью «Учтена локация квартала (индекс цен квартала ×0.60)», то есть догадка читается как методика.Замер: сегодня эффект НУЛЕВОЙ
weight_total = 0(фолбэк 1.0)Реконструкция по persisted-аналогам всех 15 оценок: у каждой был минимум 2 аналога с квартальным индексом. Фолбэк не срабатывал на проде ни разу. Ни одна существующая оценка после этой правки не изменится.
Область до блока не доходит вовсе:
target_quarterрезолвится либо изdadata.house_cadnum(9 из 701 оценок, все ЕКБ), либо из кадастра первого аналога — а кадастр вне ЕКБ отсутствует (Тагил 1/1063, Каменск 0/823, Серов 0/381, Первоуральск 7/683, Пышма 10/461), причём все 18 «областных» кадастров на самом деле66:41:(ЕКБ), и на них Guard-2 даётsame_quarter_ratio = 1.0→ skip.Это задача «на вырост», не срочная. Чиню потому, что мина взведена и правка стоит 21 строку.
Насколько взведена
92 дома вне ЕКБ уже имеют кадастр + квартал в индексе с
n_deals >= 10, и рядом с ними ноль проиндексированных аналогов. Как только кадастр цели дойдёт до эстиматора (это и есть #2576), текущий код навяжет им:В рублях, типовой лот 45 м² по медианам городов:
Что вместо 1.0 и почему
Пропуск поправки. Из трёх вариантов он единственный, который не выдумывает данные, — и он же выигрывает по числам.
Ключевое:
factor = target_qi / avg, поэтому «пропустить поправку» алгебраически тождественно «взять индекс своего квартала как индекс аналогов» (avg := target_qi→factor = 1.0). То есть вариант «индекс своего города по неполным данным» тут не конкурент, а тот же самый ответ, записанный иначе.Насколько
target_qiлучше1.0в роли неизвестногоavg— замер на 400 случайных лотах с кадастром (полная формула из эстиматора, аналоги в 1 км):1.0(как сейчас)target_qi(= пропуск)Внутри ЕКБ, где нормировка играет в пользу
1.0, пропуск уже на 28% точнее по медиане и на 35% по хвосту. Вне ЕКБ разрыв кратный.Вариант «индекс региона» отброшен: медианы районов области 0.29–0.82 при разбросе внутри района до 4×, региональное среднее не ближе к истине, чем свой же квартал.
Проверка направления на реальных сделках
Правка поднимает цену — значит, обязана быть проверена на известных объектах, а не принята за «стало больше». Медианы ₽/м² реальных ДКП Росреестра за 12 мес против того, что даёт каждый вариант:
Ошибка падает с 32–40% до 0–14% во всех шести городах. Остаточные +8…+14% — смещение коэффициента выкупа (#2617/#647), к C3 отношения не имеет.
Тест: красный прогон на старом коде
Естиматор откачен к
origin/main, тесты новые:6 000 000 → 3 600 000, ровно −40%. С фиксом:
38 passed.Третий тест (
test_c3_one_indexed_analog_is_enough_to_correct) держит обратную сторону: как только уровень аналогов известен, поправка работает как раньше — фикс не глушит механизм, а требует под него данные.Два прежних теста опирались на сам дефект —
test_quarter_index_correction_appliedиtest_bimodal_guard_allows_high_index_large_nзадавали «уровень аналогов = 1.0» тем, что не давали аналогам кадастра, и комментарий это фиксировал: «делаем аналоги БЕЗ кадастрового номера → avg_analog_index = 1.0». Переведены на явную картуanalog_indexes— утверждения о цене сохранены дословно, изменился только способ задать вход.Опровергнутые предпосылки исходной задачи
computed_at2026-08-05), блок отработал 6 раз с 5 по 12 августа, последний — сегодня 13:00 UTC.1.0(basis='city_fallback'в99a) и district-фолбэк имеютn_deals3–4, а эстиматор требуетn_deals >= 10. На проде:quarter1612 строк / 1222 видимы,district281 / 0,city_fallback1 / 0. До эстиматора доходят только строкиbasis='quarter'. MV и FDW трогать не нужно — и не нужно рисковать вторым потребителем (site_finder в main-БД).factorэто отношение,city_medianсокращается. Нормировка протекает ровно в одной точке — когда одну сторону отношения подменяют литералом. После фикса зависимость от медианы ЕКБ исчезает из ценового тракта целиком.weight_total, не даёт резолвиться иtarget_quarter. Аудит увидел вторую половину и не заметил первую.Найдено попутно (не чиню здесь)
target_quarterберётся из кадастра первого попавшегося аналога, когдаdadata.house_cadnumпуст — а он пуст в 15 из 15 применений. Спасает только то, чтоlistings_cleanотсортированrelevance_score, distance_m, то есть это ближайший аналог. Но «индекс цены квартала цели» на деле является индексом квартала соседа. Это отдельный дефект того же блока, шире C3.66:41:(ЕКБ) — Ревда и Первоуральск с екатеринбургскими кадастрами. Порча данных на стороне лоадера, не эстиматора.Test plan
pytest tests/test_estimator_quarter_index.py→ 38 passedpytest -k "estimator or estimate"→ 717 passed, 2 skippedorigin/main-эстиматоре → 2 failedRefs #2583