Витрина «сделки против объявлений» перестаёт пустовать: снят предикат по синтетической комнатности сделок #3461
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
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: lekss361/gendesign#3461
Loading…
Add table
Reference in a new issue
No description provided.
Delete branch "fix/3451-tvf-deals-rooms"
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?
Closes #3451.
TVF
street_sales_vs_listings()фильтровала сделки поd.rooms, аdeals.roomsу источникаrosreestr— не комнатность, а бакет площади: импортёр пишет тудаCASE WHEN area < 30 THEN 0 ... ELSE 4 END, и на проде 321 559 строк из 321 560 удовлетворяютrooms == area_bucket(area_m2). Предикат работал вторым фильтром по площади поверх полосы ±15 %, которую функция считает сама.Та же патология снята в #3256 (PR #3445) на четырёх сделочных площадках эстиматора — здесь последний оставшийся потребитель.
Ключ асимметричный
d.rooms = p_rooms— снят (синтетика из площади);l.rooms = p_rooms— оставлен: у объявлений комнатность настоящая, это единственный признак ассортимента на листинговой стороне. «Починить симметрично» = потерять его.Прод-замер (2026-09-12, только чтение)
1160 реальных клиентских запросов из
trade_in_estimates, улица извлеклась у 954 (у 206 — известная H1 «адрес вне словаря», к этой правке отношения не имеет). Считалось тем же путём, что у продукта: street/city резолвятсяextract_street_name()/_resolve_target_city()./sales-vs-listingsВпервые непустых — 94 клиента. В issue стоял прогноз «те же 180»: та оценка бралась по коридору эстиматора (другие period/tolerance и другой street-pattern), в PR кладётся измеренное, а не предсказанное.
Почему безопасно
test_only_the_rooms_predicate_differs_from_211); сигнатура иRETURNS TABLEпобайтово те же — иначеCREATE OR REPLACEсоздал бы вторую перегрузку вместо замены (грабли #2627).deal_rooms: intвSalesListingPairостаётся обязательным: веткаELSEв CASE импортёра ловит и NULL-площадь, прод подтверждает 0 NULL вdeals.roomsприsource='rosreestr'. Контракт API не меняется.EXPLAIN'ом на боевой БД (только чтение) — планировщик принимает.Фальсификация тестов (оба направления)
AND d.rooms = p_rooms→ красныеtest_deals_side_has_no_rooms_predicate,test_only_the_rooms_predicate_differs_from_211;AND l.rooms = p_rooms(«починил симметрично») → красныйtest_listings_side_keeps_rooms_predicate.Полный сьют
tradein-mvp/backend: 5946 passed, 35 skipped. ruff clean.Якорь в
deploy/import-rosreestr.shобновлён: потребителей, фильтрующих поd.rooms, больше нет.Приёмка на проде после деплоя
Повторить замер тем же скриптом и увидеть долю непустых ответов ≈94 % (сейчас 84.4 %).
По замечаниям ревью — замерено показываемое число, не только объём
Замечание было точное: PR мерил объём (доля непустых, счётчики строк), а пользователю видно
median_discount_pct. Механизм сдвига, который назвало ревью, тоже конкретный: сделочная сторона теперь занимает всю полосу ±15 %, листинговая по-прежнему сужена реальной комнатностью,discount_pctсчитается по абсолютным ценам → ожидался систематический сдвиг «продали дороже ask» и перескок черезSANE_DISCOUNT_MAX_PCT = 20.Проверил. Сдвиг не подтвердился, направление оказалось противоположным предсказанному. Те же 1160 запросов / 369 различных ключей, гейт эндпойнта повторён один в один (
MIN_PAIRS=10,MIN_DISTINCT_LISTINGS=2, диапазон −35…+20), только чтение:Медиана сдвинулась на 0.25 п.п. в сторону скидки, а не в плюс. Обратный эффект, которого справедливо опасалось ревью, существует, но мал и назван числом: «вне диапазона» +6, и ровно один ключ перешёл из «показано» в «погашено» —
Ясная, 65 м², 2к: было −3.23 % на 13 парах, стало «вне диапазона» на 21 паре / 3 объявлениях. Обратного перехода (показано → погашено) больше нигде нет; в другую сторону выиграли 38 клиентов.Прецедент из шапки 211 (−18.18 % → −17.11 %) соблюдён: число до/после названо.
Остальные два замечания — поправлены (коммит
de2d67c7)import-rosreestr.sh:79. Было «потребителей, фильтрующих поd.rooms, больше нет» — верно только для предикатов равенства. Дописано:app/tasks/asking_to_sold_ratio.py:148,152по-прежнему ключуется этим бакетом и намеренно зеркалит ту же синтетику на листинговой стороне (#2620); смена CASE на реальную комнатность вернёт именно #2620, а не только вопрос предикатов.trade_in.py— убрано обещание «listing с такими же rooms»: после правки это верно для пары запрос↔объявление, но не сделка↔объявление; сказано прямо, чтоdeal_roomsможет не совпасть с запрошеннымrooms.Полный сьют после правок: 5946 passed, 35 skipped, ruff чист.
На вопрос про
LIMITПотолка нет, знаю: TVF отдаёт все пары, эндпойнт сериализует их целиком, карточка рендерит 10. Прод сейчас ~71 → ~93 пары на ответ. Существовало до правки, выросло на 30 %. В этом PR не трогаю.