Адреса МЕРЫ: «Сулимова, 3/2» и «Сулимова, 32» больше не один дом — дробь в номере не теряется при сопоставлении с ГАР и схлопывании дублей #3578
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#3578
Loading…
Add table
Reference in a new issue
No description provided.
Delete branch "fix/canon-addr-slash"
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?
Мержить после #3558. Ветка стоит на голове
fix/houses-dedup(e3616e36): миграция 322 берёт тело функции из 311 и меняет в нём один шаг. Пока #3558 не смержен, в диффе этого PR видны и его коммиты; после его мержа останется один коммит3d9eaa27. Если #3558 перепишут, эту ветку надо перебазировать. Если смержить этот PR раньше, 311 откатит функцию к версии без дроби.#2189: канон-ключ адреса терял дробь в номере дома
Что было. Последний шаг
tradein_canon_addrоставлял только[а-я0-9]. Поэтому «Сулимова, 3/2» и «Сулимова, 32» давали один ключсулимова32, как и «10/7» и «107», «4/1» и «41». В ГАР это разные дома с разнымиhouse_guid: «ул. Сулимова, 3/2» — 414 квартир, «ул. Сулимова, 32» — 120. У матчера ГАР↔houses (_MATCH_SQL) с фильтром по городу из общего ключа берётся строка с max(flat_count). Отсюда «Сулимова 32 → 414 кв.» из ревизии DaData 02.07.Что сделано.
322_canon_keep_slash_in_house_number.sql. Тело функции из 311 без изменений, кроме последнего шага: S6 оставляет[а-я0-9/], новый S7 убирает «/», если хотя бы с одной стороны не цифра. Получается «3/2» →3/2и «3 / 2» →3/2. Угловые адреса («Гагарина, 27 / Первомайская, 81») и «204Г/1» дают прежний ключ. Сравнение тел 311 и 322 без комментариев отличается ровно этим шагом.REINDEX INDEX gar_house_flats_canon_idx: у 7237 индексируемых строк значение меняется. Стоит подSET LOCAL lock_timeout = '5s'(гейт #2752 зелёный).dadata_fiasиdadata_fias_revisionтоже не трогаются. Порог остановки 100: если под сброс попадёт больше, миграция падает целиком.Почему новое правило не склеивает разные дома. Новый ключ только уточняет старый: если убрать из него все «/», получится старый ключ. Значит, у двух адресов с одинаковым новым ключом совпадал и старый. Правка может только развести дома. Прод это подтверждает, см. ниже.
Замер на проде (только чтение, 17.09.2026; новое тело подставлено в SELECT как выражение)
Сначала проверил само выражение: встроенная копия тела 311 расходится с функцией на проде (тело 147) ровно у 44 домов, то есть у тех же 44 домов, что в замере #3558.
houses (49 204):
house_dedup_mergeне меняется.gar_house_flats: «/» между цифрами встречается в 7237 строках с квартирами (регион 66 — 938, 77 — 2711, 50 — 3588). Новых ключей, за которыми стоят несколько старых: 0.
Симуляция
_MATCH_SQL. Считал только по ключам, которых касается дробь: 3071 дом, 11 032 ГАР-строки. Правило как в коде: для региона 66 фильтр ЕКБ и max(flat_count), для 77 и 50 без фильтра и неоднозначный ключ не матчится.Все 77 потерянных пар я просмотрел глазами: каждая — дробь против числа («Удельная, 1/1» ↔ ГАР «11», «Ландау, 5/1» ↔ «51», «Высоцкого, 4/2» ↔ «42»). Ещё 5 — мусорные адреса из одного числа («152», «79»), пришитые к ГАР «мкр. 1/5, 2» и «пгт. Скоропусковский, 7/9». Сомнительны две: «Первомайская, 04А/04Б» ↔ ГАР «0/4а»/«0/4б» (регион 50), возможно, это тот же дом, у которого источник потерял дробь. Все 3 смены ГАР-дома в регионе 66 приходятся на дома, которые ревизия DaData уже исправила (373560 Сулимова 32, 377517 Краснолесья 103, 378914 Краснолесья 107). Новый матчер ставит им тот же ГАР-дом, что и DaData. Старый при перематче затёр бы исправления ревизии: UPDATE не смотрит на
gar_match_method.Сброс в миграции (точный SELECT по условию миграции): 47 домов (66 — 7, 77 — 21, 50 — 19), 3836 чужих квартир, 180 активных объявлений. Дома в ЕКБ: 10503, 10616, 11000, 372434, 378098, 378284, 378350.
Тесты
tests/test_2189_canon_keeps_slash.py(новый, 4 теста на живом Postgres, записи вskip_allowlist.txt):test_real_canon_keeps_slash_between_digitsпроверяет значения функции, поставленной миграциями: пары 3/2↔32, 10/7↔107, 4/1↔41 дают разные ключи; «3 / 2» совпадает с «3/2»; угловой адрес, «204Г/1», «Шаумяна,20»/«Шаумяна, 20» и «17Ар-н» дают прежние ключи;test_real_gar_match_picks_the_house_with_the_same_number: настоящийmatch_houses_to_garна ГАР-строках «3/2» (414) и «32» (120). Дом «32» получает 120, дом «3 / 2» получает 414;test_real_dedup_does_not_merge_slash_and_plain_number: «32» и «3/2» без ФИАС в 10 м друг от друга остаются двумя домами;test_migration_322_resets_only_matches_held_by_the_lost_slash: прогон настоящего файла 322 на временных таблицах. Сбрасываются «32→3/2» и «4/1→41». Верная пара «3/2→3/2», методdadata_fias_revisionи расхождение по другой причине (ЖК-префикс) остаются.test_house_dedup_merge.py: вtest_real_canon_strips_district_glued_to_house_number(#3558) ожиданиярассветная81/цвиллинга76заменены нарассветная8/1/цвиллинга7/6. Вtest_real_fias_pass_cross_guard_and_identity_carryoverпара B «Клара1772, 32»/«3/2» заменена на «ул.»/«улица» с одним номером. Зачем: после 322 у старой пары разные ключи, и страж разных ФИАС тест не проверял. Со снятым стражем тест со старой парой оставался зелёным (проверено), с новой краснеет:AssertionError: different fias must veto a same-canon merge.tradein-mvp/backend. Ветка (голова #3558, в ней maina130303c) на живом PostGIS со схемой из всехdata/sql: 6418 passed, 1 skipped, 4 failed. С заглушечным DSN: 6369 passed, 50 skipped, 4 failed. Все 4 падения —test_3466_corridor_tier_aиtest_estimator_radius_floor:'Settings' object has no attribute 'estimate_corridor_clamp_*'. На чистом origin/maina130303cони падают так же, их чинит #3572.85d455e3(с #3572), слитый локально с этой веткой, на живом PostGIS: 6422 passed, 1 skipped, rc=0.ruff check app tests: rc=0.ruff format --checkпо изменённым файлам: rc=0.scripts/check-migration-lock-timeout.py --selftestи основной прогон: rc=0.Фальсификация
S6 в 322 возвращён к
'[^а-я0-9]', миграция применена к тестовой базе (копия файла в scratchpad, после прогона восстановлена,diff -qчист):Со старой функцией дома «32» и «3/2» в одной точке сливаются, а «32» получает 414 квартир.
S7 заменён на регэксп, который ничего не ловит (дробь остаётся где угодно):
Из миграции удалён блок сброса:
Из условия сброса снято «совпадают без "/"»:
Во всех случаях файл восстановлен из копии (
diff -qчист), миграция применена заново, тесты снова зелёные.Причина красного CI после слияния с main
После слияния main (
8bd16e6a) в CI упал ровно один тест:test_real_gar_match_picks_the_house_with_the_same_number,(900189, None, None, None) != (900189, 'g2189-32', 120, 'canon_addr'). Воспроизведено на живом postgis/postgis:16-3.4 со схемой, собранной как в ci-tradein.yml (всеdata/sqlчерезls | sort,ON_ERROR_STOP): 1 failed, 3 passed.Причина — не гео-страж #3558 и не миграции 311/322. Замер на тех же строках фикстуры:
сулимова218932, дом 900190 →сулимова21893/2; ГАРg2189-32→сулимова218932,g2189-3-2→сулимова21893/2;CAST(h.region_code AS text) = gp.region_code: он пришёл в_MATCH_SQLиз #3575 (b9c89641, #2583 H5). У домов фикстурыregion_codeне задан (у колонки нет DEFAULT), сравнение даёт NULL, и UPDATE не трогает ни один дом.Гео-страж 250 м здесь ни при чём: этот тест вообще не вызывает
house_dedup_merge, а у соседнего теста дедупа координаты есть, и он проходил.Дом без региона не получает guid намеренно: это решение #3575, записанное в его тесте (
регион дома неизвестен — не доказуемо) и в runbook. Поэтому исправлена фикстура: домам заданregion_code66, как у ГАР-строк и у прогонаregion_code="66". Проверка канона со «/» осталась прежней (коммит21f9c295).Фальсификация. В 322 шаг S6 вручную возвращён к
[^а-я0-9], то есть дробь снова теряется. Схема пересобрана так же, как в CI, прогнан файл теста: rc=1, 4 failed. Исправленный тест падает по существу, а не из-за региона:Файл восстановлен из HEAD, схема пересобрана,
tradein_canon_addr('ул. Сулимова, 3/2')=сулимова3/2.Свежий main влит ещё раз (
f0d98934). На голове ветки заглушечный прогон давал rc=1 при 0 failed: три живых теста #3577 не были записаны в skip_allowlist. В main это исправил #3584; слияние затронуло только allowlist, конфликтов не было.Прогоны на
f0d98934:tests/test_2189_canon_keeps_slash.py: 4 passed, rc=0;test_house_dedup_merge.py+test_gar_flats_loader.py: 77 passed, rc=0;ruff check: rc=0;ruff format --checkпо .py-файлам ветки: rc=0.Для владельца, не правка: на проде (чтение, 17.09) у 623 из 49 233 домов нет
region_code, у 148 из них guid уже есть. После #3575 такие дома новых ГАР-пар не получат.Чего этот PR не делает (поэтому Refs, а не Closes)
--match-only.Наблюдение, не правка: матчер не ограничивает дома регионом.Устранено в main #3575 (b9c89641, #2583 H5), изменение влито в ветку. Пример ниже больше не воспроизводится. Прогон региона 66 сопоставляет с ГАР Екатеринбурга и дома из Балашихи («мкр. Балашиха-1, проспект Ленина, 2/5» ↔ «ул. Ленина, 25»).Деплой
CREATE OR REPLACE FUNCTION,REINDEX INDEX gar_house_flats_canon_idx(201 513 строк, по замеру #3558 около 6 с) и UPDATE 47 строк houses. Всё в одной транзакции подlock_timeout 5s. Если в момент деплоя идёт загрузка ГАР или перематч, миграция упадёт LockNotAvailable и откатится целиком; тогда деплой повторить.gar_house_flatsчитают только ручной загрузчик и перематч, пользовательский путь не задет.scrape_runs.status='running'.NOTICE: houses: сброшено ГАР-сопоставлений, державшихся на потерянной дроби = 47(±несколько). Если больше 100, миграция остановится сама.python -m app.tasks.gar_flats_load --match-only --region 66(и 77, 50).Приёмка на проде (критерии записаны до факта)
Сразу после деплоя 311+322 (ожидается 18.09.2026, иначе в день деплоя):
SELECT tradein_canon_addr('Екатеринбург, ул. Сулимова, 3/2'), tradein_canon_addr('Екатеринбург, ул. Сулимова, 32')возвращаетсулимова3/2 | сулимова32.SET enable_seqscan=offзапросSELECT house_num FROM gar_house_flats WHERE flat_count>0 AND tradein_canon_addr(norm_address)='сулимова3/2' AND region_code='66'возвращает3/2, план — Index/Bitmap Scan поgar_house_flats_canon_idx.SELECT count(*) FROM houses WHERE gar_house_guid IS NOT NULL AND id IN (10503,10616,11000,372434,378098,378284,378350)возвращает 0.gar_house_guidне изменился (ГАР «32», «103», «107»).Refs #2189
🤖 Generated with Claude Code