fix(tradein/estimator): восстановить и канонизировать корпус дома в дедупе (регрессия #2429) #2432
No reviewers
Labels
No labels
admin
analytics
auth
automation
bug
business
chore
ci
compliance
data
data-moat
docs
duplicate
dx
enhancement
Fable 5 ревью
feedback/max
generative
GG-форсайт
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#2432
Loading…
Add table
Reference in a new issue
No description provided.
Delete branch "fix/tradein-dedup-corpus-format-normalize"
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?
Summary
Фикс регрессии, найденной собственной live-перезамерой сразу после мержа/деплоя PR #2429 (#2431).
Проблема: #2429 починил avito metro-glue баг, но regex-фикс (
(?!\w)lookahead) отбраковывал буквенный суффикс корпуса, НЕ восстанавливая отброшенную цифру — итогhouse="65"для avito/cian glued-нотации («65к4») vshouse="65/4"для domklik slash-нотации.guard_houseвидел два разных непустых значения и блокировал легитимное кросс-source слияние. На сегодняшних данных это заблокировало 226 групп — больше, чем #2429 исправил (128).Fix: новая
_normalize_house_no()канонизирует ЛЮБУЮ нотацию корпуса («65к4» глухо, «65 к4» с пробелом, «65, корпус 4» словом, «65/4» слэш) к единому «N/M» перед сравнением вguard_house. Литер (буква без цифры за ней, «24г») НЕ трогается — семантически другое (буква корпуса дома, а не номер корпуса).Regex расширен новой альтернативой «корпус-словоформа» (пробуется ДО bare-letter альтернативы), чтобы захватить сырую корпус-подстроку ЦЕЛИКОМ («65к4», не только «65») — реформатирование в канонический вид делает отдельная функция, не сам regex (читаемее/тестируемее тремя лексическими словоформами + слэш).
Затронуто 2 return-пути
_parse_street_house— основной regex-путь и bare-street-ветка (#2291), которую дизайн явно не называл, но воркер корректно нашёл и тоже нормализовал.Live-верификация (реальные прод-данные, ДО пуша — не после)
Та же методика, что и для #2429/#2291:
ssh gendesign→docker exec tradein-backend, монки-патч regex in-memory (без записи в файлы/БД), сравнение survivor-групп на ОДНОМ И ТОМ ЖЕ сегодняшнем датасете.ssh+psql) — «Викулова, 37к1» (cian) vs «Викулова, 37/1» (domklik) → оба «37/1» ✓; «Щербакова, 31к2Чкаловская21-30 мин.Ботаническая» (avito, corpus+metro-glue вместе) → «31/2» ✓, #2429 не регрессирован; «65, кв. 12» → «65» (не проглочен «к» из «кв.») ✓; «24г» liter не тронут ✓.ул. Щербакова, 77к1Ботаническаяavito vsулица Щербакова, 77domklik/cian/yandex, реальная мульти-корпусная застройка). Это восстановление до-#2429 поведения (сам #2429 случайно склеивал их через усечение), не новая регрессия — архитектурный вопрос «голый номер = wildcard для любого корпуса?» вне scope, см. #2431.Adversarial review
deep-code-reviewer: APPROVE (severity=minor). Ревьюер честно пометил
liveDataVerified=falseдля агрегатных чисел (прод-БД для него была заблокирована песочницей) — но независимо перепроверил САМУ ЛОГИКУ regex/нормализации вручную на ~22 примерах, включая non-regression на #2423. Агрегатные live-числа я (main session) перепроверил лично через прямой SSH-доступ.Test plan
test_estimator_dedup_cross_source_2087.py— 77/77, прогнал личноtest_parse_street_house_glued_corpus_letter_partial_capture, пиновал баг «10к1»→«10» как норму) переписан вtest_parse_street_house_glued_corpus_canonicalizes_to_slash(→«10/1»)Refs #2431, #2429, #2423, #2291
Регрессия post-#2429: _DEDUP_HOUSE_NO_RE захватывал целиком только слэш-нотацию корпуса ("65/4"), а глухую ("65к4") и с пробелом ("65 к4") усекал до голой цифры ("65") — #2429 лишь остановил ложный захват буквы через `(?!\w)`, но не восстановил отброшенную цифру корпуса. guard_house в union() затем сравнивал "65" (avito/cian) != "65/4" (domklik) для ОДНОГО и того же физического дома и блокировал кросс-source слияние — 226 дублирующих групп в проде. Фикс: - _DEDUP_HOUSE_NO_RE: добавлена альтернатива для корпус-словоформы (к/корп/корпус, глухо или с пробелом, ТРЕБУЕТ цифру корпуса сразу за собой) — пробуется ДО bare-letter альтернативы, чтобы "65к4" захватывался целиком, а не усекался. - Новая _normalize_house_no(): канонизирует ЛЮБУЮ нотацию корпуса ("65к4", "65 к4", "65, корпус 4", "65/4") к единому "65/4". Литер без цифры за буквой ("24г") — другой физический суффикс, НЕ трогается. Идемпотентна. - Вызывается в единой точке — оба return _parse_street_house (включая bare-street ветку #2291) — так что guard_house всегда видит канонические значения независимо от source-формата. Тесты: rewrite test_parse_street_house_glued_corpus_letter_partial_capture (пиновал баг "10к1" -> "10" как норму) в test_parse_street_house_glued_corpus_canonicalizes_to_slash ("10к1"/"10 к1" -> "10/1"); обновлены 3 существующих кейса в test_parse_street_house_extended_formats и stale-комментарии в 2 e2e тестах; +14 новых тестов (5 unit normalize_house_no, parametrized cross-notation agreement, e2e slash-vs-glued merge в обе стороны порядка union(), adversarial liter-vs-corpus non-merge, guard всё ещё блокирует разные номера корпуса, apartment-marker "кв." не путается с "корп.", dangling "65к" без цифры = литер). Live-верификация на проде (read-only, house_id_fk+floor+area+price кластеры, count(DISTINCT source)>1): 4841 кандидат-кластера / 11756 строк. OLD-логика (текущий прод, post-#2429) 5441 survivor-строк, NEW (этот фикс) 5261 — 186 кластеров изменились, 185 полностью схлопнулись в 1 представителя (192-й частично: 4->3). Zero true guard-violations — автоматическая проверка по всем 4841 кластерам подтвердила, что ни один мёрдж не объединил лоты с ГЕНУИННО разными (после нормализации) номерами корпуса без авторитетного совпадения кадастрового номера. 6 кластеров показали ПРОТИВОПОЛОЖНЫЙ эффект (было 1 survivor, стало 2) — во всех 6 один источник (обычно domklik) даёт "голый" номер дома без корпуса ("77"), а другой — конкретный корпус ("77к1"/"77к4"); раньше баг СЛУЧАЙНО делал их равными через усечение, сейчас guard корректно их не сливает (та же консервативная семантика "genuinely-different -> не сливаем", что уже применяется к "65/4" vs "65/5"); отдельный, architecturally-orthogonal вопрос (стоит ли считать голый номер wildcard'ом для любого корпуса) — вне scope этого фикса, не блокирует.