-- 130_cad_parcels_default_source.sql
-- FIX (prod, продолжение #129) — on-demand НСПД-ингест cad_parcels падает на ВТОРОЙ NOT NULL
-- колонке `source` (после того как #129 закрыл quarter_cad_number).
--
-- БАГ (ground-truth, измерено на prod после деплоя #129):
--   cad_parcels NOT NULL колонки: cad_num, quarter_cad_number, category_id, source,
--   fetched_at, updated_at. #129-триггер достроил quarter_cad_number, но on-demand-ингест
--   (bulk_harvest.upsert_parcel / parcels-fetch воркер для не-предзагруженных участков)
--   доезжает до INSERT с source=NULL →
--     psycopg.errors.NotNullViolation: null value in column "source" of relation "cad_parcels"
--   → строка падает → geometry-транзакция роллбэчится → cad_parcels_geom тоже не сохраняется
--   → POST /analyze для участка навсегда залипает «Геометрия загружается из НСПД…».
--   Воспроизведено на РЕАЛЬНОМ участке 66:41:0610029:83 (job 57: упавшая строка несёт
--   quarter_cad_number=66:41:0610029 (достроен #129) + валидный geom, null только в source).
--
--   Все три code-вызывающих upsert_features передают непустой source ("search",
--   "search_per_cat", "wms_grid_walk"), т.е. в локальном коде source не None — но в проде
--   до INSERT доезжает NULL (как и с quarter_cad_number, путь ускользает: вероятно отдельная
--   ветка/стейл-фронт grid-walk). Вместо охоты за каждым код-путём — гарантируем на уровне БД,
--   как для quarter_cad_number: source — метаданные происхождения, для on-demand НСПД-фетча
--   осмысленный дефолт 'nspd'.
--
-- ПОЧЕМУ ТРИГГЕР (а не DEFAULT): колоночный DEFAULT срабатывает только когда колонка ОПУЩЕНА
--   из INSERT; здесь INSERT биндит source ЯВНЫМ NULL → DEFAULT не применился бы. BEFORE-триггер
--   же переопределяет явный NULL. Расширяем СУЩЕСТВУЮЩУЮ функцию #129 (CREATE OR REPLACE) — сам
--   триггер cad_parcels_derive_quarter_cad (BEFORE INSERT OR UPDATE, #129) уже навешен, меняем
--   только тело функции. Один инвариант-страж для всех NOT NULL метаданных, выводимых/дефолтимых.
--
-- ПОВЕДЕНИЕ (дополнение к #129):
--   • quarter_cad_number IS NULL + cad_num ≥3 сегмента → достроить (как #129).
--   • source IS NULL → 'nspd' (осмысленный дефолт происхождения для on-demand НСПД-ингеста;
--     не маскирует данные — это просто метка источника, явный source от bulk-загрузчиков
--     ("search"/"wms_grid_walk"/...) уважается, перетираем ТОЛЬКО NULL).
--
-- Idempotent: CREATE OR REPLACE FUNCTION (переопределяет тело на месте). Триггер не трогаем
--   (#129 его создал). BEGIN/COMMIT. Никаких DROP TABLE/TRUNCATE/строк.
-- Deploy order: чистая СХЕМА, без зависимости от кода. Эффект мгновенный со следующего ингеста:
--   on-demand cad_parcels INSERT больше не падает на NOT NULL (quarter+source достраиваются) →
--   geometry персистится → реальный новый участок анализируется.

BEGIN;

CREATE OR REPLACE FUNCTION cad_parcels_derive_quarter_cad() RETURNS TRIGGER AS $$
BEGIN
    -- #129: достроить quarter_cad_number из cad_num (первые 3 colon-сегмента), если NULL.
    IF NEW.quarter_cad_number IS NULL
       AND NEW.cad_num IS NOT NULL
       AND array_length(string_to_array(NEW.cad_num, ':'), 1) >= 3
    THEN
        NEW.quarter_cad_number :=
            array_to_string((string_to_array(NEW.cad_num, ':'))[1:3], ':');
    END IF;

    -- #130: source NOT NULL — on-demand-ингест доезжает с NULL. Дефолт 'nspd' (метка
    -- источника). Перетираем ТОЛЬКО NULL → явные source ("search"/"wms_grid_walk"/...) целы.
    IF NEW.source IS NULL THEN
        NEW.source := 'nspd';
    END IF;

    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

COMMENT ON FUNCTION cad_parcels_derive_quarter_cad() IS
    'BEFORE INSERT OR UPDATE-триггер cad_parcels (#129+#130): достраивает NOT NULL '
    'quarter_cad_number из cad_num когда NULL, и дефолтит NOT NULL source→''nspd'' когда NULL. '
    'Чинит NotNullViolation во ВСЕХ путях записи on-demand НСПД-ингеста (репро 66:41:0610029:83). '
    'Явные значения уважаются — перетираются только NULL. <3-сегм cad_num → quarter остаётся '
    'NULL (NOT NULL отклонит реальный мусор).';

COMMIT;
