-- 138_ekb_ppt_tep.sql -- #1085 (Site Finder v2 / GG-форсайт #1067) — ТЭП из ППТ/ПМТ-пояснительных записок. -- -- Что/зачем: структурированный контент пояснительных записок ППТ/ПМТ (PDF). -- Три логических таблицы документа: -- Табл.11 — баланс территории по зонам (га + %); -- Табл.12 — основные ТЭП (площади, этажность, плотность, инфраструктура); -- Табл.13 — очередность строительства (объект/зона/срок/финансирование). -- -- Линковка к геометрии: doc_ref сопоставляется с planning_projects.source_key / -- doc_full_name (данные ППТ/ПМТ уже в БД через WFS, #1085). Прямой FK не ставим -- из-за best-effort nature ссылок (одна пояснительная → несколько границ и наоборот). -- -- Grain: одна строка = один PDF-документ пояснительной записки. -- UNIQUE(doc_ref) — не допускает дублей при повторном ingest. -- -- Deploy: auto-applied deploy.yml через _schema_migrations (ровно один раз по NN). -- Idempotent: CREATE TABLE/INDEX IF NOT EXISTS + guarded DO-блок для UNIQUE. -- Без INSERT (данные приходят из ekb_ppt_tep_sync Celery-воркера). BEGIN; -- ── 1. Таблица ekb_ppt_tep ──────────────────────────────────────────────────── CREATE TABLE IF NOT EXISTS ekb_ppt_tep ( id BIGSERIAL PRIMARY KEY, doc_ref TEXT NOT NULL, -- ид/ссылка документа ППТ (для join к planning_projects) project_type TEXT NOT NULL, -- 'ppt' | 'pmt' zone_balance JSONB, -- Табл.11: баланс территории по зонам tep JSONB, -- Табл.12: основные ТЭП phasing JSONB, -- Табл.13: очередность строительства raw_tables JSONB, -- сырые таблицы для отладки/re-parse source_pdf TEXT, -- URL или путь к исходному PDF fetched_at TIMESTAMPTZ NOT NULL DEFAULT now() ); COMMENT ON TABLE ekb_ppt_tep IS 'ТЭП из пояснительных записок ППТ/ПМТ (#1085, GG-форсайт #1067). ' '1 строка = 1 PDF-документ. ' 'Три JSONB-поля: zone_balance (Табл.11), tep (Табл.12), phasing (Табл.13). ' 'Линковка к геометрии: doc_ref → planning_projects.source_key.'; COMMENT ON COLUMN ekb_ppt_tep.doc_ref IS 'Идентификатор/ссылка документа ППТ. Для сопоставления с ' 'planning_projects.source_key или doc_full_name.'; COMMENT ON COLUMN ekb_ppt_tep.project_type IS '''ppt'' — проект планировки территории; ''pmt'' — проект межевания территории.'; COMMENT ON COLUMN ekb_ppt_tep.zone_balance IS 'Баланс территории по зонам (Табл.11): [{no, zone_name, existing_ha, project_ha, ...}]. ' 'Пустой массив [] если таблица не найдена в PDF.'; COMMENT ON COLUMN ekb_ppt_tep.tep IS 'Основные ТЭП (Табл.12): [{no, indicator, unit, current, target}]. ' 'Пустой массив [] если таблица не найдена.'; COMMENT ON COLUMN ekb_ppt_tep.phasing IS 'Очередность строительства (Табл.13): [{phase, composition, zone, area_ha, ' 'funding, program}]. Пустой массив [] если таблица не найдена.'; COMMENT ON COLUMN ekb_ppt_tep.raw_tables IS 'Сырые таблицы из pdfplumber для отладки: [{page, table_index, rows: [[...]]}]. '; -- ── 2. UNIQUE(doc_ref) — дедуп-ключ для UPSERT воркера ────────────────────── -- Guard по pg_constraint → idempotent re-apply (прецедент: м.132, 136, 137). DO $$ BEGIN IF NOT EXISTS ( SELECT 1 FROM pg_constraint WHERE conname = 'uq_ekb_ppt_tep_doc_ref' ) THEN ALTER TABLE ekb_ppt_tep ADD CONSTRAINT uq_ekb_ppt_tep_doc_ref UNIQUE (doc_ref); END IF; END $$; -- ── 3. Индексы ──────────────────────────────────────────────────────────────── -- Фильтр по типу проекта (ppt/pmt). CREATE INDEX IF NOT EXISTS idx_ekb_ppt_tep_project_type ON ekb_ppt_tep (project_type); -- Свежесть / ORDER BY fetched_at для мониторинга. CREATE INDEX IF NOT EXISTS idx_ekb_ppt_tep_fetched_at ON ekb_ppt_tep (fetched_at DESC); -- GIN-индекс на zone_balance для поиска по зонам (follow-up: @> queries). CREATE INDEX IF NOT EXISTS idx_ekb_ppt_tep_zone_balance_gin ON ekb_ppt_tep USING GIN (zone_balance) WHERE zone_balance IS NOT NULL; COMMIT;