-- Foreign-key constraints + helper VIEWs across DOM.RF + Rosreestr tables. -- Idempotent: bootstraps missing dimension rows first, then ALTER TABLE ... ADD CONSTRAINT IF NOT EXISTS. -- ───────────────────────────────────────────────────────────────────────── -- 1. BOOTSTRAP DIMENSION TABLES -- ───────────────────────────────────────────────────────────────────────── -- Add row 0 = РФ to domrf_regions (used as "all-Russia" placeholder by some tables) INSERT INTO domrf_regions (region_id, region_name, federal_district) VALUES (0, 'Российская Федерация', '_RF') ON CONFLICT (region_id) DO NOTHING; -- Backfill domrf_snapshots from every snapshot_date currently used in children INSERT INTO domrf_snapshots (snapshot_date, notes) SELECT DISTINCT s, 'auto-backfilled by 40_relations.sql' FROM ( SELECT snapshot_date AS s FROM domrf_developers_full UNION SELECT snapshot_date FROM domrf_escrow_banks UNION SELECT snapshot_date FROM domrf_guaranty_regions UNION SELECT snapshot_date FROM domrf_housing_summary UNION SELECT snapshot_date FROM domrf_planned_commissioning UNION SELECT snapshot_date FROM domrf_sold_out UNION SELECT snapshot_date FROM domrf_sold_out_by_year UNION SELECT snapshot_date FROM domrf_sold_out_by_progress UNION SELECT snapshot_date FROM domrf_mortgage_rates UNION SELECT snapshot_date FROM domrf_region_aggregates UNION SELECT snapshot_date FROM domrf_flat_area_distribution UNION SELECT snapshot_date FROM domrf_developer_aggregates UNION SELECT snapshot_date FROM domrf_launch_top UNION SELECT snapshot_date FROM domrf_launch_obj_class UNION SELECT snapshot_date FROM domrf_launch_monthly UNION SELECT snapshot_date FROM domrf_sold_ready_index UNION SELECT snapshot_date FROM domrf_sold_ready_breakdown UNION SELECT snapshot_date FROM domrf_sold_ready_dynamics UNION SELECT snapshot_date FROM domrf_project_finance UNION SELECT snapshot_date FROM domrf_commissioning UNION SELECT snapshot_date FROM domrf_mortgage_dashboard UNION SELECT snapshot_date FROM domrf_mortgage_details ) AS dates ON CONFLICT (snapshot_date) DO NOTHING; -- Helper to add a constraint only if it does not already exist -- (PostgreSQL ≥9.6: do via DO block; idempotent) DO $$ DECLARE rec RECORD; BEGIN -- ────────────────────────────────────────────────────────────────────── -- 2. SNAPSHOT FKs (every domrf_* with snapshot_date column) -- ────────────────────────────────────────────────────────────────────── FOR rec IN SELECT * FROM (VALUES ('domrf_developers_full', 'fk_snap_developers_full'), ('domrf_escrow_banks', 'fk_snap_escrow_banks'), ('domrf_guaranty_regions', 'fk_snap_guaranty_regions'), ('domrf_housing_summary', 'fk_snap_housing_summary'), ('domrf_planned_commissioning', 'fk_snap_planned_commissioning'), ('domrf_sold_out', 'fk_snap_sold_out'), ('domrf_sold_out_by_year', 'fk_snap_sold_out_by_year'), ('domrf_sold_out_by_progress', 'fk_snap_sold_out_by_progress'), ('domrf_mortgage_rates', 'fk_snap_mortgage_rates'), ('domrf_region_aggregates', 'fk_snap_region_aggregates'), ('domrf_flat_area_distribution', 'fk_snap_flat_area_distribution'), ('domrf_developer_aggregates', 'fk_snap_developer_aggregates'), ('domrf_launch_top', 'fk_snap_launch_top'), ('domrf_launch_obj_class', 'fk_snap_launch_obj_class'), ('domrf_launch_monthly', 'fk_snap_launch_monthly'), ('domrf_sold_ready_index', 'fk_snap_sold_ready_index'), ('domrf_sold_ready_breakdown', 'fk_snap_sold_ready_breakdown'), ('domrf_sold_ready_dynamics', 'fk_snap_sold_ready_dynamics'), ('domrf_project_finance', 'fk_snap_project_finance'), ('domrf_commissioning', 'fk_snap_commissioning'), ('domrf_mortgage_dashboard', 'fk_snap_mortgage_dashboard'), ('domrf_mortgage_details', 'fk_snap_mortgage_details'), ('domrf_raw_endpoints', 'fk_snap_raw_endpoints') ) AS t(tbl, cn) LOOP IF NOT EXISTS ( SELECT 1 FROM pg_constraint WHERE conname = rec.cn ) THEN EXECUTE format( 'ALTER TABLE %I ADD CONSTRAINT %I FOREIGN KEY (snapshot_date) REFERENCES domrf_snapshots(snapshot_date) ON UPDATE CASCADE', rec.tbl, rec.cn ); END IF; END LOOP; -- ────────────────────────────────────────────────────────────────────── -- 3. REGION FKs (domrf_region_aggregates / flat_area_distribution / developer_aggregates → domrf_regions) -- ────────────────────────────────────────────────────────────────────── IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'fk_region_aggregates_region') THEN ALTER TABLE domrf_region_aggregates ADD CONSTRAINT fk_region_aggregates_region FOREIGN KEY (region_id) REFERENCES domrf_regions(region_id) ON UPDATE CASCADE; END IF; IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'fk_flat_area_region') THEN ALTER TABLE domrf_flat_area_distribution ADD CONSTRAINT fk_flat_area_region FOREIGN KEY (region_id) REFERENCES domrf_regions(region_id) ON UPDATE CASCADE; END IF; IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'fk_developer_agg_region') THEN ALTER TABLE domrf_developer_aggregates ADD CONSTRAINT fk_developer_agg_region FOREIGN KEY (region_id) REFERENCES domrf_regions(region_id) ON UPDATE CASCADE; END IF; -- ────────────────────────────────────────────────────────────────────── -- 4. DEVELOPER FK (domrf_developer_aggregates → domrf_developers) -- ────────────────────────────────────────────────────────────────────── IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'fk_developer_agg_developer') THEN ALTER TABLE domrf_developer_aggregates ADD CONSTRAINT fk_developer_agg_developer FOREIGN KEY (developer_id) REFERENCES domrf_developers(developer_id) ON UPDATE CASCADE; END IF; END $$; -- ───────────────────────────────────────────────────────────────────────── -- 5. HELPER VIEWS -- ───────────────────────────────────────────────────────────────────────── -- v_region_master: одна строка на регион — все ключевые метрики на последний снапшот DROP VIEW IF EXISTS v_region_master CASCADE; CREATE VIEW v_region_master AS WITH latest AS (SELECT MAX(snapshot_date) AS d FROM domrf_snapshots) SELECT r.region_id, r.region_name, r.federal_district, -- Quartirografia totals (TOTAL row) rta.flat_count AS total_flats, rta.area_sqm AS total_area_sqm, -- Sold-out (DOM.RF PDF data) so.total_area_th_sqm AS sold_out_total_th_sqm, so.sold_pct AS sold_out_sold_pct, so.unsold_pct AS sold_out_unsold_pct, -- Sold-ready breakdown (Playwright API data) srb.square_sum AS sold_ready_square, srb.sold_perc AS sold_ready_sold_pct, srb.ready_perc AS sold_ready_ready_pct, srb.sold_ready_perc AS sold_ready_balance_pct, -- Project finance pf.rns_cnt AS project_finance_permits, pf.dev_cnt AS project_finance_developers, pf.guaranty_escrow_dev_cnt AS project_finance_escrow_devs, -- Guaranty (XLSX) g.fz214_developers AS guaranty_fz214_devs, g.escrow_developers AS guaranty_escrow_devs, -- Launch (запуски новых) (SELECT value FROM domrf_launch_top WHERE dim_type = 'region' AND metric_type = 'app' AND entity_id = r.region_id::text AND snapshot_date = (SELECT d FROM latest) LIMIT 1) AS launch_app_2026_sqm, (SELECT value FROM domrf_launch_top WHERE dim_type = 'region' AND metric_type = 'rnv' AND entity_id = r.region_id::text AND snapshot_date = (SELECT d FROM latest) LIMIT 1) AS launch_rnv_2026_sqm FROM domrf_regions r LEFT JOIN domrf_region_aggregates rta ON rta.region_id = r.region_id AND rta.room_count_type = 'TOTAL' AND rta.snapshot_date = (SELECT d FROM latest) LEFT JOIN domrf_sold_out so ON so.territory_name = r.region_name AND so.snapshot_date = (SELECT MAX(snapshot_date) FROM domrf_sold_out WHERE territory_name = r.region_name) LEFT JOIN domrf_sold_ready_breakdown srb ON srb.entity_key = r.region_name AND srb.chart_type = 'regionChart' AND srb.snapshot_date = (SELECT MAX(snapshot_date) FROM domrf_sold_ready_breakdown WHERE chart_type='regionChart') LEFT JOIN domrf_project_finance pf ON pf.subject_name = r.region_name AND pf.snapshot_date = (SELECT MAX(snapshot_date) FROM domrf_project_finance) LEFT JOIN domrf_guaranty_regions g ON g.territory_name = r.region_name AND g.snapshot_date = (SELECT MAX(snapshot_date) FROM domrf_guaranty_regions) WHERE r.region_id > 0 ORDER BY total_area_sqm DESC NULLS LAST; COMMENT ON VIEW v_region_master IS 'Один срез по региону: квартирография + распроданность (DOM.РФ + sold_out PDF) + проектное финансирование + запуски/ввод 2026'; -- v_developer_master: профиль застройщика (latest snapshot) DROP VIEW IF EXISTS v_developer_master CASCADE; CREATE VIEW v_developer_master AS WITH latest AS (SELECT MAX(snapshot_date) AS d FROM domrf_snapshots) SELECT d.developer_id, d.developer_name, -- Group-of-companies aggregates (from JSON dashboard) dat.flat_count AS group_flat_count, dat.area_sqm AS group_area_sqm, -- Per-юрлицо реестр (XLSX) df.area_thousand_sqm AS legal_area_th_sqm, df.permits_count AS legal_permits, df.houses_count AS legal_houses, df.flats_count AS legal_flats, df.market_share_pct AS market_share_pct, -- Запуски / ввод 2026 (из launch_top, dim=developer) (SELECT value FROM domrf_launch_top WHERE dim_type='developer' AND metric_type='app' AND entity_id = d.developer_id AND snapshot_date = (SELECT d FROM latest) LIMIT 1) AS launch_app_2026_sqm, (SELECT value FROM domrf_launch_top WHERE dim_type='developer' AND metric_type='rnv' AND entity_id = d.developer_id AND snapshot_date = (SELECT d FROM latest) LIMIT 1) AS launch_rnv_2026_sqm, -- Распроданность (devChart) (SELECT square_sum FROM domrf_sold_ready_breakdown WHERE chart_type='devChart' AND entity_key = d.developer_name AND snapshot_date = (SELECT MAX(snapshot_date) FROM domrf_sold_ready_breakdown WHERE chart_type='devChart') LIMIT 1) AS sold_ready_square, (SELECT sold_perc FROM domrf_sold_ready_breakdown WHERE chart_type='devChart' AND entity_key = d.developer_name AND snapshot_date = (SELECT MAX(snapshot_date) FROM domrf_sold_ready_breakdown WHERE chart_type='devChart') LIMIT 1) AS sold_ready_sold_pct FROM domrf_developers d LEFT JOIN domrf_developer_aggregates dat ON dat.developer_id = d.developer_id AND dat.room_count_type = 'TOTAL' AND dat.snapshot_date = (SELECT d FROM latest) LEFT JOIN domrf_developers_full df ON df.developer_name = d.developer_name AND df.snapshot_date = (SELECT MAX(snapshot_date) FROM domrf_developers_full WHERE developer_name = d.developer_name); COMMENT ON VIEW v_developer_master IS 'Профиль застройщика: реестр + квартирография по комнатностям + запуски/ввод + распроданность'; -- v_rosreestr_market_by_region — срез цен сделок и застройщиков по региону DROP VIEW IF EXISTS v_rosreestr_market_by_region CASCADE; CREATE VIEW v_rosreestr_market_by_region AS SELECT r.region_id, r.region_name, r.federal_district, rar.period_start_date, rar.doc_type, rar.deals_count, rar.median_price_sqm AS median_rub_sqm, rar.p10_price_sqm AS p10_rub_sqm, rar.p90_price_sqm AS p90_rub_sqm FROM domrf_regions r JOIN rr_agg_region rar ON rar.region_code = r.region_id WHERE rar.realestate_type_code = '002001003000' -- Помещения (квартиры) ORDER BY r.region_id, rar.period_start_date, rar.doc_type; COMMENT ON VIEW v_rosreestr_market_by_region IS 'Цены сделок Росреестра по регионам с DOM.РФ-именами регионов (квартиры)'; -- v_market_pulse_rf — РФ-уровень summary с разных источников за last snapshot DROP VIEW IF EXISTS v_market_pulse_rf CASCADE; CREATE VIEW v_market_pulse_rf AS SELECT -- Quartirografia (SELECT SUM(area_sqm)/1e6 FROM domrf_region_aggregates WHERE room_count_type='TOTAL' AND snapshot_date = (SELECT MAX(snapshot_date) FROM domrf_region_aggregates)) AS total_construction_mln_sqm, -- Sold-ready (SELECT square_sum/1e6 FROM domrf_sold_ready_index ORDER BY snapshot_date DESC LIMIT 1) AS sold_ready_total_mln_sqm, (SELECT sold_perc FROM domrf_sold_ready_index ORDER BY snapshot_date DESC LIMIT 1) AS sold_pct, (SELECT ready_perc FROM domrf_sold_ready_index ORDER BY snapshot_date DESC LIMIT 1) AS ready_pct, -- Прайс (SELECT avg_price_per_sqm FROM domrf_sold_out WHERE territory_name = 'Российская Федерация' ORDER BY snapshot_date DESC LIMIT 1) AS rf_avg_price_per_sqm, -- Ставки (SELECT primary_rate FROM domrf_mortgage_rates WHERE bank_name = 'Средневзвешенная' ORDER BY snapshot_date DESC LIMIT 1) AS market_primary_rate, (SELECT secondary_rate FROM domrf_mortgage_rates WHERE bank_name = 'Средневзвешенная' ORDER BY snapshot_date DESC LIMIT 1) AS market_secondary_rate, -- Реальные ставки (по выданным кредитам) (SELECT primary_credit_avg_rate FROM domrf_mortgage_dashboard ORDER BY snapshot_date DESC LIMIT 1) AS actual_primary_rate, (SELECT secondary_credit_avg_rate FROM domrf_mortgage_dashboard ORDER BY snapshot_date DESC LIMIT 1) AS actual_secondary_rate, -- Объём кредитования (SELECT total_credit_count FROM domrf_mortgage_dashboard ORDER BY snapshot_date DESC LIMIT 1) AS total_credit_count, (SELECT total_credit_amount/1e9 FROM domrf_mortgage_dashboard ORDER BY snapshot_date DESC LIMIT 1) AS total_credit_amount_bln_rub; COMMENT ON VIEW v_market_pulse_rf IS 'РФ pulse: объём строительства, распроданность, средняя цена, ставки (рыночные vs реальные), объём ипотеки';