-- 180_connection_capacity.sql
-- Forgejo #2119 Phase A — «точки подключения СО СВОБОДНОЙ МОЩНОСТЬЮ» для ПТИЦА §3.
--
-- Три таблицы верифицированных источников свободной мощности (электро + вода):
--   power_supply_centers  — центры питания (ЦП: ПС 35/110 кВ) из Россети WFS +
--                           резервы свободной мощности по ЦП (rosseti-ural.ru xlsx).
--   power_tp_rp_reserves  — резервы ТП/РП напряжением ниже 35 кВ (10k+ строк,
--                           в основном БЕЗ координат — Phase B геокод).
--   water_supply_reserves — резервы ЦСВ/ЦСК Водоканала ЕКБ (docx, city-level).
--
-- Источники гео-блокируют non-RU IP → загрузчики РАБОТАЮТ НА ПРОДЕ (Celery/manual
-- docker exec). Код не должен предполагать локальный сетевой доступ.
--
-- Refresh: weekly via tasks.connection_capacity_sync (WFS + reserves + water).
-- Порядок применения: миграция ПЕРЕД деплоем кода (schema-first, sql.md).

BEGIN;

-- ── power_supply_centers ─────────────────────────────────────────────────────
-- ЦП = центр питания (ПС 35/110 кВ). geom из Россети WFS (координаты округлены
-- ~100 м — для §3 «есть ли точка рядом» этого достаточно). Резервы свободной
-- мощности (installed/current/reserve) допихиваются из xlsx-раскрытия по
-- sc_name_norm-матчу. sc_name_norm — нормализованное имя для сопоставления
-- (lower, «ПС»/voltage-префикс срезан, ё→е, тире/пробелы схлопнуты).
CREATE TABLE IF NOT EXISTS power_supply_centers (
    id                    SERIAL PRIMARY KEY,
    source                TEXT        NOT NULL,          -- 'rosseti_wfs'
    external_id           TEXT,                          -- feature id или stable hash
    sc_name               TEXT        NOT NULL,          -- исходное имя ЦП
    sc_name_norm          TEXT        NOT NULL,          -- нормализованное для матча
    dzo_name              TEXT,                          -- ДЗО (напр. «Россети Урал»)
    org_name              TEXT,
    branch_name           TEXT,                          -- филиал (br_name)
    voltage_class         TEXT,                          -- напр. '110/35/10'
    load_index            TEXT,                          -- 'open'|'limited'|'closed'|NULL
    installed_capacity_mva NUMERIC,                      -- установленная, МВА
    current_load_mva      NUMERIC,                       -- текущая нагрузка, МВА
    reserve_mva           NUMERIC,                       -- свободная мощность, МВА
    reserve_asof          DATE,                          -- дата актуальности резерва
    capacity_source       TEXT,                          -- 'cp_35_110'|'tp_rp_sub35'|NULL
    municipality          TEXT,
    place_city            TEXT,                          -- sc_placecity
    geom                  GEOMETRY(POINT, 4326),
    raw                   JSONB,                          -- сырая WFS-фича целиком
    fetched_at            TIMESTAMPTZ NOT NULL DEFAULT NOW(),

    UNIQUE (source, external_id)
);

CREATE INDEX IF NOT EXISTS gist_power_supply_centers_geom
    ON power_supply_centers USING GIST (geom);

CREATE INDEX IF NOT EXISTS idx_power_supply_centers_name_norm
    ON power_supply_centers (sc_name_norm);

COMMENT ON TABLE power_supply_centers IS
    'Центры питания (ЦП, ПС 35/110 кВ) Россети + резервы свободной мощности по ЦП. '
    'Source: Россети WFS (geom) + rosseti-ural.ru xlsx (reserve). '
    'Refresh: weekly via tasks.connection_capacity_sync. Forgejo #2119 Phase A.';

COMMENT ON COLUMN power_supply_centers.sc_name_norm IS
    'Нормализованное имя ЦП для матча с xlsx-резервом (lower, «ПС»/voltage-префикс '
    'срезан, ё→е, тире/пробелы схлопнуты).';

COMMENT ON COLUMN power_supply_centers.geom IS
    'Координаты Россети WFS округлены ~100 м — для §3 «есть ли ЦП рядом» достаточно.';

-- ── power_tp_rp_reserves ─────────────────────────────────────────────────────
-- Резервы ТП/РП напряжением ниже 35 кВ (10 741 строк в верифицированном xlsx).
-- В основном БЕЗ координат — geocode_status='pending', гео придёт в Phase B.
-- UNIQUE(name_norm, municipality, voltage_kv) — одно и то же имя ТП может
-- встречаться в разных МО / на разном напряжении.
CREATE TABLE IF NOT EXISTS power_tp_rp_reserves (
    id                    SERIAL PRIMARY KEY,
    name                  TEXT        NOT NULL,          -- напр. «ТП-721»
    name_norm             TEXT        NOT NULL,          -- нормализованное для матча
    res_unit              TEXT,                          -- РЭС
    municipality          TEXT,
    settlement            TEXT,
    voltage_kv            TEXT,                          -- класс напряжения, кВ
    installed_capacity_mva NUMERIC,                      -- установленная, МВА
    current_load_mva      NUMERIC,                       -- текущая нагрузка, МВА
    reserve_mva           NUMERIC,                       -- свободная мощность, МВА
    reserve_asof          DATE,
    geom                  GEOMETRY(POINT, 4326),         -- NULL до Phase B геокода
    geocode_status        TEXT        DEFAULT 'pending',
    raw                   JSONB,
    fetched_at            TIMESTAMPTZ DEFAULT NOW(),

    -- NULLS NOT DISTINCT: municipality/voltage_kv бывают NULL (пустые ячейки xlsx);
    -- без этого ON CONFLICT не матчится и weekly-прогон плодит дубли
    -- (те же грабли, что 140_domrf_documents_unique_nulls_not_distinct.sql).
    UNIQUE NULLS NOT DISTINCT (name_norm, municipality, voltage_kv)
);

CREATE INDEX IF NOT EXISTS gist_power_tp_rp_reserves_geom
    ON power_tp_rp_reserves USING GIST (geom);

CREATE INDEX IF NOT EXISTS idx_power_tp_rp_reserves_name_norm
    ON power_tp_rp_reserves (name_norm);

CREATE INDEX IF NOT EXISTS idx_power_tp_rp_reserves_geocode_status
    ON power_tp_rp_reserves (geocode_status);

COMMENT ON TABLE power_tp_rp_reserves IS
    'Резервы свободной мощности ТП/РП <35 кВ (rosseti-ural.ru xlsx, ~10.7k строк). '
    'geom NULL до Phase B геокода (geocode_status). Forgejo #2119 Phase A.';

-- ── water_supply_reserves ────────────────────────────────────────────────────
-- Резервы ЦСВ (центр. система водоснабжения) / ЦСК (водоотведения) Водоканала
-- ЕКБ (docx-раскрытие). Городской уровень (без geom) — фронт показывает ЦСВ
-- «ЗФС, ГСВ» как атрибут города. reserve может быть ОТРИЦАТЕЛЬНЫМ (дефицит) —
-- знак сохраняем. UNIQUE(system_kind, system_name, period).
CREATE TABLE IF NOT EXISTS water_supply_reserves (
    id                        SERIAL PRIMARY KEY,
    system_kind               TEXT        NOT NULL,      -- 'water'|'sewerage'
    system_name               TEXT        NOT NULL,      -- ЦСВ/ЦСК, напр. «ЗФС, ГСВ»
    reserve_thousand_m3_day   NUMERIC,                   -- резерв, тыс. м³/сут (может быть <0)
    connections_requested     INT,                       -- заявок на подключение
    connections_refused       INT,                       -- отказов
    note                      TEXT,
    period                    TEXT,                       -- напр. '2026-Q1'
    source_url                TEXT,
    fetched_at                TIMESTAMPTZ DEFAULT NOW(),

    -- NULLS NOT DISTINCT: period может быть NULL при неудачном infer_period.
    UNIQUE NULLS NOT DISTINCT (system_kind, system_name, period)
);

CREATE INDEX IF NOT EXISTS idx_water_supply_reserves_period
    ON water_supply_reserves (period);

CREATE INDEX IF NOT EXISTS idx_water_supply_reserves_kind
    ON water_supply_reserves (system_kind);

COMMENT ON TABLE water_supply_reserves IS
    'Резервы ЦСВ/ЦСК Водоканала ЕКБ (docx-раскрытие, city-level, без geom). '
    'reserve_thousand_m3_day может быть отрицательным (дефицит). Forgejo #2119 Phase A.';

COMMENT ON COLUMN water_supply_reserves.reserve_thousand_m3_day IS
    'Свободная мощность ЦСВ/ЦСК, тыс. м³/сут. Отрицательное значение = дефицит.';

COMMIT;
