fix(tradein/dkp): «ФАКТИЧЕСКИЕ СДЕЛКИ» называют свой возраст, а не окно поиска #2847
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#2847
Loading…
Add table
Reference in a new issue
No description provided.
Delete branch "fix/dkp-corridor-as-of"
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?
Что было
Плитка
ДКП · РОСРЕЕСТР (ФАКТИЧЕСКИЕ СДЕЛКИ)показывала медиану, диапазон и число сделок — и молчала о том, когда эти сделки случились. Молчание было осознанным и на тот момент правильным: единственным кандидатом на подпись былоperiod_months— ОКНО ПОИСКА, и «за 24 месяца» при январских сделках было бы хуже молчания (комментарий автора,mappers.ts:1150). Но у молчания своя цена: слово «ФАКТИЧЕСКИЕ» читается как «недавние».Перемеренные числа (прод, 2026-08-12, только чтение)
dealssource='rosreestr'max(deal_date)deal_dateЧисла из тикета подтверждены полностью, ни одно не опровергнуто. Квартальность не назначена, а проверена по данным: месяцы — ровно начала кварталов, день — всегда первый. Совпадает с тем, что бэкенд уже знал (
_date_precision_for_source→"quarter", #1995), но фронт этот признак игнорировал.Распределение возраста ВЫБОРКИ по реальным оценкам
Реплей корид+орной выборки (улица + город + rooms + area ±15% + окно 12 мес + ppm²-банды + city-wide widen) для 881 реальной оценки за 90 суток:
Среди оценок, у которых коридор ЕСТЬ (716): 91.3% упираются в I кв. 2026, 8.7% — в IV кв. 2025. То есть свежее общих 223 дней не бывает НИ У КОГО, а у 8.7% — на целый квартал хуже. Общий максимум таблицы на витрине был бы враньём в пользу свежести именно для них.
Форма подписи и почему такая
«по I кв. 2026», хвостом в существующей ppm-строке:
deal_date— метка пачки, а не дата регистрации. «223 дня назад» было бы ложной точностью В СТОРОНУ СОСТАРИВАНИЯ: сделка из пачки «2026-01-01» могла случиться и 31 марта, то есть быть на 90 дней свежее, чем обещает счётчик.ResultCard.ppmуже носит хвост «· N сделок» — третий факт про тот же набор встаёт в тот же ряд. Подпись не стала ни длиннее строкой, ни мутнее: одно словосочетание из четырёх слов.Что сделано
DkpCorridor.latest_deal_date—max(deal_date)по отобранным сделкам. При city-wide widen (#oblast-D) дата переезжает вместе с числами: street-выборка бывает свежее, но на экране после widen'а стоят city-числа.ppm²не входит в границы коридора — не входит и в его возраст (иначе выброшенная сделка омолаживала бы подпись, не участвуя ни в одном числе под ней).resolveDealTierтянет СВОЙ as-of. Это три разные выборки (street-deals не режет ppm²-выбросы, коридор режет и умеет расширяться до города), и приоритетна как раз street-deals — подпись из коридора описывала бы чужие числа.count=0→ppmостаётся «—», подписи нет. Под прочерком «по I кв. 2026» выглядело бы как скрытые данные.HeroSummaryпечатал{count} ДКП за {period_months} мес— ровно ту подмену окна возрастом, от которой v2 отказался. Заменено на as-of.lib/rosreestr.tsдля web,trade_in_pdf.deals_as_of_labelдля PDF), обе стороны закреплены тестом на одних и тех же 9 живых прод-датах — чтобы не получилось двух разных обещаний на одних числах.Красный прогон
Backend (тот же файл теста против
origin/mainв отдельном worktree):Единственный зелёный на main —
test_no_deals_means_no_corridor_and_nothing_to_date: это поведение там уже правильное, тест держит его от регресса.Frontend (vitest, тот же файл против
origin/main):Падения — по существу (возраст не доезжает до строки плитки), а не по ожиданию, списанному с собственной настройки: три зелёных на main — это гейты молчания (пустой случай, оценка без поля, отсутствие «N мес» в подписи), они и должны быть зелёными с обеих сторон.
Полный прогон на ветке: backend 4300 passed, 21 skipped; frontend 26 passed;
tsc --noEmitчисто;pre-commit run --from-ref origin/main --to-ref HEAD— все хуки Passed.Скриншот
Войти в живую МЕРУ не удалось — Caddy basic_auth проходит, но app-логин (
tradein_users) отбивает все учётки из keychain (user1,admin), страница остаётся на/trade-in/login. Скажу прямо: живого прод-скриншота нет.Вместо него — реальный рендер той же плитки (
ResultPanel+mapResultPanel, headless Chrome,deviceScaleFactor: 2) на живых прод-числах конкретной оценки3cb4571b-…(ЕКБ, ул. Ленина, 2к, 55 м²): n=14, P10/медиана/P90 = 78 398 / 122 928 / 139 577,max(deal_date)=2026-01-01. Два дев-сервера, один наorigin/main, второй на ветке, одна и та же временная preview-страница (в коммит не входит):Это рендер компонента, а не живая страница прода — не выдаю одно за другое.
Вопрос владельцу (числом, как просили)
Понижать ли доверие к коридору при таком возрасте — отдельное решение, в этом PR не тронуто. Число для решения: коридор сегодня ADVISORY (не клампит), и у 100% оценок с коридором опорные сделки старше 223 дней, у 8.7% — старше 315. Порога свежести у коридора нет вообще.
Чего НЕ трогал
estimator.pyв части квартального индекса иhouse_imv_backfill.HistoryView«Период: 12 мес» — это тоже окно поиска, но секция прямо под ним печатает реальную дату КАЖДОЙ сделки (fmtMonthYear(pr.deal_date)), так что там окно не выдаёт себя за возраст. Оставил как есть, называю явно.Перед мержем
Проверить
SELECT id, source FROM scrape_runs WHERE status='running'— идут свипы Яндекса, деплой ждёт их всего 5 минут.Refs #2846
Плитка ДКП несла count/median/диапазон и молчала о том, когда эти сделки случились. Молчание было осознанным: единственным кандидатом на подпись было period_months — ОКНО ПОИСКА, и «за 24 месяца» при январских сделках было бы хуже молчания (см. комментарий автора в mappers.ts). Но честное поле есть: max(deal_date) по ОТОБРАННЫМ сделкам — его и добавляем. Замер прода 2026-08-12 (только чтение): deals source='rosreestr': 96 974 строки, max(deal_date)=2026-01-01, 9 различных дат, day-of-month=1 у 100%, месяцы ровно {01,04,07,10} → deal_date это метка КВАРТАЛЬНОЙ пачки, а не дата регистрации. Реплей выборок 881 реальной оценки за 90 суток: I кв. 2026 — 654 (74.2%) · нет коридора — 165 (18.7%) · IV кв. 2025 — 62 (7.0%) → у 8.7% выборок с коридором свежайшая сделка на КВАРТАЛ старше общего максимума таблицы: общий max на витрине был бы враньём в их пользу. Отсюда форма подписи «по I кв. 2026», а не «223 дня назад»: возраст считается от НАЧАЛА квартальной пачки, тогда как сделка внутри неё могла быть и 31 марта — день был бы ложной точностью. «по», а не «за»: выборка накрывает несколько кварталов, подпись называет верхнюю границу свежести. - DkpCorridor.latest_deal_date — max по выборке, которая реально дала числа (включая city-wide widen: переезжают и числа, и дата вместе). - Строка без ppm² не входит в границы коридора — не входит и в его возраст. - Плитка v2: хвост «· по I кв. 2026» в существующей ppm-строке, отдельной строки не заводим. Каждая из трёх ветвей resolveDealTier тянет СВОЙ as-of — это три разные выборки, подпись описывает ту, чьи числа на экране. - count=0 → «—» без подписи: датировать нечего. - v1 HeroSummary печатал «{count} ДКП за {period_months} мес» — ту самую подмену окна возрастом; заменено на as-of. - PDF §03 печатал «Период сделок: 08.2025 – 08.2026», где правый конец = сегодня; теперь «Сделки: по I кв. 2026» по показанным сделкам. Роль коридора не менялась: он остаётся ADVISORY и не клампит оценку. Поправка внешним ориентиром не тронута. Refs #2846