-- 136_land_reservation.sql -- #1091 / #1062 (ИРД-5, «GG-форсайт» #1067) — хранилище ЗУ под изъятие/резервирование. -- -- Геометрии зон изъятия/резервирования НЕТ ни в одном spatial-источнике (НСПД/геопортал/ГИСОГД -- проверены пусто, vault research 2026-06). Перечень ЗУ существует ТОЛЬКО текстом в постановлениях -- (PDF, pravo.gov66.ru / ПАГЕ). Геометрию получаем join'ом кад-номера к cad_parcels.geom — это -- покрывает read-side analyse без хранения геометрии здесь. -- -- Что/зачем: per-parcel флаг «в действующем постановлении №X об изъятии/резервировании» + ссылка -- на документ. Парсер текста постановлений (page_reservation_parser.py) извлекает кад-номера и -- метаданные акта; ingest-task пишет сюда; reservation_lookup.py читает для analyze. -- -- Grain: одна строка = один кад-номер в одном акте (cad_num, act_number — дедуп-ключ). -- Источник: pravo.gov66.ru PDF-постановления (seed: 509-ПП Правительства СО, ул. Татищева ЕКБ). -- -- Deploy: auto-applied deploy.yml через _schema_migrations (ровно один раз по NN). Idempotent -- (ре-apply дважды чисто): CREATE TABLE/INDEX IF NOT EXISTS + guarded DO-блок для UNIQUE -- (constraint нельзя добавить дважды → guard по pg_constraint, прецедент м.132). -- НИКАКИХ строк здесь не вставляется (данные приходят из reservation_ingest task). BEGIN; -- ── 1. Таблица land_reservation ─────────────────────────────────────────────── CREATE TABLE IF NOT EXISTS land_reservation ( id BIGSERIAL PRIMARY KEY, cad_num TEXT NOT NULL, -- кадастровый номер ЗУ (66:41:XXXXXXX:NN) reservation_kind TEXT NOT NULL, -- 'изъятие' | 'резервирование' basis_act TEXT NOT NULL, -- «Постановление Правительства СО № 509-ПП» act_number TEXT, -- «509-ПП» (None если не распарсилось) act_date DATE, -- дата акта (NULL если не распарсилось) purpose TEXT, -- цель (реконструкция ул. Татищева, ...) doc_url TEXT, -- ссылка на PDF/страницу source TEXT NOT NULL DEFAULT 'page_pdf', -- источник: page_pdf | ... is_active BOOLEAN NOT NULL DEFAULT true, -- актуальное постановление raw_excerpt TEXT, -- ±120 символов вокруг первого кад-номера fetched_at timestamptz NOT NULL DEFAULT now() ); COMMENT ON TABLE land_reservation IS 'ЗУ под изъятие/резервирование (#1091/#1062, GG-форсайт #1067). 1 строка = 1 кад-номер ' 'в 1 постановлении. Геометрия не хранится — join к cad_parcels.geom на read-side. ' 'Парсер page_reservation_parser.py извлекает кад-номера из PDF-постановлений; ' 'reservation_lookup.py читает для analyze.'; COMMENT ON COLUMN land_reservation.cad_num IS 'Кадастровый номер ЗУ вида 66:41:XXXXXXX:NN'; COMMENT ON COLUMN land_reservation.reservation_kind IS 'изъятие | резервирование'; COMMENT ON COLUMN land_reservation.basis_act IS 'Читаемое описание акта-основания'; COMMENT ON COLUMN land_reservation.act_number IS 'Номер постановления (509-ПП, NNN-ПА и т.п.)'; COMMENT ON COLUMN land_reservation.raw_excerpt IS '±120 симв. вокруг первого кад-номера; debug/верификация'; -- ── 2. Дедуп/UPSERT-ключ (cad_num, act_number) ─────────────────────────────── -- Guard по pg_constraint → idempotent re-apply (constraint нельзя добавить дважды). -- Прецедент: м.132 (uq_ird_overlays_feature). DO $$ BEGIN IF NOT EXISTS ( SELECT 1 FROM pg_constraint WHERE conname = 'uq_land_reservation_cad_act' ) THEN ALTER TABLE land_reservation ADD CONSTRAINT uq_land_reservation_cad_act UNIQUE (cad_num, act_number); END IF; END $$; -- ── 3. Индексы ──────────────────────────────────────────────────────────────── -- Основной read-path: lookup по кад-номеру (reservation_lookup.py → analyze). CREATE INDEX IF NOT EXISTS idx_land_reservation_cad_num ON land_reservation (cad_num); -- Фильтр активных постановлений (is_active=true — рабочий подмножество). CREATE INDEX IF NOT EXISTS idx_land_reservation_active ON land_reservation (cad_num) WHERE is_active; -- Lookup по номеру акта (cross-ref / ручные проверки). CREATE INDEX IF NOT EXISTS idx_land_reservation_act_number ON land_reservation (act_number) WHERE act_number IS NOT NULL; COMMIT;