chore: initialize insar management system v2

This commit is contained in:
2026-04-14 13:16:01 +08:00
commit ecc72ec9cd
361 changed files with 2142522 additions and 0 deletions
@@ -0,0 +1,29 @@
-- Migration: Create st_intersection_agg function for efficient geometry intersection
-- This function reduces a set of geometries to their geometric intersection.
-- If no geometries are provided, it returns NULL.
-- Drop existing aggregate and function if they exist (for idempotency)
DROP AGGREGATE IF EXISTS st_intersection_agg(geometry);
DROP FUNCTION IF EXISTS st_intersection_agg(geometry, geometry);
-- Create the aggregation function
CREATE OR REPLACE FUNCTION st_intersection_agg(g1 geometry, g2 geometry)
RETURNS geometry AS
$$
SELECT CASE WHEN g1 IS NULL THEN g2
WHEN g2 IS NULL THEN g1
ELSE ST_Intersection(g1, g2) END;
$$
LANGUAGE SQL;
COMMENT ON FUNCTION st_intersection_agg(geometry, geometry) IS
'Aggregates a set of geometries by computing the intersection pairwise. Returns NULL if input is empty.';
-- Create aggregate wrapper for single-argument usage in SQL queries
CREATE AGGREGATE st_intersection_agg(geometry) (
SFUNC = st_intersection_agg,
STYPE = geometry
);
COMMENT ON AGGREGATE st_intersection_agg(geometry) IS
'Aggregates a set of geometries by computing the intersection pairwise.';
@@ -0,0 +1,319 @@
-- Migration: PostGIS helper functions and views for InSAR workflows
-- Note: keep this file UTF-8 without BOM to avoid SQL parser issues.
CREATE EXTENSION IF NOT EXISTS postgis;
-- =====================================================
-- Function: find_dinsar_pairs
-- Purpose : Find candidate D-InSAR pairs with spatial/time/overlap constraints
-- =====================================================
CREATE OR REPLACE FUNCTION find_dinsar_pairs(
p_time_baseline_min INTEGER,
p_time_baseline_max INTEGER,
p_spatial_baseline_max_meters NUMERIC,
p_overlap_threshold NUMERIC,
p_start_date TEXT DEFAULT NULL,
p_aoi_geom GEOMETRY DEFAULT NULL,
p_require_orbit_data BOOLEAN DEFAULT TRUE,
p_require_same_imaging_mode BOOLEAN DEFAULT TRUE,
p_require_same_polarization BOOLEAN DEFAULT TRUE,
p_aoi_overlap_threshold NUMERIC DEFAULT NULL
)
RETURNS TABLE (
master_id INTEGER,
slave_id INTEGER,
master_imaging_date TEXT,
slave_imaging_date TEXT,
time_baseline_days INTEGER,
spatial_baseline_meters NUMERIC,
overlap_ratio NUMERIC
)
LANGUAGE plpgsql
AS $$
BEGIN
RETURN QUERY
WITH candidate_pairs AS (
SELECT
m.id AS master_id,
s.id AS slave_id,
m.imaging_date::text AS master_imaging_date,
s.imaging_date::text AS slave_imaging_date,
ABS(to_date(s.imaging_date, 'YYYYMMDD') - to_date(m.imaging_date, 'YYYYMMDD')) AS time_baseline_days,
ST_DistanceSphere(ST_Centroid(m.geom), ST_Centroid(s.geom))::numeric AS spatial_baseline_meters,
(
ST_Area(ST_Intersection(m.geom, s.geom)::geography) /
NULLIF(GREATEST(ST_Area(m.geom::geography), ST_Area(s.geom::geography)), 0)
)::numeric AS overlap_ratio
FROM radar_data m
JOIN radar_data s ON ST_Intersects(m.geom, s.geom)
WHERE m.id < s.id
AND m.orbit_direction = s.orbit_direction
AND m.satellite = s.satellite
AND (NOT p_require_orbit_data OR (m.has_orbit_data = true AND s.has_orbit_data = true))
AND (
NOT p_require_same_imaging_mode OR (
m.imaging_mode IS NOT NULL AND m.imaging_mode <> ''
AND s.imaging_mode IS NOT NULL AND s.imaging_mode <> ''
AND m.imaging_mode = s.imaging_mode
)
)
AND (
NOT p_require_same_polarization OR (
m.polarization IS NOT NULL AND m.polarization <> ''
AND s.polarization IS NOT NULL AND s.polarization <> ''
AND m.polarization = s.polarization
)
)
AND (p_start_date IS NULL OR (m.imaging_date >= p_start_date AND s.imaging_date >= p_start_date))
AND (p_aoi_geom IS NULL OR (ST_Intersects(m.geom, p_aoi_geom) AND ST_Intersects(s.geom, p_aoi_geom)))
AND (
p_aoi_geom IS NULL OR p_aoi_overlap_threshold IS NULL OR (
ST_Area(ST_Intersection(m.geom, p_aoi_geom)::geography) /
NULLIF(ST_Area(p_aoi_geom::geography), 0) >= p_aoi_overlap_threshold
AND ST_Area(ST_Intersection(s.geom, p_aoi_geom)::geography) /
NULLIF(ST_Area(p_aoi_geom::geography), 0) >= p_aoi_overlap_threshold
)
)
AND (m.imaging_date ~ '^[0-9]{8}$' AND s.imaging_date ~ '^[0-9]{8}$')
)
SELECT
cp.master_id,
cp.slave_id,
cp.master_imaging_date,
cp.slave_imaging_date,
cp.time_baseline_days,
cp.spatial_baseline_meters,
cp.overlap_ratio
FROM candidate_pairs cp
WHERE cp.time_baseline_days BETWEEN p_time_baseline_min AND p_time_baseline_max
AND cp.spatial_baseline_meters <= p_spatial_baseline_max_meters
AND cp.overlap_ratio >= p_overlap_threshold
ORDER BY cp.overlap_ratio DESC;
END;
$$;
-- =====================================================
-- Function: calculate_coverage_overlap
-- Purpose : Compute overlap area and ratio between two images
-- =====================================================
CREATE OR REPLACE FUNCTION calculate_coverage_overlap(
p_image1_id INTEGER,
p_image2_id INTEGER
)
RETURNS TABLE (
overlap_area NUMERIC,
overlap_ratio NUMERIC,
intersection_geom GEOMETRY
)
LANGUAGE plpgsql
AS $$
BEGIN
RETURN QUERY
SELECT
ST_Area(ST_Intersection(r1.geom, r2.geom)::geography) AS overlap_area,
ST_Area(ST_Intersection(r1.geom, r2.geom)::geography) /
NULLIF(GREATEST(ST_Area(r1.geom::geography), ST_Area(r2.geom::geography)), 0) AS overlap_ratio,
ST_Intersection(r1.geom, r2.geom) AS intersection_geom
FROM radar_data r1
CROSS JOIN radar_data r2
WHERE r1.id = p_image1_id AND r2.id = p_image2_id;
END;
$$;
-- =====================================================
-- Function: find_common_overlap_area
-- Purpose : Find common overlap area among a set of images
-- =====================================================
CREATE OR REPLACE FUNCTION find_common_overlap_area(
p_image_ids INTEGER[]
)
RETURNS TABLE (
common_area NUMERIC,
common_geom GEOMETRY
)
LANGUAGE plpgsql
AS $$
DECLARE
v_first_geom GEOMETRY;
v_result_geom GEOMETRY;
BEGIN
IF p_image_ids IS NULL OR array_length(p_image_ids, 1) IS NULL THEN
RETURN;
END IF;
SELECT geom INTO v_first_geom
FROM radar_data
WHERE id = p_image_ids[1];
v_result_geom := v_first_geom;
FOR i IN 2..array_length(p_image_ids, 1) LOOP
SELECT ST_Intersection(v_result_geom, geom) INTO v_result_geom
FROM radar_data
WHERE id = p_image_ids[i];
IF v_result_geom IS NULL OR ST_IsEmpty(v_result_geom) THEN
EXIT;
END IF;
END LOOP;
IF v_result_geom IS NULL OR ST_IsEmpty(v_result_geom) THEN
RETURN;
END IF;
RETURN QUERY
SELECT
ST_Area(v_result_geom::geography) AS common_area,
v_result_geom AS common_geom;
END;
$$;
-- =====================================================
-- View: radar_pairs_view (helper view)
-- =====================================================
CREATE OR REPLACE VIEW radar_pairs_view AS
SELECT
row_number() OVER (ORDER BY m.id, s.id) AS id,
m.id AS master_id,
s.id AS slave_id,
m.imaging_date AS master_date,
s.imaging_date AS slave_date,
CASE
WHEN m.imaging_date ~ '^[0-9]{8}$' AND s.imaging_date ~ '^[0-9]{8}$'
THEN ABS(to_date(s.imaging_date, 'YYYYMMDD') - to_date(m.imaging_date, 'YYYYMMDD'))
ELSE NULL
END AS time_baseline_days,
ST_DistanceSphere(ST_Centroid(m.geom), ST_Centroid(s.geom)) AS spatial_baseline_meters,
m.geom AS geom1,
s.geom AS geom2,
m.geom AS master_geom,
s.geom AS slave_geom,
ST_Area(ST_Intersection(m.geom, s.geom)::geography) AS overlap_area
FROM radar_data m
JOIN radar_data s ON ST_Intersects(m.geom, s.geom)
WHERE m.id < s.id;
-- =====================================================
-- Function: optimize_task_selection
-- Purpose : Select pairs with spatial coverage diversity
-- =====================================================
CREATE OR REPLACE FUNCTION optimize_task_selection(
p_pair_ids INTEGER[],
p_penalty_factor NUMERIC DEFAULT 0.3
)
RETURNS TABLE (
selected_pair_id INTEGER,
score NUMERIC
)
LANGUAGE plpgsql
AS $$
DECLARE
v_selected_ids INTEGER[];
v_total_geom GEOMETRY := NULL;
v_pair RECORD;
v_inter_geom GEOMETRY;
v_new_area NUMERIC;
v_overlap_area NUMERIC;
v_score NUMERIC;
BEGIN
FOR v_pair IN
SELECT id, geom1, geom2, overlap_area
FROM radar_pairs_view
WHERE id = ANY(p_pair_ids)
ORDER BY overlap_area DESC
LOOP
v_inter_geom := ST_Intersection(v_pair.geom1, v_pair.geom2);
IF v_total_geom IS NULL THEN
v_total_geom := v_inter_geom;
v_selected_ids := array_append(v_selected_ids, v_pair.id);
ELSE
v_new_area := ST_Area(ST_Difference(v_inter_geom, v_total_geom));
v_overlap_area := ST_Area(ST_Intersection(v_inter_geom, v_total_geom));
v_score := v_new_area - (v_overlap_area * p_penalty_factor);
IF v_score > 0 THEN
v_total_geom := ST_Union(v_total_geom, v_inter_geom);
v_selected_ids := array_append(v_selected_ids, v_pair.id);
END IF;
END IF;
END LOOP;
RETURN QUERY
SELECT unnest(v_selected_ids)::INTEGER AS selected_pair_id, 0 AS score;
END;
$$;
-- =====================================================
-- Function: find_hazard_points_in_area
-- Purpose : Find hazard points within a geometry
-- =====================================================
CREATE OR REPLACE FUNCTION find_hazard_points_in_area(
p_area_geom GEOMETRY
)
RETURNS TABLE (
id INTEGER,
tybh TEXT,
hazard_type TEXT,
hazard_name TEXT,
city TEXT,
county TEXT,
longitude NUMERIC,
latitude NUMERIC
)
LANGUAGE plpgsql
AS $$
BEGIN
RETURN QUERY
SELECT
hp.id,
hp.tybh,
hp.hazard_type,
hp.hazard_name,
hp.city,
hp.county,
hp.longitude,
hp.latitude
FROM hazard_points hp
WHERE ST_Covers(p_area_geom, hp.geom);
END;
$$;
-- =====================================================
-- Table + Function: spatial query logs
-- =====================================================
CREATE TABLE IF NOT EXISTS spatial_query_logs (
id SERIAL PRIMARY KEY,
query_type TEXT,
execution_time_ms NUMERIC,
result_count INTEGER,
created_at TIMESTAMP DEFAULT NOW()
);
CREATE OR REPLACE FUNCTION log_spatial_query(
p_query_type TEXT,
p_execution_time_ms NUMERIC,
p_result_count INTEGER
)
RETURNS void
LANGUAGE plpgsql
AS $$
BEGIN
INSERT INTO spatial_query_logs (query_type, execution_time_ms, result_count)
VALUES (p_query_type, p_execution_time_ms, p_result_count);
END;
$$;
-- =====================================================
-- Indexes (redundant with ORM, kept for reference)
-- =====================================================
CREATE INDEX IF NOT EXISTS idx_radar_data_geom ON radar_data USING GIST (geom);
CREATE INDEX IF NOT EXISTS idx_dinsar_results_geom ON dinsar_results USING GIST (geom);
CREATE INDEX IF NOT EXISTS idx_hazard_points_geom ON hazard_points USING GIST (geom);
-- =====================================================
-- Optional grants (adjust to your app user)
-- =====================================================
-- GRANT EXECUTE ON FUNCTION find_dinsar_pairs TO your_app_user;
-- GRANT EXECUTE ON FUNCTION calculate_coverage_overlap TO your_app_user;
-- GRANT SELECT ON radar_pairs_view TO your_app_user;
@@ -0,0 +1,143 @@
-- Migration: D-InSAR Pairing Enhancement
-- Version: 2.0
-- Date: 2026-03-08
-- Purpose: Add dual-pool pairing, multiple strategies, and multi-satellite support
-- =====================================================
-- Function: find_dinsar_pairs_v2 (Enhanced Version)
-- Purpose: Find D-InSAR pairs with dual-pool and multi-satellite support
-- =====================================================
CREATE OR REPLACE FUNCTION find_dinsar_pairs_v2(
-- Time/Space constraints (existing)
p_time_baseline_min INTEGER,
p_time_baseline_max INTEGER,
p_spatial_baseline_max_meters NUMERIC,
p_overlap_threshold NUMERIC,
p_aoi_geom GEOMETRY DEFAULT NULL,
p_require_orbit_data BOOLEAN DEFAULT TRUE,
p_require_same_imaging_mode BOOLEAN DEFAULT TRUE,
p_require_same_polarization BOOLEAN DEFAULT TRUE,
p_aoi_overlap_threshold NUMERIC DEFAULT NULL,
-- Dual-pool date ranges (new)
p_master_date_from TEXT DEFAULT NULL,
p_master_date_to TEXT DEFAULT NULL,
p_slave_date_from TEXT DEFAULT NULL,
p_slave_date_to TEXT DEFAULT NULL,
-- Multi-satellite support (new)
p_allowed_satellites TEXT[] DEFAULT NULL,
p_cross_satellite_pairing BOOLEAN DEFAULT FALSE
)
RETURNS TABLE (
master_id INTEGER,
slave_id INTEGER,
master_imaging_date TEXT,
slave_imaging_date TEXT,
time_baseline_days INTEGER,
spatial_baseline_meters NUMERIC,
overlap_ratio NUMERIC
)
LANGUAGE plpgsql
AS $$
BEGIN
RETURN QUERY
WITH candidate_pairs AS (
SELECT
m.id AS master_id,
s.id AS slave_id,
m.imaging_date::text AS master_imaging_date,
s.imaging_date::text AS slave_imaging_date,
ABS(to_date(s.imaging_date, 'YYYYMMDD') - to_date(m.imaging_date, 'YYYYMMDD')) AS time_baseline_days,
ST_DistanceSphere(ST_Centroid(m.geom), ST_Centroid(s.geom))::numeric AS spatial_baseline_meters,
(
ST_Area(ST_Intersection(m.geom, s.geom)::geography) /
NULLIF(GREATEST(ST_Area(m.geom::geography), ST_Area(s.geom::geography)), 0)
)::numeric AS overlap_ratio
FROM radar_data m
JOIN radar_data s ON ST_Intersects(m.geom, s.geom)
WHERE m.id <> s.id
-- Master must be earlier than or equal to slave
AND m.imaging_date <= s.imaging_date
-- Same orbit direction and satellite (unless cross-satellite allowed)
AND m.orbit_direction = s.orbit_direction
AND (p_cross_satellite_pairing OR m.satellite = s.satellite)
-- Master pool date constraints
AND (p_master_date_from IS NULL OR m.imaging_date >= p_master_date_from)
AND (p_master_date_to IS NULL OR m.imaging_date <= p_master_date_to)
-- Slave pool date constraints
AND (p_slave_date_from IS NULL OR s.imaging_date >= p_slave_date_from)
AND (p_slave_date_to IS NULL OR s.imaging_date <= p_slave_date_to)
-- Satellite filter
AND (p_allowed_satellites IS NULL OR m.satellite = ANY(p_allowed_satellites))
AND (p_allowed_satellites IS NULL OR s.satellite = ANY(p_allowed_satellites))
-- Orbit data requirement
AND (NOT p_require_orbit_data OR (m.has_orbit_data = true AND s.has_orbit_data = true))
-- Imaging mode consistency
AND (
NOT p_require_same_imaging_mode OR (
m.imaging_mode IS NOT NULL AND m.imaging_mode <> ''
AND s.imaging_mode IS NOT NULL AND s.imaging_mode <> ''
AND m.imaging_mode = s.imaging_mode
)
)
-- Polarization consistency
AND (
NOT p_require_same_polarization OR (
m.polarization IS NOT NULL AND m.polarization <> ''
AND s.polarization IS NOT NULL AND s.polarization <> ''
AND m.polarization = s.polarization
)
)
-- AOI intersection
AND (p_aoi_geom IS NULL OR (ST_Intersects(m.geom, p_aoi_geom) AND ST_Intersects(s.geom, p_aoi_geom)))
-- AOI overlap threshold
AND (
p_aoi_geom IS NULL OR p_aoi_overlap_threshold IS NULL OR (
ST_Area(ST_Intersection(m.geom, p_aoi_geom)::geography) /
NULLIF(ST_Area(p_aoi_geom::geography), 0) >= p_aoi_overlap_threshold
AND ST_Area(ST_Intersection(s.geom, p_aoi_geom)::geography) /
NULLIF(ST_Area(p_aoi_geom::geography), 0) >= p_aoi_overlap_threshold
)
)
-- Valid date format
AND (m.imaging_date ~ '^[0-9]{8}$' AND s.imaging_date ~ '^[0-9]{8}$')
)
SELECT
cp.master_id,
cp.slave_id,
cp.master_imaging_date,
cp.slave_imaging_date,
cp.time_baseline_days,
cp.spatial_baseline_meters,
cp.overlap_ratio
FROM candidate_pairs cp
WHERE cp.time_baseline_days BETWEEN p_time_baseline_min AND p_time_baseline_max
AND cp.spatial_baseline_meters <= p_spatial_baseline_max_meters
AND cp.overlap_ratio >= p_overlap_threshold
ORDER BY cp.overlap_ratio DESC;
END;
$$;
COMMENT ON FUNCTION find_dinsar_pairs_v2 IS
'Enhanced D-InSAR pairing function with dual-pool support, multiple strategies, and multi-satellite capability.
Backward compatible: when all date parameters are NULL, behaves like find_dinsar_pairs.';
-- =====================================================
-- Backward Compatibility Note
-- =====================================================
-- The original find_dinsar_pairs function is preserved unchanged.
-- Applications should migrate to find_dinsar_pairs_v2 for new features.
-- When all new parameters (master_date_from/to, slave_date_from/to,
-- allowed_satellites, cross_satellite_pairing) are NULL/default,
-- the behavior is equivalent to the original function.
+153
View File
@@ -0,0 +1,153 @@
-- Migration: Pairing Refactor Foundation
-- Version: 4.0
-- Date: 2026-04-14
-- Purpose: Introduce durable pairing cache/state tables for the pairing refactor.
CREATE TABLE IF NOT EXISTS pairing_cache_state (
id SERIAL PRIMARY KEY,
cache_scope VARCHAR(32) NOT NULL,
metric_version VARCHAR(32) NOT NULL DEFAULT '2026.04.v1',
status VARCHAR(16) NOT NULL DEFAULT 'DIRTY',
scene_count INTEGER NOT NULL DEFAULT 0,
pair_count INTEGER NOT NULL DEFAULT 0,
dirty_scene_count INTEGER NOT NULL DEFAULT 0,
last_full_rebuild_at TIMESTAMP NULL,
last_incremental_reconcile_at TIMESTAMP NULL,
last_error TEXT NULL,
updated_at TIMESTAMP NULL DEFAULT NOW()
);
CREATE UNIQUE INDEX IF NOT EXISTS idx_pairing_cache_state_scope
ON pairing_cache_state (cache_scope);
INSERT INTO pairing_cache_state (
cache_scope,
metric_version,
status,
scene_count,
pair_count,
dirty_scene_count
)
SELECT
'global',
'2026.04.v1',
'DIRTY',
0,
0,
0
WHERE NOT EXISTS (
SELECT 1 FROM pairing_cache_state WHERE cache_scope = 'global'
);
CREATE TABLE IF NOT EXISTS pairing_dirty_scenes (
id SERIAL PRIMARY KEY,
scene_ref_id INTEGER NOT NULL REFERENCES radar_data(id) ON DELETE CASCADE,
scene_uid VARCHAR NOT NULL,
reason VARCHAR(64) NOT NULL DEFAULT 'scan',
status VARCHAR(16) NOT NULL DEFAULT 'PENDING',
marked_at TIMESTAMP NOT NULL DEFAULT NOW(),
resolved_at TIMESTAMP NULL
);
CREATE INDEX IF NOT EXISTS idx_pairing_dirty_scenes_scene_status
ON pairing_dirty_scenes (scene_ref_id, status);
CREATE INDEX IF NOT EXISTS idx_pairing_dirty_scenes_uid_status
ON pairing_dirty_scenes (scene_uid, status);
CREATE UNIQUE INDEX IF NOT EXISTS idx_pairing_dirty_scenes_pending_unique
ON pairing_dirty_scenes (scene_ref_id)
WHERE status = 'PENDING';
CREATE TABLE IF NOT EXISTS pairing_metric_cache (
id SERIAL PRIMARY KEY,
master_scene_ref_id INTEGER NOT NULL REFERENCES radar_data(id) ON DELETE CASCADE,
slave_scene_ref_id INTEGER NOT NULL REFERENCES radar_data(id) ON DELETE CASCADE,
master_scene_uid VARCHAR NOT NULL,
slave_scene_uid VARCHAR NOT NULL,
pair_uid VARCHAR NOT NULL,
metric_version VARCHAR(32) NOT NULL DEFAULT '2026.04.v1',
orientation_rule_version VARCHAR(32) NOT NULL DEFAULT 'date_then_scene_uid_v1',
time_baseline_days INTEGER NULL,
spatial_baseline_meters DOUBLE PRECISION NULL,
scene_overlap_ratio DOUBLE PRECISION NULL,
orbit_direction VARCHAR NULL,
same_satellite BOOLEAN NOT NULL DEFAULT TRUE,
same_imaging_mode BOOLEAN NOT NULL DEFAULT TRUE,
same_polarization BOOLEAN NOT NULL DEFAULT TRUE,
master_imaging_date VARCHAR(8) NULL,
slave_imaging_date VARCHAR(8) NULL,
master_satellite VARCHAR NULL,
slave_satellite VARCHAR NULL,
master_imaging_mode VARCHAR NULL,
slave_imaging_mode VARCHAR NULL,
master_polarization VARCHAR NULL,
slave_polarization VARCHAR NULL,
master_file_path VARCHAR NULL,
slave_file_path VARCHAR NULL,
status VARCHAR(16) NOT NULL DEFAULT 'READY',
computed_at TIMESTAMP NOT NULL DEFAULT NOW()
);
CREATE UNIQUE INDEX IF NOT EXISTS idx_pairing_metric_cache_pair_version
ON pairing_metric_cache (master_scene_ref_id, slave_scene_ref_id, metric_version);
CREATE INDEX IF NOT EXISTS idx_pairing_metric_cache_pair_uid_version
ON pairing_metric_cache (pair_uid, metric_version);
CREATE INDEX IF NOT EXISTS idx_pairing_metric_cache_metric_dates
ON pairing_metric_cache (metric_version, master_imaging_date, slave_imaging_date);
CREATE INDEX IF NOT EXISTS idx_pairing_metric_cache_orbit_direction
ON pairing_metric_cache (orbit_direction);
CREATE TABLE IF NOT EXISTS pairing_network_runs (
id SERIAL PRIMARY KEY,
network_run_id VARCHAR(64) NOT NULL,
strategy VARCHAR(32) NOT NULL,
policy_version VARCHAR(32) NOT NULL,
request_hash VARCHAR(64) NULL,
request_params_json JSON NULL,
aoi_source VARCHAR(32) NULL,
aoi_hash VARCHAR(64) NULL,
aoi_summary_json JSON NULL,
candidate_count INTEGER NOT NULL DEFAULT 0,
selected_edge_count INTEGER NOT NULL DEFAULT 0,
warning_count INTEGER NOT NULL DEFAULT 0,
status VARCHAR(16) NOT NULL DEFAULT 'PENDING',
fallback_used BOOLEAN NOT NULL DEFAULT FALSE,
created_by VARCHAR(64) NULL,
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP NULL DEFAULT NOW()
);
CREATE UNIQUE INDEX IF NOT EXISTS idx_pairing_network_runs_run_id
ON pairing_network_runs (network_run_id);
CREATE INDEX IF NOT EXISTS idx_pairing_network_runs_strategy_status
ON pairing_network_runs (strategy, status);
CREATE INDEX IF NOT EXISTS idx_pairing_network_runs_request_hash
ON pairing_network_runs (request_hash);
CREATE TABLE IF NOT EXISTS pairing_network_edges (
id SERIAL PRIMARY KEY,
network_run_ref_id INTEGER NOT NULL REFERENCES pairing_network_runs(id) ON DELETE CASCADE,
metric_cache_ref_id INTEGER NOT NULL REFERENCES pairing_metric_cache(id) ON DELETE CASCADE,
edge_rank INTEGER NOT NULL DEFAULT 0,
selection_reason VARCHAR(64) NULL,
selection_score DOUBLE PRECISION NULL,
selection_meta_json JSON NULL,
is_reference_edge BOOLEAN NOT NULL DEFAULT FALSE,
created_at TIMESTAMP NOT NULL DEFAULT NOW()
);
CREATE UNIQUE INDEX IF NOT EXISTS idx_pairing_network_edges_run_metric
ON pairing_network_edges (network_run_ref_id, metric_cache_ref_id);
CREATE INDEX IF NOT EXISTS idx_pairing_network_edges_run_rank
ON pairing_network_edges (network_run_ref_id, edge_rank);
@@ -0,0 +1,31 @@
-- Migration: Pairing Task Trace Fields
-- Version: 5.0
-- Date: 2026-04-14
-- Purpose: Add pairing network trace fields to D-InSAR task items.
ALTER TABLE IF EXISTS dinsar_task_items
ADD COLUMN IF NOT EXISTS scene_pair_uid VARCHAR(64) NULL;
ALTER TABLE IF EXISTS dinsar_task_items
ADD COLUMN IF NOT EXISTS network_run_id VARCHAR(64) NULL;
ALTER TABLE IF EXISTS dinsar_task_items
ADD COLUMN IF NOT EXISTS network_edge_id INTEGER NULL;
ALTER TABLE IF EXISTS dinsar_task_items
ADD COLUMN IF NOT EXISTS policy_version VARCHAR(32) NULL;
ALTER TABLE IF EXISTS dinsar_task_items
ADD COLUMN IF NOT EXISTS selection_strategy VARCHAR(32) NULL;
CREATE INDEX IF NOT EXISTS idx_dinsar_task_items_scene_pair_uid
ON dinsar_task_items (scene_pair_uid);
CREATE INDEX IF NOT EXISTS idx_dinsar_task_items_network_run_id
ON dinsar_task_items (network_run_id);
CREATE INDEX IF NOT EXISTS idx_dinsar_task_items_policy_version
ON dinsar_task_items (policy_version);
CREATE INDEX IF NOT EXISTS idx_dinsar_task_items_selection_strategy
ON dinsar_task_items (selection_strategy);
@@ -0,0 +1,31 @@
-- Migration: Result Product Pairing Trace Fields
-- Version: 6.0
-- Date: 2026-04-14
-- Purpose: Persist pairing trace on result catalog products.
ALTER TABLE IF EXISTS result_products
ADD COLUMN IF NOT EXISTS pair_uid VARCHAR(64) NULL;
ALTER TABLE IF EXISTS result_products
ADD COLUMN IF NOT EXISTS network_run_id VARCHAR(64) NULL;
ALTER TABLE IF EXISTS result_products
ADD COLUMN IF NOT EXISTS network_edge_id INTEGER NULL;
ALTER TABLE IF EXISTS result_products
ADD COLUMN IF NOT EXISTS policy_version VARCHAR(32) NULL;
ALTER TABLE IF EXISTS result_products
ADD COLUMN IF NOT EXISTS selection_strategy VARCHAR(32) NULL;
CREATE INDEX IF NOT EXISTS idx_result_products_pair_uid
ON result_products (pair_uid);
CREATE INDEX IF NOT EXISTS idx_result_products_network_run_id
ON result_products (network_run_id);
CREATE INDEX IF NOT EXISTS idx_result_products_policy_version
ON result_products (policy_version);
CREATE INDEX IF NOT EXISTS idx_result_products_selection_strategy
ON result_products (selection_strategy);