gendesign/tradein-mvp/backend/data/sql/147_canon_strip_geo_prefixes.sql
lekss361 b45a819cb6
All checks were successful
Deploy Trade-In / changes (push) Successful in 14s
Deploy Trade-In / build-browser (push) Has been skipped
Deploy Trade-In / build-frontend (push) Successful in 31s
Deploy Trade-In / test (push) Successful in 1m33s
Deploy Trade-In / build-backend (push) Successful in 27s
Deploy Trade-In / deploy (push) Successful in 53s
fix(tradein): канон срезает гео-префиксы (Россия/Екатеринбург/мкр/пр-т) + REINDEX — покрытие 38%→~70% (147) (#2073)
2026-06-28 17:54:26 +00:00

104 lines
10 KiB
PL/PgSQL
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

-- 147_canon_strip_geo_prefixes.sql
--
-- CONTEXT: канон-матч ГАР↔houses (мигр. 144, tradein_canon_addr) покрывал лишь 38%
-- вторички (2596 правдоподобных домов из 6810). Это НЕ дыра в данных ГАР: ГАР уже знает
-- flat_count для ~2000-2700 из 4041 непокрытых домов. Канон промахивался, потому что
-- houses-адреса несут ГЕОГРАФИЧЕСКИЕ ПРЕФИКСЫ, которые мигр.144 не срезала
-- (срезался только «р-н ...,»):
-- «Россия,», «Свердловская обл./область,», «г Екатеринбург,»/«Екатеринбург,»,
-- «мкр. Эльмаш,», «<Имя> м-н,» (суффикс-маркер микрорайона — «Химмаш м-н»),
-- «пос./посёлок ...,», «жилой район ...,», «снт/пгт ...,»,
-- ПЛЮС баг проспекта: «пр-т»/«пр-кт» → старый токен-срез убирал «пр», оставляя хвост
-- «т»/«кт» (а ГАР хранит «пр-кт Космонавтов» с тем же багом → обе стороны не совпадали).
-- Аналогичный баг у «б-р» (бульвар) и маркера дома «д.»/«д <N>».
--
-- WHAT: CREATE OR REPLACE FUNCTION tradein_canon_addr(text) — расширяет мигр.144:
-- S1 «пр-т/пр-кт/пр кт/пркт» → «проспект»; «б-р» → «бульвар» (чинит хвост-баг до
-- токен-среза, и на houses-, и на ГАР-стороне «пр-кт»);
-- S2 срез СУФФИКС-сегмента региона: «<...> обл[.]/область,»;
-- S3 срез СУФФИКС-сегмента микрорайона: «<Имя> м-н,»;
-- S4 срез ПРЕФИКС-сегментов: «россия,», «екатеринбург,», «город/г ...,»,
-- «жилой район/жилрайон ...,», «пгт/снт ...,», «р-н ...,», «м-н ...,» —
-- якорь на границе сегмента (^|,) + lookahead «,» (каскадно срезает цепочку
-- «Россия, Екатеринбург, ...» — lookahead НЕ съедает запятую, она остаётся
-- якорем следующего сегмента);
-- S4b срез НЕОДНОЗНАЧНЫХ маркеров «мкр/пос/поселок ...,» ТОЛЬКО если следом идёт
-- улица (сегмент с кириллицей) — guard `(?=,[^,]*[а-я])`: защищает «мкр. X, 15»
-- (номер прямо в микрорайоне) от потери дома;
-- S5b срез маркера дома «д[.]» перед номером (lookahead на цифру);
-- далее — СУЩЕСТВУЮЩИЙ токен-срез типов улиц (S5) + keep [а-я0-9] (S6) из мигр.144.
-- Чистые ГАР-адреса «ул Шаумяна, 20» и houses «ул. Шаумяна,20» по-прежнему → «шаумяна20»
-- (новые гео-срезы — no-op на чистых адресах, backward-compatible).
--
-- Проверено на проде (ROLLBACK-tx, REINDEX внутри):
-- RECOVERY: из 4041 непокрытых view-домов канон совпал с ГАР-ЕКБ (flat_count>0) у 2905,
-- из них правдоподобных (flat_count>=GREATEST(floors,8) AND active<=flat_count) — 2789.
-- NO-REGRESSION: из 4681 уже-совпавших домов сохранили тот же flat_count 4678, lost=0,
-- changed=3 — и все 3 это УЛУЧШЕНИЕ (проспект Ленина: ГАР хранит «пр-кт Ленина», старый
-- баг матчил недоучтённую строку fc 2/8 → новый канон цепляет полную fc 166/34/11).
-- FALSE-POSITIVE: выборка из 12 пар — улица+номер совпадают точно (нет 33→333 ошибок).
-- Итог покрытия: 38% → ~70-80% (после ре-матча).
--
-- DEPENDENCIES (existing prod objects):
-- - tradein_canon_addr(text) — мигр. 144 (заменяем тело, IMMUTABLE, обратносовместимо).
-- - gar_house_flats_canon_idx — ФУНКЦИОНАЛЬНЫЙ btree-индекс ON gar_house_flats
-- (tradein_canon_addr(norm_address)) WHERE flat_count>0 (мигр. 144). Postgres НЕ
-- перестраивает функц.индекс при смене тела функции → значения в индексе СТАЛЫ →
-- обязателен REINDEX (ниже). Проверено `pg_indexes ILIKE '%tradein_canon_addr%'`:
-- это ЕДИНСТВЕННЫЙ индекс, зависящий от функции.
-- - v_building_sale_share (мигр. 143/145) использует houses.gar_flat_count — не ломается
-- (мы не трогаем колонки/вью; ре-матч лишь до-заполняет gar_flat_count).
--
-- POST-DEPLOY (НЕ в этом файле — отдельный шаг main-сессии): ре-матч уже зовёт
-- tradein_canon_addr, поэтому после деплоя функции достаточно прогнать матчер:
-- python -m app.tasks.gar_flats_load --match-only --region 66
-- (он set-based UPDATE'ит houses.gar_flat_count по новому канону; идемпотентно).
--
-- SAFETY / IDEMPOTENCY:
-- - CREATE OR REPLACE FUNCTION + REINDEX INDEX — повторный прогон no-op (REINDEX просто
-- перестроит индекс ещё раз). REINDEX INDEX <name> допустим внутри транзакции (в отличие
-- от REINDEX CONCURRENTLY / DATABASE).
-- - Функция остаётся IMMUTABLE (обязательно для функц.индекса).
-- - Раннер деплоя гонит файл через psql ON_ERROR_STOP=on БЕЗ --single-transaction →
-- транзакцию открывает сам файл (BEGIN/COMMIT), как 144/145.
--
-- Deploy order: после 145_building_sale_share_plausible_denom.sql.
BEGIN;
-- Канонический ключ адреса: расширяет мигр.144 срезом гео-префиксов + фиксом проспект/бульвар/дом.
-- ВАЖНО порядок regexp_replace: внутренний (S0) исполняется первым, внешний (keep) — последним.
CREATE OR REPLACE FUNCTION tradein_canon_addr(s text) RETURNS text AS $func$
SELECT regexp_replace( -- S6: оставить только [а-я0-9]
regexp_replace( -- S5: срез типов улиц (токены на границе не-кириллицы)
regexp_replace( -- S5b: срез маркера дома «д[.]» перед номером
regexp_replace( -- S4b: срез «мкр/пос/поселок ...,» только если следом улица
regexp_replace( -- S4: срез префикс-сегментов (каскад через lookahead)
regexp_replace( -- S3: срез суффикс-сегмента «<Имя> м-н,»
regexp_replace( -- S2: срез суффикс-сегмента «<...> обл/область,»
regexp_replace( -- S1b: «б-р» → «бульвар»
regexp_replace( -- S1a: «пр-т/пр-кт/пр кт/пркт» → «проспект»
translate(lower(coalesce(s, '')), 'ё', 'е'), -- S0: lower + ё→е
'(^|[^а-я])пр[-. ]?к?т([^а-я]|$)', '\1проспект\2', 'g'),
'(^|[^а-я])б-р([^а-я]|$)', '\1бульвар\2', 'g'),
'(^|,)[^,]* обл[а-я]*\.?(?=,)', '\1', 'g'),
'(^|,)[^,]* м-н(?=,)', '\1', 'g'),
'(^|,)\s*(россия|екатеринбург|город|жилой район|жилрайон|пгт|снт|р-н|м-н|г)([. ][^,]*)?(?=,)', '\1', 'g'),
'(^|,)\s*(мкр|поселок|пос)[. ][^,]*(?=,[^,]*[а-я])', '\1', 'g'),
'(^|[^а-я])д\.?(?= *[0-9])', '\1', 'g'),
'(^|[^а-я])(улица|ул|переулок|пер|проспект|пркт|пр|бульвар|бр|шоссе|ш|проезд|набережная|наб|площадь|пл|тупик|туп|аллея|микрорайон|мкр)([^а-я]|$)', '\1\3', 'g'),
'[^а-я0-9]', '', 'g');
$func$ LANGUAGE sql IMMUTABLE;
COMMENT ON FUNCTION tradein_canon_addr(text) IS
'Канонический ключ адреса для ГАР↔houses матча (мигр. 144 + 147): lower+ё→е, '
'нормализация проспект/бульвар (пр-т/пр-кт/б-р), срез гео-префиксов '
'(Россия/обл/Екатеринбург/г/мкр/м-н/пос/жилой район/р-н/снт/пгт) и маркера дома «д.», '
'затем срез типов улиц как токенов на границе не-кириллицы → оставляет только [а-я0-9]. '
'Поднял покрытие 38%%→~70-80%% (recovery 2905/4041, lost=0). См. gar-flats-runbook.md.';
-- ОБЯЗАТЕЛЬНО: перестроить функц.индекс — иначе он держит СТАРЫЕ (мигр.144) значения канона
-- и JOIN gar_pick матчил бы по устаревшему ключу. Единственный зависящий индекс (pg_indexes).
REINDEX INDEX gar_house_flats_canon_idx;
COMMIT;