Slice 2 of the SmartTimeline (docs/design-smart-timeline-2026-05-08.md
§6 + §9 + §10) bundled with m/paliad#31's layered requirements:
Migration 076:
- appointments.deadline_rule_id nullable FK to deadline_rules + partial idx
- deadlines.source CHECK widened to include 'anchor' (alongside existing
'manual','fristenrechner','rule','import').
ProjectionService (extended):
- Wires FristenrechnerService + DeadlineRuleService.
- For() now emits Kind="projected" rows for any rule lacking a matching
paliad.deadlines.rule_id / appointments.deadline_rule_id row, with
Status in {predicted | predicted_overdue | court_set}.
- Lookahead cap (default 7, override via ?lookahead=N, max 50): future
predicted rows beyond N are dropped; predicted_overdue + court_set
rows are exempt from the cap (#31 layer 1).
- Dependency annotations DependsOnRuleCode/Date/Name on every row that
carries a DeadlineRuleID, walked from the rule's parent_id chain
(#31 layer 2). Date prefers actuals over projections.
- AnchorOverrides built from completed deadlines (completed_at /
status='completed') + appointments tied via deadline_rule_id.
- triggerDate derives from the proceeding's root rule's anchor when
present, else today() as placeholder.
Anchor write path (POST /api/projects/{id}/timeline/anchor):
- Sequence guard: if rule.parent_id has no anchored actual, return
409 predecessor_missing with the missing rule's code/name DE+EN +
pre-formatted bilingual messages so the frontend can render an
inline error with a "Stattdessen <predecessor> erfassen" link
(#31 layer 3, no confirm-and-write override in v1).
- kind dispatch: rules with event_type IN ('hearing','decision','order')
write paliad.appointments with deadline_rule_id; everything else
writes paliad.deadlines with source='anchor', status='completed',
completed_at=actual_date.
- Idempotent: existing (project_id, rule_id) row PATCHes instead of
inserting (race-safe per design §13).
Skip write path (POST /api/projects/{id}/timeline/skip):
- Writes paliad.project_events with event_type='rule_skipped' +
metadata.rule_code; subsequent reads drop the matching projected
row from the cascade (§6.4).
Handlers expose projection meta via X-Projection-{Has,Total,Shown,Overdue,Lookahead}
headers so the wire shape stays []TimelineEvent (frozen since Slice 1).
58 lines
2.4 KiB
SQL
58 lines
2.4 KiB
SQL
-- t-paliad-173 — SmartTimeline Slice 2.
|
|
-- Two structural additions for click-to-anchor (§6 of
|
|
-- docs/design-smart-timeline-2026-05-08.md) + the layered SoC→SoD
|
|
-- sequence enforcement from m/paliad#31:
|
|
--
|
|
-- 1. paliad.appointments.deadline_rule_id — nullable FK to
|
|
-- paliad.deadline_rules. Court-set rules (Hauptverhandlung,
|
|
-- Decision, Order) anchor as appointments rather than deadlines
|
|
-- and need to remember which rule they came from so downstream
|
|
-- reflow has the parent_id chain.
|
|
--
|
|
-- 2. paliad.deadlines.source CHECK — adds 'anchor' alongside
|
|
-- the existing 'manual' / 'fristenrechner' values + the two
|
|
-- legacy values the design doc mentions ('rule', 'import') for
|
|
-- forward-compat. 'anchor' separates a click-to-anchor write from
|
|
-- a user-typed-it-in 'manual' write so analytics + a future
|
|
-- Outlook-import path can tell them apart.
|
|
--
|
|
-- paliad.project_events.event_type is intentionally NOT constrained —
|
|
-- the column is free-text in prod (every event_type today lives in
|
|
-- code, not in a CHECK). Slice 2 needs to write 'rule_skipped' rows
|
|
-- (§6.4); no schema change is required for that.
|
|
--
|
|
-- Idempotent: re-applying is a no-op. Tracker advances 75 → 76.
|
|
|
|
-- 1. paliad.appointments.deadline_rule_id ----------------------------------
|
|
|
|
ALTER TABLE paliad.appointments
|
|
ADD COLUMN IF NOT EXISTS deadline_rule_id uuid NULL
|
|
REFERENCES paliad.deadline_rules(id) ON DELETE SET NULL;
|
|
|
|
COMMENT ON COLUMN paliad.appointments.deadline_rule_id IS
|
|
'When non-NULL, this appointment is the actual occurrence of a '
|
|
'standard-course rule (Hauptverhandlung, Decision, Order). '
|
|
'Anchors downstream re-projection via FristenrechnerService '
|
|
'AnchorOverrides. See docs/design-smart-timeline-2026-05-08.md §6.';
|
|
|
|
CREATE INDEX IF NOT EXISTS appointments_deadline_rule_id_idx
|
|
ON paliad.appointments (deadline_rule_id)
|
|
WHERE deadline_rule_id IS NOT NULL;
|
|
|
|
-- 2. paliad.deadlines.source CHECK -----------------------------------------
|
|
|
|
DO $$
|
|
BEGIN
|
|
IF EXISTS (
|
|
SELECT 1 FROM pg_constraint
|
|
WHERE conname = 'deadlines_source_check'
|
|
AND conrelid = 'paliad.deadlines'::regclass
|
|
) THEN
|
|
ALTER TABLE paliad.deadlines DROP CONSTRAINT deadlines_source_check;
|
|
END IF;
|
|
END $$;
|
|
|
|
ALTER TABLE paliad.deadlines
|
|
ADD CONSTRAINT deadlines_source_check
|
|
CHECK (source IN ('manual', 'fristenrechner', 'rule', 'import', 'anchor'));
|