Phase 3 Slice 3 Step C (design §3.C). INSERT 77 active rows from
paliad.event_deadlines into paliad.deadline_rules so the unified
backend can serve both pipelines. Source rows preserved (mig 086
wraps the source table in a read-only trigger; Slice 9 drops it).
Mapping:
trigger_event_id ← event_deadlines.trigger_event_id (bigint, mig 028)
name (DE, NOT NULL) ← event_deadlines.title_de (NOT NULL DEFAULT '')
name_en (NOT NULL) ← event_deadlines.title (EN, NOT NULL)
duration_value / unit ← event_deadlines.duration_value / unit
timing ← event_deadlines.timing (before / after)
alt_duration_value / unit ← event_deadlines.alt_duration_*
combine_op ← event_deadlines.combine_op (mig 078 column)
deadline_notes (DE) ← event_deadlines.notes (DE; NULLIF '' so empty
stays NULL on dr side)
deadline_notes_en ← event_deadlines.notes_en (mig 036)
legal_source ← event_deadlines.legal_source
published_at ← event_deadlines.created_at (chronological audit)
sequence_order = 1000 + ed.id (large offset so Pipeline-C rules
sort after any hand-authored
Pipeline-A sequence_orders; preserves
source ordering within Pipeline C)
lifecycle_state = 'published' / priority = 'mandatory' / is_active = ed.is_active
Pipeline-A-only fields stay NULL on the new rows: proceeding_type_id,
parent_id, spawn_proceeding_type_id, code, primary_party, event_type,
condition_expr, condition_flag. is_court_set = false (no court-set
rules in the Pipeline-C corpus today; legal-review pass can flip
Zustellung-* later via a separate slice).
Idempotency: WHERE NOT EXISTS guard on (trigger_event_id, name).
Re-running the migration is a no-op.
Hard assertion at end: COUNT(deadline_rules WHERE trigger_event_id
IS NOT NULL) must equal COUNT(event_deadlines WHERE is_active=true)
post-mig. RAISE EXCEPTION on mismatch — better to fail the migration
loudly than to ship a partial Pipeline-C corpus and poison Slice 4.
Audit-reason set via set_config so the mig 079 trigger writes 77
paliad.deadline_rule_audit rows with the design §3.C citation
preserved as the rationale. That's the persistent compliance trail
for the data-move.
No mandatory bool on event_deadlines (the head instruction sketch
suggested mapping it; the schema doesn't have one) — Pipeline-C
rules default priority='mandatory', consistent with the statutory
nature of the corpus.
185 lines
8.9 KiB
SQL
185 lines
8.9 KiB
SQL
-- t-paliad-184 / Fristen Phase 3 Slice 3 Step C — data-move 77 rows
|
||
-- from paliad.event_deadlines → paliad.deadline_rules so the Phase-3
|
||
-- unified backend can serve both pipelines.
|
||
--
|
||
-- Source rows are PRESERVED (mig 086's read-only trigger blocks
|
||
-- further writes; mig 090 in Slice 9 drops the table once every
|
||
-- caller has cut over). The data-move is one-way; legacy callers
|
||
-- continue reading event_deadlines via plain SELECTs until Slice 9.
|
||
--
|
||
-- Mapping (per design §3.C):
|
||
--
|
||
-- paliad.event_deadlines → paliad.deadline_rules
|
||
-- ------------------------- ----------------------
|
||
-- id (new gen_random_uuid())
|
||
-- trigger_event_id trigger_event_id (Phase 3 column from mig 078)
|
||
-- title (EN, NOT NULL) name_en (NOT NULL)
|
||
-- title_de (DE, NOT NULL DEFAULT '') name (NOT NULL — every row has non-empty title_de in live data)
|
||
-- duration_value duration_value
|
||
-- duration_unit (days/weeks/months/working_days) duration_unit
|
||
-- timing (before/after) timing
|
||
-- notes (DE) deadline_notes (DE)
|
||
-- notes_en (EN, nullable) deadline_notes_en (EN, nullable)
|
||
-- alt_duration_value alt_duration_value
|
||
-- alt_duration_unit alt_duration_unit
|
||
-- combine_op (max/min, nullable) combine_op (Phase 3 column from mig 078)
|
||
-- legal_source legal_source
|
||
-- is_active is_active
|
||
-- created_at published_at (preserves chronology — lifecycle_state='published' on every row)
|
||
-- updated_at = now() (this is the publish event)
|
||
--
|
||
-- Pipeline-A-only fields default:
|
||
-- proceeding_type_id = NULL (event-rooted, no proceeding)
|
||
-- parent_id = NULL (Pipeline C is flat, no chain)
|
||
-- spawn_proceeding_type_id = NULL (no spawn)
|
||
-- code = NULL (no local rule code in Pipeline C)
|
||
-- primary_party = NULL (event_deadlines has no party column)
|
||
-- event_type = NULL (filing/hearing/decision is a
|
||
-- Pipeline-A category)
|
||
-- is_court_set = false (no court-set Pipeline-C rules
|
||
-- in the corpus; legal-review
|
||
-- pass can flip Zustellung-* if
|
||
-- those ever land here)
|
||
-- is_spawn = false
|
||
-- is_mandatory = true (Pipeline C has no mandatory
|
||
-- bool; design §2.3 says default
|
||
-- 'mandatory' is correct for
|
||
-- statutory event-driven deadlines)
|
||
-- is_optional = false
|
||
-- priority = 'mandatory'
|
||
-- condition_expr = NULL (Pipeline C has no flag gating)
|
||
-- condition_flag = NULL
|
||
-- sequence_order = 1000 + event_deadlines.id
|
||
-- (large offset so Pipeline-C
|
||
-- rows sort AFTER any future
|
||
-- hand-edited Pipeline-A
|
||
-- sequence_orders without
|
||
-- colliding with the
|
||
-- existing 0–171 range)
|
||
-- lifecycle_state = 'published'
|
||
--
|
||
-- Idempotency: WHERE NOT EXISTS guard on (trigger_event_id, name) skips
|
||
-- rows that already exist in deadline_rules. Re-running the migration
|
||
-- is a no-op.
|
||
--
|
||
-- Hard assertion at end: COUNT(deadline_rules WHERE trigger_event_id
|
||
-- IS NOT NULL) == COUNT(event_deadlines WHERE is_active = true) (77 = 77).
|
||
-- RAISE EXCEPTION on mismatch so a partial move fails the migration
|
||
-- loudly instead of poisoning Slice 4.
|
||
--
|
||
-- Audit-reason cites design §3.C — the rationale persists in the
|
||
-- paliad.deadline_rule_audit log forever via the mig 079 trigger.
|
||
|
||
SELECT set_config(
|
||
'paliad.audit_reason',
|
||
'pipeline C migration 085: data-move event_deadlines → deadline_rules per design §3.C — '
|
||
|| 'preserves source rows; mig 086 wraps the source table read-only',
|
||
true);
|
||
|
||
INSERT INTO paliad.deadline_rules (
|
||
id,
|
||
proceeding_type_id,
|
||
parent_id,
|
||
trigger_event_id,
|
||
spawn_proceeding_type_id,
|
||
code,
|
||
name,
|
||
name_en,
|
||
primary_party,
|
||
event_type,
|
||
is_mandatory,
|
||
is_optional,
|
||
is_court_set,
|
||
is_spawn,
|
||
duration_value,
|
||
duration_unit,
|
||
timing,
|
||
alt_duration_value,
|
||
alt_duration_unit,
|
||
combine_op,
|
||
rule_code,
|
||
deadline_notes,
|
||
deadline_notes_en,
|
||
legal_source,
|
||
condition_expr,
|
||
condition_flag,
|
||
sequence_order,
|
||
is_active,
|
||
priority,
|
||
lifecycle_state,
|
||
draft_of,
|
||
published_at,
|
||
created_at,
|
||
updated_at
|
||
)
|
||
SELECT
|
||
gen_random_uuid() AS id,
|
||
NULL::integer AS proceeding_type_id,
|
||
NULL::uuid AS parent_id,
|
||
ed.trigger_event_id AS trigger_event_id,
|
||
NULL::integer AS spawn_proceeding_type_id,
|
||
NULL::text AS code,
|
||
ed.title_de AS name,
|
||
ed.title AS name_en,
|
||
NULL::text AS primary_party,
|
||
NULL::text AS event_type,
|
||
true AS is_mandatory,
|
||
false AS is_optional,
|
||
false AS is_court_set,
|
||
false AS is_spawn,
|
||
ed.duration_value AS duration_value,
|
||
ed.duration_unit AS duration_unit,
|
||
ed.timing AS timing,
|
||
ed.alt_duration_value AS alt_duration_value,
|
||
ed.alt_duration_unit AS alt_duration_unit,
|
||
ed.combine_op AS combine_op,
|
||
NULL::text AS rule_code,
|
||
NULLIF(ed.notes, '') AS deadline_notes,
|
||
ed.notes_en AS deadline_notes_en,
|
||
ed.legal_source AS legal_source,
|
||
NULL::jsonb AS condition_expr,
|
||
NULL::text[] AS condition_flag,
|
||
(1000 + ed.id)::integer AS sequence_order,
|
||
ed.is_active AS is_active,
|
||
'mandatory' AS priority,
|
||
'published' AS lifecycle_state,
|
||
NULL::uuid AS draft_of,
|
||
ed.created_at AS published_at,
|
||
ed.created_at AS created_at,
|
||
now() AS updated_at
|
||
FROM paliad.event_deadlines ed
|
||
WHERE ed.is_active = true
|
||
AND NOT EXISTS (
|
||
SELECT 1
|
||
FROM paliad.deadline_rules dr
|
||
WHERE dr.trigger_event_id = ed.trigger_event_id
|
||
AND dr.name = ed.title_de
|
||
);
|
||
|
||
-- Hard assertion: every active event_deadlines row must have a matching
|
||
-- deadline_rules row by (trigger_event_id, name). If the counts diverge,
|
||
-- something in the WHERE NOT EXISTS clause (likely a stale duplicate)
|
||
-- prevented a real insert — fail the migration rather than ship a
|
||
-- partial Pipeline-C corpus.
|
||
DO $$
|
||
DECLARE
|
||
n_source int;
|
||
n_target int;
|
||
BEGIN
|
||
SELECT count(*) INTO n_source
|
||
FROM paliad.event_deadlines WHERE is_active = true;
|
||
|
||
SELECT count(*) INTO n_target
|
||
FROM paliad.deadline_rules WHERE trigger_event_id IS NOT NULL;
|
||
|
||
RAISE NOTICE 'mig 085: event_deadlines(active)=%, deadline_rules(trigger_event_id IS NOT NULL)=%',
|
||
n_source, n_target;
|
||
|
||
IF n_target <> n_source THEN
|
||
RAISE EXCEPTION 'mig 085: data-move incomplete — expected % unified rows, got %. '
|
||
'Investigate event_deadlines (trigger_event_id, title_de) duplicates '
|
||
'OR re-applied migration on dirtied target.',
|
||
n_source, n_target;
|
||
END IF;
|
||
END $$;
|