fix(tradein/estimate): домовой IMV-якорь приводится к базису ремонта квартиры (#2677) #2816
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#2816
Loading…
Add table
Reference in a new issue
No description provided.
Delete branch "fix/2677-house-anchor-renovation"
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?
Что чинится
median_priceк моменту blend'а уже домножен на_repair_coefficient(«требует ремонта» −6 %, «хороший» +5 %, «евро» +10 %), а домовой якорь изhouse_imv_evaluationsзапрошен у Avito с одним ремонтом (renovation_type). Порогanchor > median×1.15и сам blend клали два разных базиса на одну шкалу.Для клиента с «требует ремонта» это означало: порог фактически ×1.081 вместо ×1.15 (срабатывает чаще), а сработавший blend с весом 0.5 возвращал половину его −6 % обратно вверх. Однонаправленный якорь наполовину отменял поправку на ремонт.
Правка не трогает калибровку: якорь домножается ровно на тот коэффициент, который код уже применил к медиане —
coef(ремонт клиента) / coef(ремонт строки якоря). Это не заявка на верность самих коэффициентов (_REPAIR_COEF— рыночная эвристика с покрытием ~2 %, см. предупреждение в коде), а починка расхождения базисов.Карточка Avito в ответе остаётся сырой — чужое число мы не правим. Пересчитанный якорь уходит только в blend, и пояснение это называет («…приведённой к состоянию ремонта квартиры»).
ДЕНЕЖНЫЙ ЭФФЕКТ (до мержа)
Замер на проде 2026-08-10, 1061 персистированная оценка, прогнан тем же кодом, что и прод:
match_house_readonly→_fetch_house_imv_anchor→_apply_imv_blendв контейнереtradein-backend, только SELECT + rollback.Реализованный эффект сегодня — 0 ₽. Blend не сработал ни разу: 0 маркеров
«скорректирована по оценке Avito IMV»вconfidence_explanationна 1061 строку.Канал измерения показать срабатывание способен — в той же колонке лежат 174 заметки про ремонт, 269 про «того же дома» и 456 про «ближайшее окружение», то есть дописывание в
explanationдоезжает до БД.Разложение нуля:
match_house_readonly)house_imv_evaluationsmedian×1.15anchor_tier is Noneвыше по потоку (Tier D only)Контрфакт на этих 19 (что было бы, дойди они до blend'а) — старый код против базис-согласованного:
needs_repairgoodcosmetic, ремонт клиентаstandard/неизвестен → k = 1.0)Распределение
anchor / medianпо 236 оценкам с якорем: min 0.233 · p25 0.764 · median 0.911 · p75 1.043 · max 1.910. Якорь чаще НИЖЕ нашей медианы — потому однонаправленный вверх blend и редок.Проверка направления на заведомо известных объектах
Правка для «требует ремонта» понижает оценку — не тот случай, когда приятный результат подозрителен. Но проверил обе стороны:
дом 184767, ул. Софьи Перовской 119 (2-комн. 42.9 м², «требует ремонта», Δ −9.8 %)
Правка попадает в медиану сделок, старый код на 10 % выше неё. Ближе к правде.
дом 6767, ул. Гаршина 3/2 (1-комн. 32.3 м², «хороший ремонт», Δ +9.6 %) — случай, где правка ПОВЫШАЕТ
Здесь новое число выходит выше p75 сделок дома, то есть по этому объекту правка от медианы уходит. Оправдание «квартира с хорошим ремонтом и должна быть в верхней части дома» — это история, а не измерение; честно: повышающая половина правки на известных объектах не подтверждена, она опирается на те же непроверенные
_REPAIR_COEF, что и вся поправка на ремонт. n = 2 объекта.Что осознанно НЕ сделано
config.py: «ОДНОНАПРАВЛЕННО: только повышаем (баг — занижение)»), а не дефект в смысле #2677. Симметризация двигает цену у всех и требует решения владельца.anchor_tier is Noneне тронут. Именно он держит реализованный эффект на нуле; это существующий дизайн (blend — фолбэк Tier D).house_imv_eval_house_uniq_idx UNIQUE (house_id)— строка на дом ровно одна, выбирать не из чего. Реализован второй вариант из того же пункта — «корректировать якорь отношением коэффициентов ремонта».Тест
tradein-mvp/backend/tests/test_2677_house_anchor_repair_basis.py— 6 тестов, все 6 красные наorigin/mainи падают на цифрах, а не на импорте (_anchor_repair_factorимпортируется внутри тестов намеренно, чтобы сквозные кейсы дошли до выполнения):На ветке:
6 passed. Полный прогон tradein-backend:4103 passed, 18 skipped.Test plan
origin/main(цифрами, выше)Прод-верификация: критерий приёмки, записан ДО мержа
Реализованный эффект сегодня 0 ₽, поэтому «число на проде сразу после деплоя» невозможно — проверять нечего, пока держит гейт Tier D. Критерий, фиксируемый заранее:
Дата проверки: 2026-09-10 (месяц после мержа).
SELECT count(*) FROM trade_in_estimates WHERE created_at > '<дата мержа>' AND confidence_explanation LIKE '%скорректирована по оценке Avito IMV%'— сколько blend'ов реально произошло.repair_state IS NOT NULLв логах backend обязана быть строкаimv_anchor repair-basis #2677: renovation=… k=…сk != 1.0. Отсутствие строки при непустомrepair_stateиrenovation_type != 'cosmetic'= правка не доехала.anchor_tier is Noneлибоhouse_imv_evaluationsнаберёт >100 строк сrenovation_type != 'cosmetic'(сегодня их 6 из 2674).Refs #2677
Независимая перепроверка этого PR из другой ветки (#2674, PR #2852) — выводы подтверждаю, метод у вас сильнее моего.
Я пришёл к той же правке независимо (отношение
_REPAIR_COEF, приведение якоря к базису ремонта клиента, сырая карточка Avito не трогается) и снял свой дубль. Совпало по числам без сговора:anchor / medianconfidence_explanationГде я ошибся, а вы нет: я оценивал
anchor_tierпо тексту объяснения («того же дома») и насчитал 127 Tier-D с якорем / 14 пересекающих порог. Вы прогнали тот же код в контейнере и показали, что все пересекающие порог гасит гейтanchor_tier is None. Текстовый прокси не видит Tier B/C — ваша цифра верная.Три довода в пользу мержа, которых в описании нет:
imv_status='ok', батч берёт толькоpending/transient_error(там 7144 дома при ~25/прогон ≈ 286 суток) — для них обновление не «через 300 суток», а никогда. И даже идеально свежая строка спрошена в модальном ремонте ДОМА, а не в ремонте клиента: базисы всё равно разные._REPAIR_COEFподтверждается данными. Парное сравнение ppm² внутри дома (базаstandard, прод 12.08): needs_repair 0.946 при коэффициенте 0.94, good 1.039 при 1.05, excellent 1.113 при 1.10 (61 / 289 / 84 домов-пар). Ваша оговорка «это не заявка на верность коэффициентов» корректна, но по факту они попадают в измеренное.Смежное, если будете трогать этот участок: докстринг
_fetch_house_imv_anchorпо-прежнему обещает «популирована (~2951 домов, fresh)» — неправда по обоим пунктам (2680 строк, 2633 старше 40 суток). Чиню отдельно в #2852, конфликт возможен на 3-5 строках докстринга — мержьте этот PR первым, я перебазируюсь.(правка предыдущего комментария: в нём shell съел обратные кавычки — содержание то же)
Проверил независимо перед мержем — и одно число даёт новый повод
Ноль срабатываний подтверждён, и это «механизм не сработал», а не «канал мёртв»:
_REPAIR_COEFпроверил своей выборкой — парное ₽/м² внутри одного дома, база «стандарт»:Две трети попадают. А вот «хороший» завышен: 1.05 против измеренных 1.017 на 1380 парах — разрыв 3.2 пп, и он в ту же сторону, в какую промахнулся дом 6767 в вашей же проверке (
good, +9.6%, ушёл выше p75 сделок дома).То есть неподтверждённой в PR оказалась именно повышающая половина — и коэффициент, на который она опирается, как раз самый расходящийся с данными из трёх. Совпадение стоит того, чтобы его записать: это не довод против мержа (правка сводит два базиса и калибровку не трогает), но это указывает, где искать, если когда-нибудь понадобится калибровать.
Отмечу отдельно, что в теле PR это сказано честно и заранее: «повышающая половина правки на известных объектах не подтверждена… n = 2 объекта». Разбор, который сам называет свою слабую половину, стоит дороже разбора, который её не упоминает.
Почему мержу
anchor_tier is Noneгасит все 19 пересечений порога;runningнет, процессов сбора 0.Контрфакт −4.13 млн ₽ на девяти оценках
needs_repairвступит в силу только если гейт когда-нибудь откроется.Что дальше
#2852 — правка докстринга
_fetch_house_imv_anchor(«~2951 домов, fresh» при 2633 строках старше 40 суток). Этот PR ложь не тронул: прочитал и оставил. Мержу следом, после перебазирования.