All checks were successful
CI / changes (pull_request) Successful in 9s
CI Trade-In / changes (pull_request) Successful in 10s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Successful in 1m51s
CI / backend-tests (pull_request) Successful in 14m12s
Миграция 184: адресные точки выхода газораспределительной сети (форма 6 приказа ФАС №960, раскрытие АО «Екатеринбурггаз», ~1037 строк/мес). - grs_name_norm нормализуется как в gas_grs_capacity (181) → JOIN «точка выхода → свободная мощность ГРС» без FK (источники разные: ГТЕ vs ЕКгаз). - Размерность млн. м³/МЕСЯЦ (НЕ тыс. м³/ч как в 181) — отражено в COMMENT. - «0.000*» = требуется гидрорасчёт: free_capacity_mln_m3 NULL + free_capacity_needs_calc TRUE (не путать с настоящим 0). - is_forecast разводит факт («за <месяц>») и прогноз («на <месяц>») одного period_month; логический UNIQUE(grs_name_norm, outlet_name, period_month, is_forecast) — все 4 колонки NOT NULL, обычный UNIQUE (без NULLS NOT DISTINCT). - Индексы: GIST(geom), btree(grs_name_norm), btree(period_month DESC). - geom NULL до PR-3 (геокод DaData) и навсегда у ~81% строк без адреса. Идемпотентно (IF NOT EXISTS), BEGIN/COMMIT. Провалидировано dry-run в ephemeral postgis/postgis:16-3.4 (prod-образ): двойной прогон, fact+forecast coexist, ON CONFLICT upsert, отклонение дубля. PR-план #2119 B2: PR-1 schema (этот) · PR-2 лоадер · PR-3 геокод · PR-4 §3. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
121 lines
9.8 KiB
PL/PgSQL
121 lines
9.8 KiB
PL/PgSQL
-- 184_gas_grs_outlet_points.sql
|
||
-- Forgejo #2119 Phase B2 — адресные точки выхода газораспределительной сети
|
||
-- (форма 6 приказа ФАС №960) для ПТИЦА §3. PR-1 из 4 (Schema).
|
||
--
|
||
-- Одна таблица gas_grs_outlet_points: построчная выгрузка «точек выхода» ГРС из
|
||
-- раскрытия АО «Екатеринбурггаз» (ГРО, газораспределительная организация):
|
||
-- https://ekgas.ru/ — apex-домен, форма 6 (техвозможность подключения по
|
||
-- точкам выхода) приказа ФАС России №960 «О порядке раскрытия информации…».
|
||
-- ~1037 строк/месяц, помесячные срезы.
|
||
--
|
||
-- Источник ГЕО-БЛОКИРУЕТ non-RU IP → загрузчик РАБОТАЕТ НА ПРОДЕ (Celery / manual
|
||
-- docker exec) — как gas_grs_capacity (181, ГТЕ) и rosseti-/vodokanal-лоадеры (180).
|
||
-- Код не должен предполагать локальный сетевой доступ.
|
||
--
|
||
-- Связь с B1: grs_name_norm нормализуется ТЕМ ЖЕ правилом, что и в
|
||
-- gas_grs_capacity (181): lower, ё→е, пробелы/дефисы схлопнуты, префиксы «ГРС»/«АРП»
|
||
-- СОХРАНЯЮТСЯ. Это даёт JOIN «точка выхода → свободная мощность ГРС» без FK
|
||
-- (источники разные — ГТЕ vs Екатеринбурггаз, — жёсткий FK был бы ложным).
|
||
--
|
||
-- ВНИМАНИЕ, РАЗМЕРНОСТЬ: объёмы формы 6 — млн. м³/МЕСЯЦ (не тыс. м³/ЧАС, как
|
||
-- free_capacity_th_m3_h в gas_grs_capacity!). Не смешивать при агрегации в §3.
|
||
--
|
||
-- «0.000*» в столбце свободной мощности источника = НЕ ноль, а «требует
|
||
-- гидравлического расчёта» → free_capacity_mln_m3 = NULL, флаг
|
||
-- free_capacity_needs_calc = TRUE (см. семантику ниже).
|
||
--
|
||
-- address_extracted покрывает лишь ~19% строк (у ~81% «точки выхода» адреса в
|
||
-- тексте нет) → geom останется NULL у большинства даже после PR-3 (геокод DaData).
|
||
--
|
||
-- PR-план #2119 B2:
|
||
-- PR-1 (этот) — schema.
|
||
-- PR-2 — лоадер (forward-fill grs_name по block-marker «Точка входа», парс
|
||
-- «за/на <месяц> <год>» → period_month + is_forecast, разбор «0.000*»,
|
||
-- извлечение address_extracted, UPSERT ON CONFLICT).
|
||
-- PR-3 — геокод address_extracted через DaData → geom (ключи DaData на проде).
|
||
-- PR-4 — интеграция в отчёт §3 (свободная мощность точек выхода рядом с ГРС B1).
|
||
--
|
||
-- Порядок применения: миграция ПЕРЕД деплоем кода (schema-first, sql.md).
|
||
-- Таблица НОВАЯ → deploy.yml гоняет всё в одной транзакции; CREATE INDEX
|
||
-- CONCURRENTLY здесь не нужен (и запрещён внутри транзакции).
|
||
|
||
BEGIN;
|
||
|
||
-- ── gas_grs_outlet_points ─────────────────────────────────────────────────────
|
||
-- geom NULL до PR-3 (геокод) и у ~81% строк без адреса — навсегда. raw jsonb —
|
||
-- сырые ячейки строки формы 6 целиком (неизвестные/будущие колонки не теряются).
|
||
CREATE TABLE IF NOT EXISTS gas_grs_outlet_points (
|
||
id BIGSERIAL PRIMARY KEY,
|
||
grs_name TEXT NOT NULL, -- «Точка входа» (block-marker, forward-fill в лоадере)
|
||
grs_name_norm TEXT NOT NULL, -- нормализация как в gas_grs_capacity → матч на B1-ГРС
|
||
outlet_name TEXT NOT NULL, -- «Точка выхода» — полный свободный текст (юрлицо/объект)
|
||
consumer_type TEXT, -- котельная / ТЭЦ / площадка / промплощадка
|
||
consumption_group TEXT, -- «5 - 0,1-1млн.куб.м» и т.п.
|
||
volume_requested_mln_m3 NUMERIC, -- заявки, млн. м³/мес
|
||
volume_approved_mln_m3 NUMERIC, -- удовлетворено, млн. м³/мес
|
||
free_capacity_mln_m3 NUMERIC, -- свободная мощность, млн. м³/мес; NULL если «0.000*»
|
||
free_capacity_needs_calc BOOLEAN NOT NULL DEFAULT FALSE, -- маркер «0.000*» (нужен гидрорасчёт)
|
||
address_extracted TEXT, -- адресный хвост для DaData; NULL если адреса нет (~81%)
|
||
geom GEOMETRY(POINT, 4326), -- NULL до PR-3 геокода (и навсегда у строк без адреса)
|
||
period_month DATE NOT NULL, -- 2026-05-01 из «за май 2026» (первое число месяца)
|
||
is_forecast BOOLEAN NOT NULL DEFAULT FALSE, -- «на <месяц>»=TRUE (прогноз), «за»=FALSE (факт)
|
||
asof DATE, -- дата актуальности данных
|
||
source_url TEXT, -- URL страницы/файла раскрытия
|
||
raw JSONB, -- сырая строка формы 6 целиком
|
||
fetched_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||
|
||
-- Логический dedup-ключ. Все 4 колонки NOT NULL → обычный UNIQUE достаточен
|
||
-- (NULLS NOT DISTINCT не нужен: NULL в ключе невозможен по CHECK NOT NULL).
|
||
-- Тот же (grs_name_norm, outlet_name) может встречаться в двух строках одного
|
||
-- period_month — факт («за») и прогноз («на»); is_forecast разводит их.
|
||
UNIQUE (grs_name_norm, outlet_name, period_month, is_forecast)
|
||
);
|
||
|
||
CREATE INDEX IF NOT EXISTS idx_gas_grs_outlet_points_name_norm
|
||
ON gas_grs_outlet_points (grs_name_norm);
|
||
|
||
CREATE INDEX IF NOT EXISTS idx_gas_grs_outlet_points_period
|
||
ON gas_grs_outlet_points (period_month DESC);
|
||
|
||
CREATE INDEX IF NOT EXISTS gist_gas_grs_outlet_points_geom
|
||
ON gas_grs_outlet_points USING GIST (geom);
|
||
|
||
COMMENT ON TABLE gas_grs_outlet_points IS
|
||
'Адресные точки выхода газораспределительной сети (форма 6 приказа ФАС №960), '
|
||
'раскрытие АО «Екатеринбурггаз» (ekgas.ru), ~1037 строк/мес, помесячные срезы. '
|
||
'ВНИМАНИЕ: объёмы в млн. м³/МЕСЯЦ (не тыс. м³/ч как в gas_grs_capacity!). '
|
||
'grs_name_norm матчит gas_grs_capacity (181, ГТЕ) без FK. geom NULL до PR-3 '
|
||
'геокода и навсегда у ~81% строк без адреса. Refresh: monthly. Forgejo #2119 B2.';
|
||
|
||
COMMENT ON COLUMN gas_grs_outlet_points.grs_name_norm IS
|
||
'Нормализованное имя ГРС/точки входа для матча на gas_grs_capacity (181): lower, '
|
||
'ё→е, пробелы/дефисы схлопнуты, префиксы «ГРС»/«АРП» СОХРАНЯЮТСЯ. Заполняется '
|
||
'forward-fill по block-marker «Точка входа» в лоадере (PR-2).';
|
||
|
||
COMMENT ON COLUMN gas_grs_outlet_points.outlet_name IS
|
||
'«Точка выхода» — полный свободный текст (юрлицо / объект / промплощадка). '
|
||
'Часть логического UNIQUE-ключа; адресный хвост из него → address_extracted.';
|
||
|
||
COMMENT ON COLUMN gas_grs_outlet_points.free_capacity_mln_m3 IS
|
||
'Свободная (резервная) мощность точки выхода, млн. м³/МЕСЯЦ. NULL, когда в '
|
||
'источнике «0.000*» — это НЕ ноль, а «требуется гидравлический расчёт» '
|
||
'(free_capacity_needs_calc=TRUE). Реальный 0 (без звёздочки) хранится как 0.';
|
||
|
||
COMMENT ON COLUMN gas_grs_outlet_points.free_capacity_needs_calc IS
|
||
'TRUE = в источнике «0.000*»: свободная мощность не определена, нужен '
|
||
'гидравлический расчёт (free_capacity_mln_m3 при этом NULL). FALSE = значение '
|
||
'известно (в т.ч. настоящий 0).';
|
||
|
||
COMMENT ON COLUMN gas_grs_outlet_points.address_extracted IS
|
||
'Адресный хвост, вытащенный из outlet_name для геокода DaData (PR-3). NULL, '
|
||
'когда адреса в тексте нет (~81% строк) → geom у них останется NULL.';
|
||
|
||
COMMENT ON COLUMN gas_grs_outlet_points.period_month IS
|
||
'Первое число месяца отчётного среза (2026-05-01 из «за/на май 2026»). Часть '
|
||
'логического UNIQUE-ключа: помесячные срезы не перезатирают друг друга.';
|
||
|
||
COMMENT ON COLUMN gas_grs_outlet_points.is_forecast IS
|
||
'TRUE = прогноз («на <месяц> <год>»), FALSE = факт («за <месяц> <год>»). '
|
||
'Разводит факт и прогноз одного period_month в UNIQUE-ключе.';
|
||
|
||
COMMIT;
|