-- 137_ekb_krt_sites.sql -- #1060 (Site Finder v2 / GG-форсайт) — реестр КРТ-площадок ЕКБ. -- -- Источник: https://xn--80acgfbsl1azdqr.xn--p1ai/дляработы/гиз/крт -- (екатеринбург.рф, SSR-страница, 15+ площадок). -- -- Реквизиты каждой площадки: застройщик, вид застройки (жилая/нежилая), -- параметры договора КРТ (номер/дата/цена аукциона), градпотенциал, площадь, -- список постановлений ПАГЕ. Геометрия отсутствует в источнике — join к -- opportunity_krt запланирован отдельно. -- -- Ключ: contract_number — присутствует у площадок с заключённым договором КРТ -- (большинство). Площадки без договора (ранние стадии «подготовки к аукциону») -- используют site_name как резервный ключ (UNIQUE(site_name) + partial UNIQUE -- по contract_number WHERE contract_number IS NOT NULL реализован через два -- раздельных индекса + DO-блоки ниже). -- -- Deploy: auto-applied deploy.yml через _schema_migrations (ровно один раз по NN). -- Idempotent (CREATE TABLE/INDEX IF NOT EXISTS + guarded DO-блоки по pg_constraint). -- Без INSERT — данные приходят из ekb_krt_sync Celery-воркера (#1060). -- -- Параллельный PR берёт номер 138 — этот файл 137 зарезервирован для этого PR. BEGIN; -- ── 1. Таблица ekb_krt_sites ───────────────────────────────────────────────── CREATE TABLE IF NOT EXISTS ekb_krt_sites ( id BIGSERIAL PRIMARY KEY, site_name TEXT NOT NULL, -- «N. КРТ жилой/нежилой застройки в районе ул. X» district TEXT, -- район/квартал (извлечён из названия best-effort) krt_kind TEXT, -- «жилой застройки» | «нежилой застройки» developer_name TEXT, -- застройщик из текста договора contract_number TEXT, -- «1-2/21.7-14» (NULL у незавершённых площадок) contract_date DATE, -- дата договора КРТ auction_start_price NUMERIC, -- начальная стоимость аукциона, руб. auction_final_price NUMERIC, -- цена по результатам торгов, руб. gradpotential_sqm NUMERIC, -- градостроительный потенциал, кв. м area_ha NUMERIC, -- площадь территории, га mkd_count INTEGER, -- всего многоквартирных домов (если есть) paga_decisions JSONB, -- [{date, number, title}] постановления ПАГЕ raw_block TEXT, -- исходный текст блока (для отладки/re-parse) source_url TEXT, -- URL страницы-источника fetched_at TIMESTAMPTZ NOT NULL DEFAULT now() ); COMMENT ON TABLE ekb_krt_sites IS 'Реестр КРТ-площадок Екатеринбурга (#1060, GG-форсайт). ' 'Источник: екатеринбург.рф/дляработы/гиз/крт. ' 'Обновляется еженедельно воркером ekb_krt_sync. ' 'Геометрия отсутствует — join к opportunity_krt планируется отдельно.'; COMMENT ON COLUMN ekb_krt_sites.contract_number IS 'Номер договора КРТ («1-2/21.7-14»). NULL у площадок на стадии подготовки к аукциону.'; COMMENT ON COLUMN ekb_krt_sites.paga_decisions IS 'JSON-массив постановлений ПАГЕ: [{date: «DD.MM.YYYY», number: «NNN», title: «...»}]. ' 'Все постановления из блока, включая об утверждении ППТ/ПМТ.'; COMMENT ON COLUMN ekb_krt_sites.krt_kind IS '«жилой застройки» или «нежилой застройки» — вид КРТ из названия площадки.'; -- ── 2. UNIQUE по site_name — надёжный ключ для уникальности (присутствует всегда) ── -- Служит ON CONFLICT target для UPSERT воркера. contract_number NULL у ранних стадий, -- поэтому site_name выбран основным дедуп-ключом. Дополнительно: уникальный индекс -- по contract_number WHERE NOT NULL — для быстрого lookup по номеру договора. DO $$ BEGIN IF NOT EXISTS ( SELECT 1 FROM pg_constraint WHERE conname = 'uq_ekb_krt_sites_site_name' ) THEN ALTER TABLE ekb_krt_sites ADD CONSTRAINT uq_ekb_krt_sites_site_name UNIQUE (site_name); END IF; END $$; -- ── 3. Partial unique index по contract_number (где не NULL) ───────────────── -- Не через DO-блок + ALTER (partial UNIQUE через ALTER ADD CONSTRAINT не поддерживает -- WHERE clause в PostgreSQL). Вместо этого — CREATE UNIQUE INDEX IF NOT EXISTS с WHERE. CREATE UNIQUE INDEX IF NOT EXISTS uix_ekb_krt_sites_contract_number ON ekb_krt_sites (contract_number) WHERE contract_number IS NOT NULL; -- ── 4. Индексы ─────────────────────────────────────────────────────────────── -- Поиск по виду застройки (жилая/нежилая). CREATE INDEX IF NOT EXISTS idx_ekb_krt_sites_krt_kind ON ekb_krt_sites (krt_kind); -- Поиск по застройщику (JOIN с domrf_kn / анализ по девелоперу). CREATE INDEX IF NOT EXISTS idx_ekb_krt_sites_developer ON ekb_krt_sites (developer_name) WHERE developer_name IS NOT NULL; -- Свежесть данных / ORDER BY fetched_at для мониторинга. CREATE INDEX IF NOT EXISTS idx_ekb_krt_sites_fetched_at ON ekb_krt_sites (fetched_at DESC); COMMIT;