Deadline-rule primitives: working_days unit, combine=max, multi-anchor, outer-cap, before-mode backward snap (Wave 2) #103

Open
opened 2026-05-25 13:34:35 +00:00 by mAi · 0 comments
Collaborator

Why this issue exists

m's 2026-05-25 15:29 steer: "we prefer the good and solid approach over a quick fix. We want the full solution". This overrides the (R) defer on §9 Q2/Q3/Q4 of curie's audit (docs/research-deadlines-completeness-2026-05-25.md) — we BUILD the primitives instead of documenting workarounds.

Without these primitives, at least 8 RoP/EPC rules cannot be modelled accurately (R.198, R.213, R.245.2, R.320, EPC Art.112a, R.109.1, R.109.4, R.131.2).

Scope — Tier 3 primitives (curie's §10 Tier 3)

Primitive 1 — duration_unit = 'working_days'

Adds a business-day arithmetic path to the deadline calculator. Counts only Mon–Fri minus court holidays from paliad.gerichte / public holiday tables.

  • Schema: paliad.deadline_rules.duration_unit already accepts string; verify enum / check constraint. Add 'working_days' to allowed set if not present.
  • Calculator: extend internal/services/deadline_calculator.go (or similar) — when duration_unit='working_days', walk forward N business days from the anchor.
  • Holiday lookup: respect the project's gerichte / jurisdiction so a UPC case skips the right Sundays + Bundesfeiertage + division-specific closures.
  • Unit tests + integration tests covering corner cases (anchor on weekend, period crosses year-end, leap year).

Primitive 2 — combine_op = 'max'

When a rule has two anchors (primary_anchor + secondary_anchor), combine_op decides how to combine:

  • min — earliest of the two end dates (current behavior for some rules)

  • max — latest of the two end dates (NEW)

  • Schema: column combine_op text may exist per pauli's 2026-05-13 audit observation — verify. If not, add as nullable with default NULL (= single-anchor, no combine).

  • Service: extend the projection to handle two anchors + the combine_op.

  • UI: render combined dates correctly in the timeline.

Primitive 3 — Multi-anchor "whichever later" trigger

Some rules anchor on the later of two user-supplied dates (e.g. R.245.2.a/b: 2 months from final decision OR defect discovery, whichever later).

  • DB: paliad.deadline_rules gains secondary_trigger_event_id, plus the new combine_op from Primitive 2.
  • Anchor capture: the user can record TWO trigger dates per such rule. Existing paliad.deadline_anchors may need to support multiple rows per (project, rule) — verify.
  • UI: capture flow lets the user enter both dates (e.g. two date pickers on the anchor capture form).

Primitive 4 — Outer-cap modelling

Some deadlines have a fixed outer cap regardless of trigger (e.g. R.245.2 + R.320 + EPC Art.112a — all 12 months from a different anchor than the per-case 2-month period).

  • Schema: outer_cap_value int, outer_cap_unit text, outer_cap_anchor_event_id columns on paliad.deadline_rules.
  • Service: when computing the deadline, also compute the outer cap. The effective deadline = min(per-case period, outer cap).
  • UI: render BOTH dates on the card with a small "outer cap" annotation when they differ.

Primitive 5 — Backward timing='before' mode + snap-to-working-day

Rules that anchor BACKWARDS from a future event (e.g. R.109.1 = 1 month BEFORE oral hearing, R.109.4 = 2 weeks BEFORE oral hearing). Schema supports timing='before' but no rule populates it AND the calculator doesn't have a backward path.

  • Calculator: when timing='before', subtract the period from the anchor. Snap result to the nearest preceding working day if it falls on weekend/holiday.
  • UI: render with directional indicator (e.g. "2 weeks before Oral Hearing" rather than a forward date).
  • Edge: if the backward result is in the past at compute time, mark as "missed" (existing handling).

Primitive 6 — Wiedereinsetzung backward-from-missed-deadline arithmetic (Q4 BUILD)

m's call: build the calendar arithmetic for PatG §123 / R.320 Wiedereinsetzung. The 2-month period anchors on the missed deadline AND a 1-year outer cap from the missed deadline.

  • Schema: same outer-cap shape as Primitive 4, BUT anchored on the missed-deadline-id (which is itself a deadline in the same proceeding).
  • Service: new "missed-deadline" anchor type. When a deadline is marked overdue / missed, derive the wiedereinsetzung deadline at compute time.
  • UI: a small Wiedereinsetzung-Frist chip on overdue deadlines (only when the rule allows it — UPC R.320, PatG §123, ZPO §233).

Cross-proceeding spawn execution (Q9 / Q13 — full wire)

m's Q9 (wire is_spawn from upc.inf.cfi.decisionupc.dmgs.cfi.app) + Q13 (cross-proc spawn end-to-end) require:

  • Projection service: when a is_spawn=true rule fires (its anchor lands), AUTO-CREATE a sibling project of the target proceeding_type, link it via spawn_from = source_project.id, and seed its deadlines.
  • Or: don't auto-create projects, but DO surface the spawned-proceeding-timeline as a visual layer on the source project's SmartTimeline.
  • m's preferred answer (R=wire end-to-end) implies the auto-create path. Confirm before shipping — possibly inventor design pass first.

Slice plan

  • Slice A — Primitives 1+2+5 (working_days + combine_op + backward-before mode). These unlock 5+ rules immediately (Tier 1 T1.8 + T1.9 + T1.10 + R.245.2 partial). ~250 LoC calculator + ~100 LoC migration + tests.
  • Slice B — Primitive 3+4 (multi-anchor + outer-cap). Unlocks R.245.2 + R.320 + EPC Art.112a outer-cap.
  • Slice C — Primitive 6 (Wiedereinsetzung) + Q9 spawn execution. Cross-proceeding mechanics.
  • Slice D — Wave 1 / Wave 3 rule additions that DEPEND on these primitives (e.g. T1.8 upc.pi.cfi.merits_start which is blocked on working_days). Pure data, runs after the primitives compile.

Hard rules

  • No regression on existing deadline-rule projection — every current rule keeps computing the same date. The new primitives are additive.
  • Test coverage for each primitive — table-driven tests, week boundaries, year boundaries, holidays, leap years.
  • Cite curie's audit in each migration: -- per docs/research-deadlines-completeness-2026-05-25.md Tier 3 Primitive N.
  • go build ./... && go test ./internal/... && cd frontend && bun run build clean.
  • Branch: mai/<worker>/wave2-tier3-primitives-slice-<a/b/c/d>.
  • Solid > quick fix. Don't shortcut the calculator with a workaround. Schema additions belong in the schema.

Out of scope

  • Tier 2 cascade work (separate Wave).
  • Q10 GebrMG (deferred per tracker).
  • The 13 ambiguity Q's themselves — those are answered in head's locked decisions.

Reporting

mai report completed per slice with branch + SHAs + migration slot + which primitives shipped + which Tier-1 rule additions are now unblocked.

## Why this issue exists m's 2026-05-25 15:29 steer: "we prefer the good and solid approach over a quick fix. We want the full solution". This overrides the (R) defer on §9 Q2/Q3/Q4 of curie's audit (docs/research-deadlines-completeness-2026-05-25.md) — we BUILD the primitives instead of documenting workarounds. Without these primitives, at least 8 RoP/EPC rules cannot be modelled accurately (R.198, R.213, R.245.2, R.320, EPC Art.112a, R.109.1, R.109.4, R.131.2). ## Scope — Tier 3 primitives (curie's §10 Tier 3) ### Primitive 1 — `duration_unit = 'working_days'` Adds a business-day arithmetic path to the deadline calculator. Counts only Mon–Fri minus court holidays from `paliad.gerichte` / public holiday tables. - Schema: `paliad.deadline_rules.duration_unit` already accepts string; verify enum / check constraint. Add `'working_days'` to allowed set if not present. - Calculator: extend `internal/services/deadline_calculator.go` (or similar) — when `duration_unit='working_days'`, walk forward N business days from the anchor. - Holiday lookup: respect the project's gerichte / jurisdiction so a UPC case skips the right Sundays + Bundesfeiertage + division-specific closures. - Unit tests + integration tests covering corner cases (anchor on weekend, period crosses year-end, leap year). ### Primitive 2 — `combine_op = 'max'` When a rule has two anchors (`primary_anchor` + `secondary_anchor`), `combine_op` decides how to combine: - `min` — earliest of the two end dates (current behavior for some rules) - **`max`** — latest of the two end dates (NEW) - Schema: column `combine_op text` may exist per pauli's 2026-05-13 audit observation — verify. If not, add as nullable with default `NULL` (= single-anchor, no combine). - Service: extend the projection to handle two anchors + the combine_op. - UI: render combined dates correctly in the timeline. ### Primitive 3 — Multi-anchor "whichever later" trigger Some rules anchor on the later of two user-supplied dates (e.g. R.245.2.a/b: 2 months from final decision OR defect discovery, whichever later). - DB: `paliad.deadline_rules` gains `secondary_trigger_event_id`, plus the new `combine_op` from Primitive 2. - Anchor capture: the user can record TWO trigger dates per such rule. Existing `paliad.deadline_anchors` may need to support multiple rows per (project, rule) — verify. - UI: capture flow lets the user enter both dates (e.g. two date pickers on the anchor capture form). ### Primitive 4 — Outer-cap modelling Some deadlines have a fixed outer cap regardless of trigger (e.g. R.245.2 + R.320 + EPC Art.112a — all 12 months from a different anchor than the per-case 2-month period). - Schema: `outer_cap_value int`, `outer_cap_unit text`, `outer_cap_anchor_event_id` columns on `paliad.deadline_rules`. - Service: when computing the deadline, also compute the outer cap. The effective deadline = min(per-case period, outer cap). - UI: render BOTH dates on the card with a small "outer cap" annotation when they differ. ### Primitive 5 — Backward `timing='before'` mode + snap-to-working-day Rules that anchor BACKWARDS from a future event (e.g. R.109.1 = 1 month BEFORE oral hearing, R.109.4 = 2 weeks BEFORE oral hearing). Schema supports `timing='before'` but no rule populates it AND the calculator doesn't have a backward path. - Calculator: when `timing='before'`, subtract the period from the anchor. Snap result to the nearest preceding working day if it falls on weekend/holiday. - UI: render with directional indicator (e.g. "2 weeks before Oral Hearing" rather than a forward date). - Edge: if the backward result is in the past at compute time, mark as "missed" (existing handling). ### Primitive 6 — Wiedereinsetzung backward-from-missed-deadline arithmetic (Q4 BUILD) m's call: build the calendar arithmetic for PatG §123 / R.320 Wiedereinsetzung. The 2-month period anchors on the missed deadline AND a 1-year outer cap from the missed deadline. - Schema: same outer-cap shape as Primitive 4, BUT anchored on the missed-deadline-id (which is itself a deadline in the same proceeding). - Service: new "missed-deadline" anchor type. When a deadline is marked overdue / missed, derive the wiedereinsetzung deadline at compute time. - UI: a small `Wiedereinsetzung-Frist` chip on overdue deadlines (only when the rule allows it — UPC R.320, PatG §123, ZPO §233). ### Cross-proceeding spawn execution (Q9 / Q13 — full wire) m's Q9 (wire `is_spawn` from `upc.inf.cfi.decision` → `upc.dmgs.cfi.app`) + Q13 (cross-proc spawn end-to-end) require: - Projection service: when a `is_spawn=true` rule fires (its anchor lands), AUTO-CREATE a sibling project of the target proceeding_type, link it via `spawn_from = source_project.id`, and seed its deadlines. - Or: don't auto-create projects, but DO surface the spawned-proceeding-timeline as a visual layer on the source project's SmartTimeline. - m's preferred answer (R=wire end-to-end) implies the auto-create path. Confirm before shipping — possibly inventor design pass first. ## Slice plan - **Slice A** — Primitives 1+2+5 (working_days + combine_op + backward-before mode). These unlock 5+ rules immediately (Tier 1 T1.8 + T1.9 + T1.10 + R.245.2 partial). ~250 LoC calculator + ~100 LoC migration + tests. - **Slice B** — Primitive 3+4 (multi-anchor + outer-cap). Unlocks R.245.2 + R.320 + EPC Art.112a outer-cap. - **Slice C** — Primitive 6 (Wiedereinsetzung) + Q9 spawn execution. Cross-proceeding mechanics. - **Slice D** — Wave 1 / Wave 3 rule additions that DEPEND on these primitives (e.g. T1.8 `upc.pi.cfi.merits_start` which is blocked on working_days). Pure data, runs after the primitives compile. ## Hard rules - **No regression** on existing deadline-rule projection — every current rule keeps computing the same date. The new primitives are additive. - **Test coverage** for each primitive — table-driven tests, week boundaries, year boundaries, holidays, leap years. - **Cite curie's audit** in each migration: `-- per docs/research-deadlines-completeness-2026-05-25.md Tier 3 Primitive N`. - `go build ./... && go test ./internal/... && cd frontend && bun run build` clean. - Branch: `mai/<worker>/wave2-tier3-primitives-slice-<a/b/c/d>`. - **Solid > quick fix.** Don't shortcut the calculator with a workaround. Schema additions belong in the schema. ## Out of scope - Tier 2 cascade work (separate Wave). - Q10 GebrMG (deferred per tracker). - The 13 ambiguity Q's themselves — those are answered in head's locked decisions. ## Reporting `mai report completed` per slice with branch + SHAs + migration slot + which primitives shipped + which Tier-1 rule additions are now unblocked.
mAi self-assigned this 2026-05-25 13:34:35 +00:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: m/paliad#103
No description provided.