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
104 lines
10 KiB
PL/PgSQL
104 lines
10 KiB
PL/PgSQL
-- 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;
|