-- 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;
