Files
paliad/cmd/server
m 85d7dd497c feat(t-paliad-173): SmartTimeline Slice 2 backend — projection + anchor + skip + sequence guard
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).
2026-05-09 15:33:20 +02:00
..