feat(db): bulk cadastre schema — 7 tables + cadastre_jobs (#168 PR1/5) #169

Merged
lekss361 merged 2 commits from feat/cadastre-bulk-schema into main 2026-05-15 10:05:26 +00:00
Showing only changes of commit b4bc70489b - Show all commits

View file

@ -32,7 +32,9 @@
-- J. Migrate data: cad_parcels_geom -> cad_parcels
-- K. Backup + replace: cad_buildings -> cad_buildings_old_apr26 -> DROP + rename
-- L. Restore FK + views
-- M. DROP cad_parcels_geom (after data migrated)
-- M. Replace cad_parcels_geom TABLE with back-compat VIEW over cad_parcels
-- N. COMMENT ON TABLE for all 8 tables
-- O. set_updated_at triggers for all tables with updated_at column
--
-- Idempotency: safe to re-run — all DDL uses IF NOT EXISTS / IF EXISTS guards.
-- Data migrations use INSERT ... ON CONFLICT DO NOTHING.
@ -293,7 +295,11 @@ CREATE TABLE IF NOT EXISTS cadastre_jobs (
error TEXT,
phase_state JSONB,
raw_targets JSONB,
CONSTRAINT cadastre_jobs_pkey PRIMARY KEY (job_id)
CONSTRAINT cadastre_jobs_pkey PRIMARY KEY (job_id),
CONSTRAINT cadastre_jobs_status_chk
CHECK (status IN (
'queued', 'running', 'done', 'failed', 'zombie', 'cancelled', 'paused'
))
);
-- ===========================================================================
@ -499,14 +505,13 @@ BEGIN
ALTER TABLE cad_buildings_new RENAME TO cad_buildings;
-- Rename indexes to drop the _new suffix
ALTER INDEX IF EXISTS cad_buildings_new_pkey RENAME TO cad_buildings_pkey;
-- Note: ALTER INDEX RENAME on a PK index also renames the constraint
-- automatically in PG 16. No separate RENAME CONSTRAINT needed.
ALTER INDEX IF EXISTS cad_buildings_new_geom_gist RENAME TO cad_buildings_geom_gist;
ALTER INDEX IF EXISTS cad_buildings_new_quarter_idx RENAME TO cad_buildings_quarter_idx;
ALTER INDEX IF EXISTS cad_buildings_new_objdoc_idx RENAME TO cad_buildings_objdoc_idx;
ALTER INDEX IF EXISTS cad_buildings_new_complex_idx RENAME TO cad_buildings_complex_idx;
ALTER INDEX IF EXISTS cad_buildings_new_purpose_idx RENAME TO cad_buildings_purpose_idx;
-- Rename constraint
ALTER TABLE cad_buildings
RENAME CONSTRAINT cad_buildings_new_pkey TO cad_buildings_pkey;
END IF;
END $$;
@ -627,21 +632,159 @@ SELECT
FROM complexes c;
-- ===========================================================================
-- M. DROP cad_parcels_geom (original table — data migrated to cad_parcels)
-- Only dropped if cad_parcels exists and has at least as many rows.
-- Guard: skip drop if the new table doesn't exist yet (paranoia).
-- M. Replace cad_parcels_geom TABLE with a back-compat VIEW over cad_parcels.
-- Original table had exactly: cad_num, geom, raw_props, fetched_at.
-- The view preserves the same 4-column signature so that any legacy callers
-- (e.g. site-finder analyze fallback) continue to work without code changes.
-- Bulk PR2/5 will switch callers to cad_parcels directly, after which this
-- view can be dropped.
-- ===========================================================================
DO $$
BEGIN
-- Drop the old table only if cad_parcels (migration target) already exists.
-- Guard prevents accidental drop if J INSERT was skipped on re-run.
IF EXISTS (
SELECT 1 FROM information_schema.tables
WHERE table_schema = 'public' AND table_name = 'cad_parcels'
) AND EXISTS (
SELECT 1 FROM information_schema.tables
WHERE table_schema = 'public' AND table_name = 'cad_parcels_geom'
WHERE table_schema = 'public'
AND table_name = 'cad_parcels_geom'
AND table_type = 'BASE TABLE'
) THEN
DROP TABLE cad_parcels_geom;
END IF;
END $$;
-- Create back-compat VIEW (idempotent via CREATE OR REPLACE).
-- Matches original column order and types exactly.
CREATE OR REPLACE VIEW cad_parcels_geom AS
SELECT
cad_num,
geom,
raw_props,
fetched_at
FROM cad_parcels;
COMMENT ON VIEW cad_parcels_geom IS
'Back-compat shim: preserves original 4-column signature (cad_num, geom, '
'raw_props, fetched_at) for legacy site-finder analyze fallback. '
'Bulk PR2/5 will migrate callers to cad_parcels directly; drop this view then.';
-- ===========================================================================
-- N. COMMENT ON TABLE for all 8 tables
-- ===========================================================================
COMMENT ON TABLE cad_parcels IS
'Земельные участки ЕГРН из NSPD (categoryId 36368, EKB кадастровый округ 66:41:*). '
'Широкая схема с полными атрибутами NSPD + геометрия. Заменяет cad_parcels_geom.';
COMMENT ON TABLE cad_buildings IS
'Здания ЕГРН из NSPD (categoryId 36369). Широкая схема: objdoc_id для '
'tab-group-data lookup, complex_id FK на complexes, cultural_heritage_object BOOLEAN.';
COMMENT ON TABLE cad_constructions IS
'Сооружения из NSPD (categoryId 36383). Объединяет Сооружения и Сооружения ЕГРН '
'(is_egrn=TRUE). Полный набор технических параметров (высота, объём, протяжённость).';
COMMENT ON TABLE cad_oncs IS
'Объекты незавершённого строительства из NSPD (categoryId 36384, EKB 66:41:*).';
COMMENT ON TABLE cad_enk IS
'Единые недвижимые комплексы из NSPD (categoryId 39663, EKB 66:41:*).';
COMMENT ON TABLE cad_zouit IS
'Зоны с особыми условиями использования территории из NSPD '
'(categoryIds 36940 / 469039 / 469040 / 469042, все в одной таблице). '
'Натуральный ключ: (reg_numb_border, category_id) — один border может входить '
'в несколько категорий.';
COMMENT ON TABLE cad_quarter_stats IS
'Агрегированная статистика кадастрового квартала (1-to-1 с cad_quarters_geom). '
'Заполняется bulk-harvest worker''ом: счётчики объектов, суммы площадей, кадастровая '
'стоимость, real_srid ответа NSPD.';
COMMENT ON TABLE cadastre_jobs IS
'Resumable saga state для bulk harvest runs (один job = один кадастровый квартал '
'или batch). Зеркалит паттерн nspd_geo_jobs. heartbeat_at обновляется worker''ом '
'каждые N секунд; zombie-детектор переводит зависшие jobs в статус zombie.';
-- ===========================================================================
-- O. set_updated_at triggers for all tables with updated_at column.
-- set_updated_at() function already exists in this DB (verified).
-- CREATE OR REPLACE TRIGGER is not available in PG 16 — use DO guard.
-- ===========================================================================
DO $$ BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_trigger WHERE tgname = 'cad_parcels_set_updated_at'
) THEN
CREATE TRIGGER cad_parcels_set_updated_at
BEFORE UPDATE ON cad_parcels
FOR EACH ROW EXECUTE FUNCTION set_updated_at();
END IF;
END $$;
DO $$ BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_trigger WHERE tgname = 'cad_buildings_set_updated_at'
) THEN
CREATE TRIGGER cad_buildings_set_updated_at
BEFORE UPDATE ON cad_buildings
FOR EACH ROW EXECUTE FUNCTION set_updated_at();
END IF;
END $$;
DO $$ BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_trigger WHERE tgname = 'cad_constructions_set_updated_at'
) THEN
CREATE TRIGGER cad_constructions_set_updated_at
BEFORE UPDATE ON cad_constructions
FOR EACH ROW EXECUTE FUNCTION set_updated_at();
END IF;
END $$;
DO $$ BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_trigger WHERE tgname = 'cad_oncs_set_updated_at'
) THEN
CREATE TRIGGER cad_oncs_set_updated_at
BEFORE UPDATE ON cad_oncs
FOR EACH ROW EXECUTE FUNCTION set_updated_at();
END IF;
END $$;
DO $$ BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_trigger WHERE tgname = 'cad_enk_set_updated_at'
) THEN
CREATE TRIGGER cad_enk_set_updated_at
BEFORE UPDATE ON cad_enk
FOR EACH ROW EXECUTE FUNCTION set_updated_at();
END IF;
END $$;
DO $$ BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_trigger WHERE tgname = 'cad_zouit_set_updated_at'
) THEN
CREATE TRIGGER cad_zouit_set_updated_at
BEFORE UPDATE ON cad_zouit
FOR EACH ROW EXECUTE FUNCTION set_updated_at();
END IF;
END $$;
DO $$ BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_trigger WHERE tgname = 'cad_quarter_stats_set_updated_at'
) THEN
CREATE TRIGGER cad_quarter_stats_set_updated_at
BEFORE UPDATE ON cad_quarter_stats
FOR EACH ROW EXECUTE FUNCTION set_updated_at();
END IF;
END $$;
-- cadastre_jobs intentionally excluded — heartbeat_at is the liveness mechanism,
-- not updated_at (which the table does not have).
COMMIT;