feat(tradein/yandex): is_pro_seller из агентского блока + бэкфилл (#3063 п.2) #3100
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#3100
Loading…
Add table
Reference in a new issue
No description provided.
Delete branch "fix/3063-pro-seller-derive"
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?
Пункт 2 чеклиста #3063 (п.1 закрыт PR #3067, п.3 — отдельное решение, не здесь).
Что
Detail-обогащение Яндекса извлекало
agency_name, ноis_pro_sellerне выводило: 4 469 активных листингов с известным агентством стояли с NULL (замер владельца 23-24.08). Признак идёт в оценщик.Как
save_detail_enrichment:is_pro_seller = COALESCE(:is_pro_seller, is_pro_seller), параметрTrueпри известномagency_name, иначе NULL — вывод только в одну сторону: отсутствие агентского блока не доказывает «частник» (п.3 задачи), в ту сторону не пишем ничего.271_listings_pro_seller_from_agency.sql: одноразовый бэкфилл существующих строк («починка разбора ≠ починка строк» — UPDATE прошлого задним числом не перезапишет). Скоуп — все источники: правило «есть имя агентства ⇒ профи» источник-независимо. Идемпотентна (IS DISTINCT FROM TRUE). Гейт lock_timeout пройден.Проверки
test_3063_pro_seller_from_agency.py: агентский блок →is_pro_seller=Trueв параметрах UPDATE (красный на origin/main по значению — ключа нет), без блока → NULL (инвариант, COALESCE сохранит известное), форма миграции (идемпотентность, is_homeowner не тронут)test_yandex_detail*: 52 passedПриёмка после деплоя:
SELECT count(*) FROM listings WHERE agency_name IS NOT NULL AND is_pro_seller IS NULL→ ~0 (было 4 469 только по яндексу); дальше признак поддерживается detail-обогащением.🤖 Generated with Claude Code
Ревью: корректно, мержу. Плюс замер, которого в описании не хватало
Что проверял прицельно и что оказалось
Зеркальный гард
IS DISTINCT FROM. Главный риск для этого класса правок: поле добавили вSET, но не в парный unchanged-гард — тогда обновление, меняющее ТОЛЬКО его, молча подавляется (ровно то, что чинилось в #2992/#3067). Проверил: вproviders/yandex/detail.pyтакого гарда нет вовсе —save_detail_enrichmentэто обычный UPDATE, а не upsert изscraper_kit/base.py. Риск неприменим.Односторонний вывод.
COALESCE(CAST(:is_pro_seller AS boolean), is_pro_seller)+True if e.agency_name else None— отсутствие агентского блока не пишет ничего, а неFALSE. Это правильная граница: «нет блока» и «частник» — разные утверждения, и второе из первого не следует.CAST(... AS boolean), а не::boolean— по правилу psycopg v3.Замер миграции 271 на живой БД (Poincare, 26.08)
Предикат
is_pro_seller IS DISTINCT FROM TRUEшире, чем описание («у них NULL»): он захватывает и явныеFALSE, то есть в принципе способен перевернуть уже проставленное отрицание. Проверил фактический скоуп:Строк с заполненным агентством и явным
FALSEноль, так что расхождение предиката с комментарием сейчас безвредно — миграция бьёт ровно по NULL. Оставляю как наблюдение, а не как замечание: правило «есть имя агентства ⇒ продаёт профи» источник-независимо, и еслиFALSEпри непустом агентстве когда-нибудь появится, переворот будет скорее верным, чем нет.В теле PR цифра 4 469 — это активные; всего затронется 8 275 строк. Стоит иметь в виду при проверке после деплоя.
Идемпотентность заявлена верно: после прогона все подходящие строки становятся
TRUE, повтор обновит 0.