-- 99_nspd_entities_denorm.sql
-- Context : Denormalized entity tables для NSPD parcel и building features.
--           nspd_quarter_dumps.features_json хранит все feature'ы в JSONB; эти
--           таблицы дают быстрый lookup по cad_num без json-unpacking на query time.
--           Источник: layer="parcels" (NSPD layer 36048) и layer="buildings" (36049)
--           из features_json. Геометрия трансформируется 3857→4326 при UPSERT.
--
-- Dependencies: 88_nspd_quarter_dumps.sql (logical; таблица должна существовать до backfill)
-- Deploy order: после 98_nspd_quarter_dumps_opportunity_flag.sql
-- Idempotent: yes — IF NOT EXISTS на всех объектах
-- Related:
--   - backend/app/services/scrapers/nspd_denorm.py :: denorm_parcel_feature / denorm_building_feature
--   - backend/app/workers/tasks/nspd_denorm_backfill.py :: backfill_all_dumps
--   - backend/app/workers/tasks/nspd_sync.py :: harvest_quarter (inline denorm)
-- Issue: #94 PR3

BEGIN;

-- ── nspd_parcels ──────────────────────────────────────────────────────────────
-- Denormalized parcel features из NSPD quarter dumps (layer="parcels", id 36048).
-- Каждая строка = один земельный участок из любого квартала.
-- PK cad_num: один участок может пересекать несколько кварталов, берём
-- последний harvest (ON CONFLICT DO UPDATE).

CREATE TABLE IF NOT EXISTS nspd_parcels (
    -- Кадастровый номер участка (4-сегментный, e.g. '66:41:0204016:10').
    -- Первичный ключ — natural, стабильный идентификатор в ЕГРН.
    cad_num             TEXT PRIMARY KEY,

    -- Квартал из которого была взята последняя версия этого участка.
    quarter_cad         TEXT NOT NULL,

    -- ВРИ (вид разрешённого использования) — properties.permitted_use из NSPD.
    -- Nullable: НСПД не всегда возвращает ВРИ для старых записей.
    permitted_use       TEXT,

    -- Категория земель (properties.land_category).
    -- Nullable: может отсутствовать в ответе.
    land_category       TEXT,

    -- Кадастровая стоимость ЗУ (properties.cost_value), руб.
    cost_value          NUMERIC,

    -- Кадастровая стоимость за м² = cost_value / area_sqm.
    -- Вычисляется при UPSERT; NULL если area_sqm = 0 или cost_value = NULL.
    cost_per_m2         NUMERIC,

    -- Площадь участка (properties.area), м².
    area_sqm            NUMERIC,

    -- Адрес участка (properties.address).
    address             TEXT,

    -- Геометрия участка в WGS84 (трансформируется из EPSG:3857 при UPSERT).
    -- Geometry не Polygon т.к. НСПД иногда возвращает MultiPolygon или Point.
    geom                GEOMETRY(Geometry, 4326),

    -- Дата snapshot'а (день harvest'а).
    snapshot_date       DATE NOT NULL DEFAULT CURRENT_DATE,

    -- Timestamp последнего UPSERT.
    updated_at          TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

COMMENT ON TABLE nspd_parcels IS
    'Denormalized parcel features из NSPD quarter dumps (layer=parcels, id 36048). '
    'PK = cad_num (последний harvest wins). Заполняется Celery task backfill_all_dumps '
    'или inline в harvest_quarter. Источник: features_json JSONB колонка nspd_quarter_dumps. '
    'Геометрия трансформирована EPSG:3857→4326. (#94 PR3)';

COMMENT ON COLUMN nspd_parcels.cad_num IS
    'Кадастровый номер участка (4-сегментный). Natural PK из НСПД.';

COMMENT ON COLUMN nspd_parcels.quarter_cad IS
    '3-сегментный квартал последнего harvest''а. Используется для debug/audit.';

COMMENT ON COLUMN nspd_parcels.cost_per_m2 IS
    'Кадастровая стоимость за м² (cost_value / area_sqm). '
    'NULL если area_sqm = 0 или cost_value отсутствует. Вычисляется в Python при UPSERT.';

-- Индекс для lookup по кварталу (join с nspd_quarter_dumps).
CREATE INDEX IF NOT EXISTS idx_nspd_parcels_quarter_cad
    ON nspd_parcels (quarter_cad);

-- GIST индекс на геометрию для spatial queries (соседние участки, ST_DWithin).
CREATE INDEX IF NOT EXISTS idx_nspd_parcels_geom_gist
    ON nspd_parcels USING GIST (geom);

-- Partial B-tree на cost_per_m2 для ценовых аналитических запросов.
CREATE INDEX IF NOT EXISTS idx_nspd_parcels_cost_per_m2
    ON nspd_parcels (cost_per_m2)
    WHERE cost_per_m2 IS NOT NULL;


-- ── nspd_buildings ────────────────────────────────────────────────────────────
-- Denormalized building features из NSPD quarter dumps (layer="buildings", id 36049).
-- Каждая строка = одно здание/сооружение ЕГРН.

CREATE TABLE IF NOT EXISTS nspd_buildings (
    -- Кадастровый номер здания (5-сегментный, e.g. '66:41:0204016:10:1').
    cad_num             TEXT PRIMARY KEY,

    -- Квартал последнего harvest'а.
    quarter_cad         TEXT NOT NULL,

    -- Назначение здания (properties.purpose).
    -- Ключевые значения: 'Многоквартирный дом', 'Нежилое здание', etc.
    purpose             TEXT,

    -- Количество надземных этажей (properties.floors_above_ground).
    floors              INTEGER,

    -- Количество подземных этажей (properties.floors_underground).
    floors_underground  INTEGER,

    -- Год постройки (properties.year_built).
    year_built          INTEGER,

    -- Кадастровая стоимость здания (properties.cost_value), руб.
    cost_value          NUMERIC,

    -- Площадь по данным ГКН (properties.build_record_area), м².
    build_record_area   NUMERIC,

    -- Адрес здания (properties.address).
    address             TEXT,

    -- Геометрия здания в WGS84 (трансформируется из EPSG:3857 при UPSERT).
    geom                GEOMETRY(Geometry, 4326),

    -- Дата snapshot'а.
    snapshot_date       DATE NOT NULL DEFAULT CURRENT_DATE,

    -- Timestamp последнего UPSERT.
    updated_at          TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

COMMENT ON TABLE nspd_buildings IS
    'Denormalized building features из NSPD quarter dumps (layer=buildings, id 36049). '
    'PK = cad_num (последний harvest wins). Заполняется Celery task backfill_all_dumps '
    'или inline в harvest_quarter. Ключевой use-case: МКД-соседи для P2-scoring. '
    'Геометрия трансформирована EPSG:3857→4326. (#94 PR3)';

COMMENT ON COLUMN nspd_buildings.purpose IS
    'Назначение здания (properties.purpose). Для МКД = "Многоквартирный дом". '
    'Используется в partial index idx_nspd_buildings_mkd для быстрой фильтрации.';

COMMENT ON COLUMN nspd_buildings.floors IS
    'Количество надземных этажей (properties.floors_above_ground из НСПД). '
    'Nullable: старые ОКС в ЕГРН не всегда имеют этажность.';

-- Индекс для lookup по кварталу.
CREATE INDEX IF NOT EXISTS idx_nspd_buildings_quarter_cad
    ON nspd_buildings (quarter_cad);

-- GIST индекс на геометрию (МКД-соседи через ST_DWithin).
CREATE INDEX IF NOT EXISTS idx_nspd_buildings_geom_gist
    ON nspd_buildings USING GIST (geom);

-- Partial index для МКД (основной use-case P2-scoring).
-- ILIKE не работает в partial index WHERE clause — используем точный строковый prefixmatch.
-- Здания с purpose IS NULL или другим purpose — в idx_nspd_buildings_geom_gist.
CREATE INDEX IF NOT EXISTS idx_nspd_buildings_mkd
    ON nspd_buildings (quarter_cad)
    WHERE purpose = 'Многоквартирный дом';

COMMENT ON INDEX idx_nspd_buildings_mkd IS
    'Partial index для быстрого lookup МКД по кварталу (P2-scoring neighbors). '
    'WHERE purpose = exact строка из НСПД. (#94 PR3)';

COMMIT;
