93 lines
8 KiB
PL/PgSQL
93 lines
8 KiB
PL/PgSQL
-- 133_zone_regulation_cache.sql
|
||
-- #1067 (Site Finder v2 / ИРД-content «GG-форсайт», C8b) — кэш ПЗЗ-регламента по индексу зоны.
|
||
--
|
||
-- Регламент ПЗЗ идентичен для ВСЕХ участков одной территориальной зоны (Ц-1, Ж-2, ...), а зон
|
||
-- на город ~100. Поэтому резолвить ВРИ+предельные параметры через ЕКБ-геопортал urbanCard на
|
||
-- каждый участок — расточительно (reverse-eng API без SLA, см. ekb_geoportal_client.py). Кэшируем
|
||
-- ОДИН раз на (city, zone_index): 3 списка ВРИ (540н) + сырой текст предельных параметров +
|
||
-- числовой экстракт (regex) ключевых параметров застройки для capacity-оценки.
|
||
--
|
||
-- Что отдаёт urbanCard (live-verified 2026-06-06, vault research/EKB_Geoportal_PZZ_Content_API_Jun06):
|
||
-- pzzList[0]: urbanTerZone (index+имя) · main/condition/addition TypesPermittedUse (ВРИ) ·
|
||
-- limitTypesPermittedUse[].typePermittedUsePars[].otherDescription — предельные параметры
|
||
-- СВОБОДНЫМ ТЕКСТОМ (min площадь ЗУ / max % застройки / КСИ / подземная застройка / этажность),
|
||
-- часто «не подлежат установлению». Парсятся сервисом zone_regulation.py (regex) → params_numeric.
|
||
--
|
||
-- Зерно (grain): одна строка = один индекс зоны в городе. Дедуп-ключ — (city, zone_index).
|
||
-- re-fetch UPSERT-ит (latest wins): регламент меняется только при правке ПЗЗ (редко) →
|
||
-- fetched_at = freshness, refresh-воркер дёргает по возрасту.
|
||
--
|
||
-- Связь с ird_overlays (м.132): ird_overlays.zone_index (когда резолвлен) JOIN-ится сюда за
|
||
-- полным регламентом зоны участка. content в ird_overlays НЕ дублируется — single source тут.
|
||
--
|
||
-- Deploy: auto-applied deploy.yml через _schema_migrations (ровно один раз по NN). Idempotent:
|
||
-- CREATE TABLE/INDEX IF NOT EXISTS + guarded DO для UNIQUE (constraint нельзя добавить дважды,
|
||
-- прецедент м.40/110/125/132). НИКАКИХ строк не вставляется (данные — из сервиса C8b).
|
||
|
||
BEGIN;
|
||
|
||
-- ── 1. Таблица zone_regulation_cache ───────────────────────────────────────────
|
||
CREATE TABLE IF NOT EXISTS zone_regulation_cache (
|
||
id BIGSERIAL PRIMARY KEY,
|
||
city TEXT NOT NULL DEFAULT 'ekb', -- мульти-город forward-compat; пока только ЕКБ
|
||
zone_index TEXT NOT NULL, -- индекс ПЗЗ-зоны ('Ц-1', 'Ж-2', 'ЦС-1')
|
||
zone_full_name TEXT, -- 'Ц-1 Общественно-деловая зона городского центра'
|
||
-- ── ВРИ (списки строк «код 540н + текст», JSONB-массивы) ──
|
||
main_vri JSONB NOT NULL DEFAULT '[]'::jsonb, -- основные ВРИ
|
||
conditional_vri JSONB NOT NULL DEFAULT '[]'::jsonb, -- условно-разрешённые ВРИ
|
||
auxiliary_vri JSONB NOT NULL DEFAULT '[]'::jsonb, -- вспомогательные ВРИ
|
||
-- ── предельные параметры ──
|
||
limit_params_raw JSONB NOT NULL DEFAULT '[]'::jsonb, -- сырые otherDescription-строки (forward-compat/LLM)
|
||
params_numeric JSONB NOT NULL DEFAULT '{}'::jsonb, -- структурный экстракт: {param: [values...]} (regex, C8b)
|
||
-- ── headline-скаляры для capacity (most-permissive из найденных; NULL = «не подлежат установлению»/нет) ──
|
||
max_far NUMERIC, -- коэф. строит. использования ЗУ (КСИ), max найденный
|
||
max_building_pct NUMERIC, -- макс. процент застройки, max найденный (%)
|
||
underground_pct NUMERIC, -- процент застройки подземного пространства, max (%)
|
||
min_parcel_area_m2 NUMERIC, -- мин. площадь ЗУ, наименьший из найденных минимумов (кв. м)
|
||
max_floors INTEGER, -- предельная этажность, max найденный
|
||
max_height_m NUMERIC, -- предельная высота, max найденный (м)
|
||
source TEXT NOT NULL DEFAULT 'ekb_geoportal', -- источник регламента
|
||
fetched_at timestamptz NOT NULL DEFAULT now(), -- когда резолвили (freshness для refresh)
|
||
-- city/zone_index — осмысленные только непустыми; ловим мусор громко на write.
|
||
CONSTRAINT zone_regulation_cache_zone_index_chk CHECK (length(zone_index) > 0),
|
||
CONSTRAINT zone_regulation_cache_city_chk CHECK (length(city) > 0)
|
||
);
|
||
|
||
COMMENT ON TABLE zone_regulation_cache IS
|
||
'Кэш ПЗЗ-градрегламента по индексу зоны (#1067 C8b, GG-форсайт). 1 строка = 1 (city, '
|
||
'zone_index). 3 списка ВРИ (540н) + сырой текст предельных параметров + числовой экстракт '
|
||
'(params_numeric/headline-скаляры, regex). Резолв из ЕКБ-геопортала urbanCard (zone_regulation.py). '
|
||
'UPSERT по uq_zone_regulation_cache (city, zone_index); latest wins. ird_overlays.zone_index JOIN-ится сюда.';
|
||
COMMENT ON COLUMN zone_regulation_cache.params_numeric IS
|
||
'Числовой экстракт предельных параметров (regex из limit_params_raw): {max_far:[..], '
|
||
'max_building_pct:[..], underground_pct:[..], min_parcel_area_m2:[..], max_floors:[..], '
|
||
'max_height_m:[..]}. Списки т.к. параметр повторяется per-ВРИ. headline-скаляры — агрегат.';
|
||
COMMENT ON COLUMN zone_regulation_cache.max_far IS
|
||
'Коэф. строит. использования ЗУ (КСИ) — max найденный (capacity best-case). NULL = не установлен';
|
||
COMMENT ON COLUMN zone_regulation_cache.min_parcel_area_m2 IS
|
||
'Мин. площадь ЗУ — НАИМЕНЬШИЙ из найденных минимумов (самый разрешительный). NULL = не установлен';
|
||
|
||
-- ── 2. Дедуп/UPSERT-ключ (city, zone_index) ────────────────────────────────────
|
||
-- Цель ON CONFLICT для сервиса C8b: re-fetch обновляет регламент той же зоны, не плодя дубли.
|
||
-- Guard по pg_constraint → idempotent re-apply (прецедент м.125/132).
|
||
DO $$
|
||
BEGIN
|
||
IF NOT EXISTS (
|
||
SELECT 1 FROM pg_constraint WHERE conname = 'uq_zone_regulation_cache'
|
||
) THEN
|
||
ALTER TABLE zone_regulation_cache
|
||
ADD CONSTRAINT uq_zone_regulation_cache
|
||
UNIQUE (city, zone_index);
|
||
END IF;
|
||
END
|
||
$$;
|
||
|
||
-- ── 3. Индексы ─────────────────────────────────────────────────────────────────
|
||
-- Главный read-path: lookup регламента по индексу зоны (UNIQUE уже покрывает (city, zone_index),
|
||
-- но частый запрос — по zone_index без city). Очередь refresh — по возрасту (fetched_at).
|
||
CREATE INDEX IF NOT EXISTS idx_zone_regulation_cache_zone_index
|
||
ON zone_regulation_cache (zone_index);
|
||
CREATE INDEX IF NOT EXISTS idx_zone_regulation_cache_fetched_at
|
||
ON zone_regulation_cache (fetched_at);
|
||
|
||
COMMIT;
|