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