372 lines
14 KiB
SQL
372 lines
14 KiB
SQL
-- Migration: Source product and orbit asset inventory
|
|
-- Version: 10.0
|
|
-- Date: 2026-05-12
|
|
-- Purpose: Add first-class source product assets, orbit assets, scene-orbit bindings,
|
|
-- inventory state, and radar_data compatibility fields.
|
|
|
|
ALTER TABLE IF EXISTS radar_data
|
|
ADD COLUMN IF NOT EXISTS source_product_ref_id INTEGER NULL;
|
|
|
|
ALTER TABLE IF EXISTS radar_data
|
|
ADD COLUMN IF NOT EXISTS source_archive_asset_id INTEGER NULL;
|
|
|
|
ALTER TABLE IF EXISTS radar_data
|
|
ADD COLUMN IF NOT EXISTS selected_orbit_asset_id INTEGER NULL;
|
|
|
|
ALTER TABLE IF EXISTS radar_data
|
|
ADD COLUMN IF NOT EXISTS orbit_binding_status VARCHAR(32) NOT NULL DEFAULT 'UNBOUND';
|
|
|
|
ALTER TABLE IF EXISTS radar_data
|
|
ADD COLUMN IF NOT EXISTS orbit_binding_reason TEXT NULL;
|
|
|
|
ALTER TABLE IF EXISTS radar_data
|
|
ADD COLUMN IF NOT EXISTS acquisition_start_time_utc TIMESTAMP NULL;
|
|
|
|
ALTER TABLE IF EXISTS radar_data
|
|
ADD COLUMN IF NOT EXISTS acquisition_stop_time_utc TIMESTAMP NULL;
|
|
|
|
ALTER TABLE IF EXISTS radar_data
|
|
ADD COLUMN IF NOT EXISTS absolute_orbit VARCHAR NULL;
|
|
|
|
ALTER TABLE IF EXISTS radar_data
|
|
ADD COLUMN IF NOT EXISTS relative_orbit VARCHAR NULL;
|
|
|
|
ALTER TABLE IF EXISTS radar_data
|
|
ADD COLUMN IF NOT EXISTS source_format VARCHAR(32) NULL;
|
|
|
|
ALTER TABLE IF EXISTS radar_data
|
|
ADD COLUMN IF NOT EXISTS metadata_json JSON NULL;
|
|
|
|
CREATE TABLE IF NOT EXISTS source_product_assets (
|
|
id SERIAL PRIMARY KEY,
|
|
asset_uid VARCHAR(128) NOT NULL UNIQUE,
|
|
logical_product_uid VARCHAR(128) NULL,
|
|
satellite_family VARCHAR(32) NULL,
|
|
satellite VARCHAR(32) NULL,
|
|
source_format VARCHAR(32) NOT NULL,
|
|
product_type VARCHAR(64) NULL,
|
|
product_level VARCHAR(64) NULL,
|
|
imaging_mode VARCHAR(64) NULL,
|
|
polarization VARCHAR(64) NULL,
|
|
absolute_orbit VARCHAR(64) NULL,
|
|
relative_orbit VARCHAR(64) NULL,
|
|
orbit_direction VARCHAR(32) NULL,
|
|
acquisition_start_time_utc TIMESTAMP NULL,
|
|
acquisition_stop_time_utc TIMESTAMP NULL,
|
|
imaging_date VARCHAR(8) NULL,
|
|
root_ref_id INTEGER NULL REFERENCES managed_roots(id) ON DELETE SET NULL,
|
|
root_path VARCHAR NULL,
|
|
file_path VARCHAR NOT NULL UNIQUE,
|
|
archive_path VARCHAR NULL,
|
|
path_kind VARCHAR(24) NULL,
|
|
file_name VARCHAR(255) NULL,
|
|
file_stem VARCHAR(255) NULL,
|
|
file_ext VARCHAR(32) NULL,
|
|
size_bytes BIGINT NULL,
|
|
mtime_epoch DOUBLE PRECISION NULL,
|
|
checksum_sha256 VARCHAR(64) NULL,
|
|
checksum_status VARCHAR(32) NOT NULL DEFAULT 'NOT_COMPUTED',
|
|
parser_name VARCHAR(64) NULL,
|
|
parser_version VARCHAR(32) NULL,
|
|
parse_status VARCHAR(32) NOT NULL DEFAULT 'PENDING',
|
|
parse_error TEXT NULL,
|
|
parsed_at TIMESTAMP NULL,
|
|
metadata_json JSON NULL,
|
|
is_active BOOLEAN NOT NULL DEFAULT TRUE,
|
|
missing_since TIMESTAMP NULL,
|
|
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
|
updated_at TIMESTAMP DEFAULT NOW()
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS orbit_assets (
|
|
id SERIAL PRIMARY KEY,
|
|
orbit_uid VARCHAR(128) NOT NULL UNIQUE,
|
|
satellite_family VARCHAR(32) NULL,
|
|
satellite VARCHAR(32) NULL,
|
|
orbit_type VARCHAR(64) NOT NULL,
|
|
native_format VARCHAR(32) NOT NULL,
|
|
quality_class VARCHAR(32) NOT NULL DEFAULT 'unknown',
|
|
root_ref_id INTEGER NULL REFERENCES managed_roots(id) ON DELETE SET NULL,
|
|
root_path VARCHAR NULL,
|
|
file_path VARCHAR NOT NULL UNIQUE,
|
|
file_name VARCHAR(255) NULL,
|
|
file_stem VARCHAR(255) NULL,
|
|
file_ext VARCHAR(32) NULL,
|
|
size_bytes BIGINT NULL,
|
|
mtime_epoch DOUBLE PRECISION NULL,
|
|
checksum_sha256 VARCHAR(64) NULL,
|
|
checksum_status VARCHAR(32) NOT NULL DEFAULT 'NOT_COMPUTED',
|
|
validity_start_time_utc TIMESTAMP NULL,
|
|
validity_stop_time_utc TIMESTAMP NULL,
|
|
generation_time_utc TIMESTAMP NULL,
|
|
published_time_utc TIMESTAMP NULL,
|
|
parser_name VARCHAR(64) NULL,
|
|
parser_version VARCHAR(32) NULL,
|
|
parse_status VARCHAR(32) NOT NULL DEFAULT 'PENDING',
|
|
parse_error TEXT NULL,
|
|
parsed_at TIMESTAMP NULL,
|
|
metadata_json JSON NULL,
|
|
is_active BOOLEAN NOT NULL DEFAULT TRUE,
|
|
missing_since TIMESTAMP NULL,
|
|
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
|
updated_at TIMESTAMP DEFAULT NOW()
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS scene_orbit_bindings (
|
|
id SERIAL PRIMARY KEY,
|
|
radar_data_id INTEGER NOT NULL REFERENCES radar_data(id) ON DELETE CASCADE,
|
|
orbit_asset_id INTEGER NOT NULL REFERENCES orbit_assets(id) ON DELETE CASCADE,
|
|
binding_role VARCHAR(32) NOT NULL DEFAULT 'primary_orbit',
|
|
match_status VARCHAR(32) NOT NULL DEFAULT 'CANDIDATE',
|
|
selection_status VARCHAR(32) NOT NULL DEFAULT 'CANDIDATE',
|
|
selection_rank INTEGER NULL,
|
|
priority_score DOUBLE PRECISION NULL,
|
|
coverage_margin_before_seconds DOUBLE PRECISION NULL,
|
|
coverage_margin_after_seconds DOUBLE PRECISION NULL,
|
|
match_rule_version VARCHAR(64) NULL,
|
|
match_reason TEXT NULL,
|
|
selected_at TIMESTAMP NULL,
|
|
metadata_json JSON NULL,
|
|
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
|
updated_at TIMESTAMP DEFAULT NOW(),
|
|
CONSTRAINT uq_scene_orbit_binding_role UNIQUE (radar_data_id, orbit_asset_id, binding_role)
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS orbit_asset_derivatives (
|
|
id SERIAL PRIMARY KEY,
|
|
orbit_asset_id INTEGER NOT NULL REFERENCES orbit_assets(id) ON DELETE CASCADE,
|
|
engine_code VARCHAR(32) NOT NULL,
|
|
derivative_format VARCHAR(32) NOT NULL,
|
|
derivative_role VARCHAR(64) NULL,
|
|
pool_path VARCHAR NOT NULL,
|
|
size_bytes BIGINT NULL,
|
|
mtime_epoch DOUBLE PRECISION NULL,
|
|
checksum_sha256 VARCHAR(64) NULL,
|
|
generation_status VARCHAR(32) NOT NULL DEFAULT 'PENDING',
|
|
generation_error TEXT NULL,
|
|
generated_at TIMESTAMP NULL,
|
|
metadata_json JSON NULL,
|
|
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
|
updated_at TIMESTAMP DEFAULT NOW(),
|
|
CONSTRAINT uq_orbit_asset_derivative_pool_path UNIQUE (orbit_asset_id, engine_code, derivative_format, pool_path)
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS asset_inventory_states (
|
|
id SERIAL PRIMARY KEY,
|
|
root_ref_id INTEGER NOT NULL REFERENCES managed_roots(id) ON DELETE CASCADE,
|
|
inventory_type VARCHAR(32) NOT NULL,
|
|
root_path VARCHAR NOT NULL,
|
|
scan_mode VARCHAR(32) NOT NULL DEFAULT 'file_pool',
|
|
status VARCHAR(32) NOT NULL DEFAULT 'NEVER_SCANNED',
|
|
last_scan_started_at TIMESTAMP NULL,
|
|
last_scan_finished_at TIMESTAMP NULL,
|
|
last_seen_entry_count INTEGER NULL,
|
|
last_asset_count INTEGER NULL,
|
|
last_issue_count INTEGER NULL,
|
|
parser_version VARCHAR(32) NULL,
|
|
needs_rescan BOOLEAN NOT NULL DEFAULT TRUE,
|
|
last_error TEXT NULL,
|
|
metadata_json JSON NULL,
|
|
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
|
updated_at TIMESTAMP DEFAULT NOW(),
|
|
CONSTRAINT uq_asset_inventory_state_root_type UNIQUE (root_ref_id, inventory_type)
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS asset_inventory_issues (
|
|
id SERIAL PRIMARY KEY,
|
|
root_ref_id INTEGER NULL REFERENCES managed_roots(id) ON DELETE SET NULL,
|
|
inventory_type VARCHAR(32) NOT NULL,
|
|
asset_ref_id INTEGER NULL REFERENCES source_product_assets(id) ON DELETE SET NULL,
|
|
radar_data_id INTEGER NULL REFERENCES radar_data(id) ON DELETE SET NULL,
|
|
orbit_asset_id INTEGER NULL REFERENCES orbit_assets(id) ON DELETE SET NULL,
|
|
severity VARCHAR(16) NOT NULL DEFAULT 'warning',
|
|
issue_code VARCHAR(64) NOT NULL,
|
|
issue_message TEXT NULL,
|
|
source_path VARCHAR NULL,
|
|
status VARCHAR(16) NOT NULL DEFAULT 'OPEN',
|
|
first_seen_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
|
last_seen_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
|
resolved_at TIMESTAMP NULL,
|
|
metadata_json JSON NULL
|
|
);
|
|
|
|
DO $$
|
|
BEGIN
|
|
IF NOT EXISTS (
|
|
SELECT 1 FROM pg_constraint WHERE conname = 'fk_radar_data_source_product_ref_id'
|
|
) THEN
|
|
ALTER TABLE radar_data
|
|
ADD CONSTRAINT fk_radar_data_source_product_ref_id
|
|
FOREIGN KEY (source_product_ref_id)
|
|
REFERENCES source_product_assets(id)
|
|
ON DELETE SET NULL;
|
|
END IF;
|
|
|
|
IF NOT EXISTS (
|
|
SELECT 1 FROM pg_constraint WHERE conname = 'fk_radar_data_source_archive_asset_id'
|
|
) THEN
|
|
ALTER TABLE radar_data
|
|
ADD CONSTRAINT fk_radar_data_source_archive_asset_id
|
|
FOREIGN KEY (source_archive_asset_id)
|
|
REFERENCES source_product_assets(id)
|
|
ON DELETE SET NULL;
|
|
END IF;
|
|
|
|
IF NOT EXISTS (
|
|
SELECT 1 FROM pg_constraint WHERE conname = 'fk_radar_data_selected_orbit_asset_id'
|
|
) THEN
|
|
ALTER TABLE radar_data
|
|
ADD CONSTRAINT fk_radar_data_selected_orbit_asset_id
|
|
FOREIGN KEY (selected_orbit_asset_id)
|
|
REFERENCES orbit_assets(id)
|
|
ON DELETE SET NULL;
|
|
END IF;
|
|
END $$;
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_radar_data_source_product_ref
|
|
ON radar_data (source_product_ref_id);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_radar_data_source_archive_asset
|
|
ON radar_data (source_archive_asset_id);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_radar_data_selected_orbit_asset
|
|
ON radar_data (selected_orbit_asset_id);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_radar_data_orbit_binding_status
|
|
ON radar_data (orbit_binding_status);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_radar_data_acquisition_start
|
|
ON radar_data (acquisition_start_time_utc);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_radar_data_absolute_orbit
|
|
ON radar_data (absolute_orbit);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_radar_data_relative_orbit
|
|
ON radar_data (relative_orbit);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_radar_data_source_format
|
|
ON radar_data (source_format);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_source_product_assets_asset_uid
|
|
ON source_product_assets (asset_uid);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_source_product_assets_family_date
|
|
ON source_product_assets (satellite_family, imaging_date);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_source_product_assets_satellite
|
|
ON source_product_assets (satellite);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_source_product_assets_source_format
|
|
ON source_product_assets (source_format);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_source_product_assets_parse_status
|
|
ON source_product_assets (parse_status);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_source_product_assets_root_active
|
|
ON source_product_assets (root_ref_id, is_active);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_source_product_assets_logical_product
|
|
ON source_product_assets (logical_product_uid);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_source_product_assets_file_path
|
|
ON source_product_assets (file_path);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_orbit_assets_orbit_uid
|
|
ON orbit_assets (orbit_uid);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_orbit_assets_family_sat_window
|
|
ON orbit_assets (satellite_family, satellite, validity_start_time_utc, validity_stop_time_utc);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_orbit_assets_orbit_type
|
|
ON orbit_assets (orbit_type);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_orbit_assets_native_format
|
|
ON orbit_assets (native_format);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_orbit_assets_quality_class
|
|
ON orbit_assets (quality_class);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_orbit_assets_parse_status
|
|
ON orbit_assets (parse_status);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_orbit_assets_root_active
|
|
ON orbit_assets (root_ref_id, is_active);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_orbit_assets_file_path
|
|
ON orbit_assets (file_path);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_scene_orbit_bindings_radar
|
|
ON scene_orbit_bindings (radar_data_id);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_scene_orbit_bindings_orbit
|
|
ON scene_orbit_bindings (orbit_asset_id);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_scene_orbit_bindings_match_status
|
|
ON scene_orbit_bindings (match_status);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_scene_orbit_bindings_selection_status
|
|
ON scene_orbit_bindings (selection_status);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_scene_orbit_bindings_scene_selected
|
|
ON scene_orbit_bindings (radar_data_id, selection_status);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_orbit_asset_derivatives_asset_engine
|
|
ON orbit_asset_derivatives (orbit_asset_id, engine_code);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_orbit_asset_derivatives_pool_path
|
|
ON orbit_asset_derivatives (pool_path);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_orbit_asset_derivatives_generation_status
|
|
ON orbit_asset_derivatives (generation_status);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_asset_inventory_states_type_status
|
|
ON asset_inventory_states (inventory_type, status);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_asset_inventory_states_needs_rescan
|
|
ON asset_inventory_states (needs_rescan);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_asset_inventory_issues_open
|
|
ON asset_inventory_issues (status, severity);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_asset_inventory_issues_root_type
|
|
ON asset_inventory_issues (root_ref_id, inventory_type);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_asset_inventory_issues_asset
|
|
ON asset_inventory_issues (asset_ref_id);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_asset_inventory_issues_orbit
|
|
ON asset_inventory_issues (orbit_asset_id);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_asset_inventory_issues_radar
|
|
ON asset_inventory_issues (radar_data_id);
|
|
|
|
UPDATE radar_data
|
|
SET
|
|
orbit_binding_status = CASE
|
|
WHEN has_orbit_data IS TRUE AND NULLIF(orbit_file_path, '') IS NOT NULL THEN 'MATCHED'
|
|
WHEN has_orbit_data IS TRUE THEN 'MATCHED'
|
|
ELSE COALESCE(NULLIF(orbit_binding_status, ''), 'UNBOUND')
|
|
END
|
|
WHERE orbit_binding_status IS NULL
|
|
OR orbit_binding_status = ''
|
|
OR (has_orbit_data IS TRUE AND orbit_binding_status = 'UNBOUND');
|
|
|
|
UPDATE radar_data
|
|
SET absolute_orbit = COALESCE(NULLIF(absolute_orbit, ''), NULLIF(orbit_circle, ''))
|
|
WHERE absolute_orbit IS NULL OR absolute_orbit = '';
|
|
|
|
UPDATE radar_data
|
|
SET source_format = COALESCE(
|
|
NULLIF(source_format, ''),
|
|
CASE
|
|
WHEN upper(replace(replace(replace(COALESCE(satellite, ''), '-', ''), '_', ''), ' ', '')) LIKE 'S1%%'
|
|
AND lower(COALESCE(file_path, '')) LIKE '%%.zip' THEN 'S1_ZIP'
|
|
WHEN upper(replace(replace(replace(COALESCE(satellite, ''), '-', ''), '_', ''), ' ', '')) LIKE 'S1%%'
|
|
AND lower(COALESCE(file_path, '')) LIKE '%%.safe' THEN 'S1_SAFE_DIR'
|
|
WHEN upper(replace(replace(replace(COALESCE(satellite, ''), '-', ''), '_', ''), ' ', '')) LIKE 'LT1%%' THEN 'LT1_DIR'
|
|
WHEN upper(replace(replace(replace(COALESCE(satellite, ''), '-', ''), '_', ''), ' ', '')) LIKE 'GF3%%' THEN 'GF3_DIR'
|
|
ELSE NULL
|
|
END
|
|
)
|
|
WHERE source_format IS NULL OR source_format = '';
|