feat: sync latest ARR implementation
This commit is contained in:
281
database/012_monthly_report_publication.sql
Normal file
281
database/012_monthly_report_publication.sql
Normal file
@@ -0,0 +1,281 @@
|
||||
-- Durable metadata-only monthly report publication for ARR2.
|
||||
-- PostgreSQL 15+. Migrations 008-011 remain immutable.
|
||||
|
||||
BEGIN;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
IF current_database() <> 'booking_test' THEN
|
||||
RAISE EXCEPTION
|
||||
'ARR monthly publication migration is allowed only in booking_test';
|
||||
END IF;
|
||||
IF to_regclass('ingestion.artifacts') IS NULL
|
||||
OR to_regclass('ingestion.outbox_events') IS NULL
|
||||
OR to_regclass('finance.daily_versions') IS NULL THEN
|
||||
RAISE EXCEPTION
|
||||
'ARR migrations 008 through 011 must be applied first';
|
||||
END IF;
|
||||
IF to_regnamespace('reporting') IS NOT NULL THEN
|
||||
RAISE EXCEPTION
|
||||
'ARR monthly publication migration is already applied';
|
||||
END IF;
|
||||
IF NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM pg_constraint
|
||||
WHERE conrelid = 'ingestion.artifacts'::regclass
|
||||
AND conname = 'artifacts_storage_provider_check'
|
||||
) THEN
|
||||
RAISE EXCEPTION
|
||||
'ingestion.artifacts storage-provider constraint is missing';
|
||||
END IF;
|
||||
END;
|
||||
$$;
|
||||
|
||||
ALTER TABLE ingestion.artifacts
|
||||
DROP CONSTRAINT artifacts_storage_provider_check;
|
||||
|
||||
ALTER TABLE ingestion.artifacts
|
||||
ADD CONSTRAINT artifacts_storage_provider_check CHECK (
|
||||
storage_provider IN ('oss', 's3', 'local_fixture', 'local')
|
||||
);
|
||||
|
||||
CREATE SCHEMA reporting;
|
||||
|
||||
COMMENT ON SCHEMA reporting IS
|
||||
'Metadata-only publication lifecycle and lineage for derived ARR reports; business rows remain in Finance facts.';
|
||||
|
||||
CREATE TABLE reporting.monthly_runs (
|
||||
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
||||
period_start date NOT NULL,
|
||||
as_of_date date NOT NULL,
|
||||
version_no integer NOT NULL CHECK (version_no > 0),
|
||||
source_snapshot_sha256 character(64) NOT NULL CHECK (
|
||||
source_snapshot_sha256 ~ '^[0-9a-f]{64}$'
|
||||
),
|
||||
report_status text NOT NULL CHECK (report_status IN (
|
||||
'reserved', 'active', 'superseded', 'failed'
|
||||
)),
|
||||
processor_version text NOT NULL CHECK (btrim(processor_version) <> ''),
|
||||
rule_set_sha256 character(64) NOT NULL CHECK (
|
||||
rule_set_sha256 ~ '^[0-9a-f]{64}$'
|
||||
),
|
||||
result_schema_version text NOT NULL CHECK (
|
||||
btrim(result_schema_version) <> ''
|
||||
),
|
||||
row_count integer NOT NULL CHECK (row_count >= 0),
|
||||
channel_count integer NOT NULL CHECK (channel_count >= 5),
|
||||
workbook_artifact_id bigint REFERENCES ingestion.artifacts(id),
|
||||
result_artifact_id bigint REFERENCES ingestion.artifacts(id),
|
||||
semantic_sha256 character(64) CHECK (
|
||||
semantic_sha256 IS NULL OR semantic_sha256 ~ '^[0-9a-f]{64}$'
|
||||
),
|
||||
failure_code text,
|
||||
failure_message text,
|
||||
created_at timestamptz NOT NULL DEFAULT now(),
|
||||
updated_at timestamptz NOT NULL DEFAULT now(),
|
||||
published_at timestamptz,
|
||||
superseded_at timestamptz,
|
||||
failed_at timestamptz,
|
||||
CONSTRAINT monthly_runs_period_shape CHECK (
|
||||
period_start = date_trunc('month', period_start)::date
|
||||
AND as_of_date >= period_start
|
||||
AND as_of_date < (period_start + interval '1 month')::date
|
||||
),
|
||||
CONSTRAINT monthly_runs_period_version_unique
|
||||
UNIQUE (period_start, version_no),
|
||||
CONSTRAINT monthly_runs_snapshot_unique
|
||||
UNIQUE (period_start, source_snapshot_sha256),
|
||||
CONSTRAINT monthly_runs_status_shape CHECK (
|
||||
(
|
||||
report_status = 'reserved'
|
||||
AND workbook_artifact_id IS NULL
|
||||
AND result_artifact_id IS NULL
|
||||
AND semantic_sha256 IS NULL
|
||||
AND failure_code IS NULL
|
||||
AND failure_message IS NULL
|
||||
AND published_at IS NULL
|
||||
AND superseded_at IS NULL
|
||||
AND failed_at IS NULL
|
||||
)
|
||||
OR (
|
||||
report_status = 'active'
|
||||
AND workbook_artifact_id IS NOT NULL
|
||||
AND result_artifact_id IS NOT NULL
|
||||
AND semantic_sha256 IS NOT NULL
|
||||
AND failure_code IS NULL
|
||||
AND failure_message IS NULL
|
||||
AND published_at IS NOT NULL
|
||||
AND superseded_at IS NULL
|
||||
AND failed_at IS NULL
|
||||
)
|
||||
OR (
|
||||
report_status = 'superseded'
|
||||
AND workbook_artifact_id IS NOT NULL
|
||||
AND result_artifact_id IS NOT NULL
|
||||
AND semantic_sha256 IS NOT NULL
|
||||
AND failure_code IS NULL
|
||||
AND failure_message IS NULL
|
||||
AND published_at IS NOT NULL
|
||||
AND superseded_at IS NOT NULL
|
||||
AND failed_at IS NULL
|
||||
)
|
||||
OR (
|
||||
report_status = 'failed'
|
||||
AND workbook_artifact_id IS NULL
|
||||
AND result_artifact_id IS NULL
|
||||
AND semantic_sha256 IS NULL
|
||||
AND failure_code IS NOT NULL
|
||||
AND btrim(failure_code) <> ''
|
||||
AND published_at IS NULL
|
||||
AND superseded_at IS NULL
|
||||
AND failed_at IS NOT NULL
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX monthly_runs_one_active_period_idx
|
||||
ON reporting.monthly_runs (period_start)
|
||||
WHERE report_status = 'active';
|
||||
|
||||
CREATE INDEX monthly_runs_period_history_idx
|
||||
ON reporting.monthly_runs (period_start, version_no DESC);
|
||||
|
||||
CREATE TABLE reporting.monthly_run_daily_versions (
|
||||
report_id bigint NOT NULL REFERENCES reporting.monthly_runs(id),
|
||||
business_date date NOT NULL,
|
||||
daily_version_id bigint NOT NULL,
|
||||
PRIMARY KEY (report_id, business_date),
|
||||
CONSTRAINT monthly_run_daily_versions_version_unique
|
||||
UNIQUE (report_id, daily_version_id),
|
||||
CONSTRAINT monthly_run_daily_versions_version_fk
|
||||
FOREIGN KEY (daily_version_id, business_date)
|
||||
REFERENCES finance.daily_versions(id, business_date)
|
||||
);
|
||||
|
||||
CREATE TABLE reporting.monthly_channel_manifest (
|
||||
report_id bigint NOT NULL REFERENCES reporting.monthly_runs(id),
|
||||
worksheet text NOT NULL CHECK (
|
||||
btrim(worksheet) <> ''
|
||||
AND char_length(worksheet) <= 31
|
||||
AND worksheet !~ '[:\\/?*\[\]]'
|
||||
),
|
||||
worksheet_order integer NOT NULL CHECK (worksheet_order > 0),
|
||||
row_count integer NOT NULL CHECK (row_count >= 0),
|
||||
PRIMARY KEY (report_id, worksheet),
|
||||
CONSTRAINT monthly_channel_manifest_order_unique
|
||||
UNIQUE (report_id, worksheet_order)
|
||||
);
|
||||
|
||||
CREATE OR REPLACE FUNCTION reporting.validate_monthly_run_publication()
|
||||
RETURNS trigger
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
DECLARE
|
||||
workbook_kind text;
|
||||
workbook_provider text;
|
||||
result_kind text;
|
||||
result_provider text;
|
||||
manifest_count integer;
|
||||
manifest_rows bigint;
|
||||
manifest_min integer;
|
||||
manifest_max integer;
|
||||
lineage_count integer;
|
||||
BEGIN
|
||||
IF NEW.report_status NOT IN ('active', 'superseded') THEN
|
||||
RETURN NEW;
|
||||
END IF;
|
||||
|
||||
SELECT artifact_kind, storage_provider
|
||||
INTO workbook_kind, workbook_provider
|
||||
FROM ingestion.artifacts
|
||||
WHERE id = NEW.workbook_artifact_id;
|
||||
|
||||
SELECT artifact_kind, storage_provider
|
||||
INTO result_kind, result_provider
|
||||
FROM ingestion.artifacts
|
||||
WHERE id = NEW.result_artifact_id;
|
||||
|
||||
IF workbook_kind IS DISTINCT FROM 'monthly_xlsx'
|
||||
OR workbook_provider IS DISTINCT FROM 'local'
|
||||
OR result_kind IS DISTINCT FROM 'result_json'
|
||||
OR result_provider IS DISTINCT FROM 'local' THEN
|
||||
RAISE EXCEPTION
|
||||
'published monthly run must reference controlled local monthly_xlsx and result_json artifacts';
|
||||
END IF;
|
||||
|
||||
SELECT count(*), COALESCE(sum(row_count), 0), min(worksheet_order), max(worksheet_order)
|
||||
INTO manifest_count, manifest_rows, manifest_min, manifest_max
|
||||
FROM reporting.monthly_channel_manifest
|
||||
WHERE report_id = NEW.id;
|
||||
|
||||
IF manifest_count <> NEW.channel_count
|
||||
OR manifest_rows <> NEW.row_count
|
||||
OR manifest_min <> 1
|
||||
OR manifest_max <> manifest_count THEN
|
||||
RAISE EXCEPTION
|
||||
'published monthly run requires a continuous reconciled channel manifest';
|
||||
END IF;
|
||||
|
||||
SELECT count(*)
|
||||
INTO lineage_count
|
||||
FROM reporting.monthly_run_daily_versions
|
||||
WHERE report_id = NEW.id;
|
||||
|
||||
IF lineage_count < 1 THEN
|
||||
RAISE EXCEPTION
|
||||
'published monthly run requires daily-version lineage';
|
||||
END IF;
|
||||
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$;
|
||||
|
||||
CREATE TRIGGER monthly_runs_publication_guard
|
||||
BEFORE INSERT OR UPDATE ON reporting.monthly_runs
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION reporting.validate_monthly_run_publication();
|
||||
|
||||
CREATE OR REPLACE FUNCTION reporting.protect_published_monthly_child()
|
||||
RETURNS trigger
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
DECLARE
|
||||
parent_id bigint;
|
||||
BEGIN
|
||||
parent_id := COALESCE(OLD.report_id, NEW.report_id);
|
||||
IF EXISTS (
|
||||
SELECT 1
|
||||
FROM reporting.monthly_runs AS run
|
||||
WHERE run.id = parent_id
|
||||
AND run.report_status IN ('active', 'superseded')
|
||||
) THEN
|
||||
RAISE EXCEPTION
|
||||
'published monthly report lineage and manifest are immutable';
|
||||
END IF;
|
||||
RETURN COALESCE(NEW, OLD);
|
||||
END;
|
||||
$$;
|
||||
|
||||
CREATE TRIGGER monthly_run_daily_versions_immutable
|
||||
BEFORE UPDATE OR DELETE ON reporting.monthly_run_daily_versions
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION reporting.protect_published_monthly_child();
|
||||
|
||||
CREATE TRIGGER monthly_channel_manifest_immutable
|
||||
BEFORE UPDATE OR DELETE ON reporting.monthly_channel_manifest
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION reporting.protect_published_monthly_child();
|
||||
|
||||
COMMENT ON TABLE reporting.monthly_runs IS
|
||||
'Durable publication versions and artifact identities only; no guest-bearing monthly business rows are duplicated.';
|
||||
|
||||
COMMENT ON COLUMN reporting.monthly_runs.as_of_date IS
|
||||
'Greatest ARRIVAL included in this publication; never derived from an upload filename or wall-clock date.';
|
||||
|
||||
COMMENT ON TABLE reporting.monthly_run_daily_versions IS
|
||||
'Immutable current Finance daily-version pins used to build one monthly publication.';
|
||||
|
||||
COMMENT ON TABLE reporting.monthly_channel_manifest IS
|
||||
'Privacy-minimized worksheet order and row-count manifest for one monthly publication.';
|
||||
|
||||
COMMIT;
|
||||
Reference in New Issue
Block a user