design(t-paliad-131): v2 — flip slug rule (EN for shared) + drop flag_param

m's revisions (23:36):

- Q1 corrected: EN slug for shared concepts too (klageerwiderung →
  statement-of-defence, replik → reply-to-defence, berufungsfrist →
  notice-of-appeal, einspruchsfrist → opposition, wiedereinsetzung →
  re-establishment-of-rights). DE slug only for German-law-only
  concepts (nichtzulassungsbeschwerde, versaeumnisurteil-einspruch,
  hinweisbeschluss-stellungnahme).

- Q4 simplified: drop the customizable-extension flag_param mechanism.
  Replace with a generalised "user can override any computed date,
  downstream re-anchors off it" capability. CalcOptions gains
  AnchorOverrides map[string]string; tree-walk consults it before the
  computed-date map. UI gives each row a click-to-edit date affordance
  (also unlocks court-set decision dates being entered post-hoc, which
  the existing IsCourtSet placeholder UX has been hinting at). PatG §82
  seed stays at 2 months static; user-set extensions handled by inline
  date override, not by a flag_param mechanism.

  Cleaner. No new DB column. Generalises beyond extensions to any case
  where the user knows the real date better than the calculator's
  projection.
This commit is contained in:
m
2026-05-04 23:38:23 +02:00
parent 94ebc1d043
commit 20eaa9bba4

View File

@@ -291,27 +291,27 @@ ALTER TABLE paliad.event_deadlines
### 4.4 Concept ↔ rule mapping examples ### 4.4 Concept ↔ rule mapping examples
**Slug naming rule (Q1, locked):** EN slug for concepts native to UPC/EPC; DE slug for concepts that exist only in German law; **DE slug for shared concepts** (those that exist in both DE and UPC/EPC) because m's primary working language is German and the slug is internal/maintenance-facing only — `name_de` and `name_en` carry both labels for users. **Slug naming rule (Q1, locked):** **EN slug** for concepts native to UPC/EPC AND for **shared concepts** that exist in both DE and UPC/EPC. **DE slug** only for concepts that exist exclusively in German law (no UPC/EPC equivalent). `name_de` and `name_en` carry both labels for the user-facing surface; the slug is internal/maintenance-facing.
| concept slug | name_de | name_en | maps to deadline_rules in (proceeding_type, code) | | concept slug | name_de | name_en | maps to deadline_rules in (proceeding_type, code) |
|---|---|---|---| |---|---|---|---|
| `klageerwiderung` | Klageerwiderung | Statement of Defence | (UPC_INF, inf.sod), (DE_INF, de_inf.erwidg), (DE_NULL, de_null.erwidg), (UPC_REV, rev.defence), (EPA_OPP, epa_opp.erwidg), (DPMA_OPP, dpma_opp.erwidg) — shared, DE wins | | `statement-of-defence` | Klageerwiderung | Statement of Defence | (UPC_INF, inf.sod), (DE_INF, de_inf.erwidg), (DE_NULL, de_null.erwidg), (UPC_REV, rev.defence), (EPA_OPP, epa_opp.erwidg), (DPMA_OPP, dpma_opp.erwidg) — shared, EN |
| `replik` | Replik | Reply to Defence | (UPC_INF, inf.reply), (DE_INF, de_inf.replik), (UPC_REV, rev.reply), (UPC_DAMAGES, damages.reply), (UPC_DISCOVERY, disc.reply) — shared, DE wins | | `reply-to-defence` | Replik | Reply to Defence | (UPC_INF, inf.reply), (DE_INF, de_inf.replik), (UPC_REV, rev.reply), (UPC_DAMAGES, damages.reply), (UPC_DISCOVERY, disc.reply) — shared, EN |
| `duplik` | Duplik | Rejoinder | (UPC_INF, inf.rejoin), (UPC_REV, rev.rejoin), (DE_INF, de_inf.duplik), (UPC_DAMAGES, damages.rejoin), … — shared, DE wins | | `rejoinder` | Duplik | Rejoinder | (UPC_INF, inf.rejoin), (UPC_REV, rev.rejoin), (DE_INF, de_inf.duplik), (UPC_DAMAGES, damages.rejoin), … — shared, EN |
| `berufungsfrist` | Berufungsfrist | Notice of Appeal Period | (DE_INF, de_inf.berufung), (DE_NULL, de_null.berufung), (UPC_APP, app.notice), (EPA_APP, epa_app.beschwerde) — shared, DE wins | | `notice-of-appeal` | Berufungsschrift | Notice of Appeal | (DE_INF, de_inf.berufung), (DE_NULL, de_null.berufung), (UPC_APP, app.notice), (EPA_APP, epa_app.beschwerde) — shared, EN |
| `berufungsbegruendung` | Berufungsbegründung | Statement of Grounds of Appeal | (DE_INF, de_inf.beruf_begr), (DE_NULL, de_null.beruf_begr), (UPC_APP, app.grounds), (EPA_APP, epa_app.begr) — shared, DE wins | | `statement-of-grounds-of-appeal` | Berufungsbegründung | Statement of Grounds of Appeal | (DE_INF, de_inf.beruf_begr), (DE_NULL, de_null.beruf_begr), (UPC_APP, app.grounds), (EPA_APP, epa_app.begr) — shared, EN |
| `einspruchsfrist` | Einspruchsfrist | Opposition Period | (EPA_OPP, epa_opp.frist), (DPMA_OPP, dpma_opp.frist) — shared, DE wins | | `opposition` | Einspruch / Einspruchsfrist | Opposition | (EPA_OPP, epa_opp.frist), (DPMA_OPP, dpma_opp.frist) — shared, EN |
| `wiedereinsetzung` | Wiedereinsetzung in den vorigen Stand | Re-establishment of Rights | event_trigger only — PatG §123, ZPO §233, EPÜ Art.122, DPMA §123 — shared cross-cutting, DE wins (HLC vocab) | | `re-establishment-of-rights` | Wiedereinsetzung in den vorigen Stand | Re-establishment of Rights | event_trigger only — PatG §123, ZPO §233, EPÜ Art.122, DPMA §123 — shared cross-cutting, EN |
| `nichtzulassungsbeschwerde` | Nichtzulassungsbeschwerde | Complaint Against Denial of Leave | (DE_INF_BGH, …) — DE-only, DE slug | | `application-to-amend` | Antrag auf Patentänderung | Application to Amend the Patent | (UPC_INF, inf.app_to_amend), (UPC_REV, rev.app_to_amend) — UPC/EPC-native, EN |
| `versaeumnisurteil-einspruch` | Einspruch gegen Versäumnisurteil | Objection to Default Judgment | event_trigger only — ZPO §339 — DE-only, DE slug | | `defence-to-application-to-amend` | Erwiderung auf den Antrag auf Patentänderung | Defence to Application to Amend | (UPC_INF, inf.def_to_amend), (UPC_REV, rev.def_to_amend) — UPC-native, EN |
| `hinweisbeschluss-stellungnahme` | Stellungnahme zum Hinweisbeschluss | Response to Court's Preliminary Opinion | (DE_NULL, …) — DE-only, DE slug |
| `application-to-amend` | Antrag auf Patentänderung | Application to Amend the Patent | (UPC_INF, inf.app_to_amend), (UPC_REV, rev.app_to_amend) — UPC/EPC-native, EN slug |
| `defence-to-application-to-amend` | Erwiderung auf den Antrag auf Patentänderung | Defence to App to Amend | (UPC_INF, inf.def_to_amend), (UPC_REV, rev.def_to_amend) — UPC-native, EN |
| `counterclaim-for-revocation` | Nichtigkeitswiderklage | Counterclaim for Revocation | (UPC_INF, inf.ccr_filing) — UPC-native, EN | | `counterclaim-for-revocation` | Nichtigkeitswiderklage | Counterclaim for Revocation | (UPC_INF, inf.ccr_filing) — UPC-native, EN |
| `counterclaim-for-infringement` | Verletzungswiderklage | Counterclaim for Infringement | (UPC_REV, rev.cc_inf) — UPC-native, EN | | `counterclaim-for-infringement` | Verletzungswiderklage | Counterclaim for Infringement | (UPC_REV, rev.cc_inf) — UPC-native, EN |
| `request-for-discretionary-review` | Antrag auf Ermessensüberprüfung | Request for Discretionary Review | (UPC_APP_ORDERS, app_ord.discretion) — UPC-native, EN | | `request-for-discretionary-review` | Antrag auf Ermessensüberprüfung | Request for Discretionary Review | (UPC_APP_ORDERS, app_ord.discretion) — UPC-native, EN |
| `oral-hearing` | Mündliche Verhandlung | Oral Hearing | (every UPC tree, oral), (DE_INF, de_inf.termin), (DE_NULL, de_null.termin), (EPA_*, oral) — court-set, EN slug because UPC/EPC dominate the surface area in this app | | `oral-hearing` | Mündliche Verhandlung | Oral Hearing | (every UPC tree, oral), (DE_INF, de_inf.termin), (DE_NULL, de_null.termin), (EPA_*, oral) — court-set, shared, EN |
| `decision` | Entscheidung | Decision | court-set, ditto, EN | | `decision` | Entscheidung | Decision | court-set, shared, EN |
| `nichtzulassungsbeschwerde` | Nichtzulassungsbeschwerde | Complaint Against Denial of Leave | (DE_INF_BGH, …) — DE-only, DE slug |
| `versaeumnisurteil-einspruch` | Einspruch gegen Versäumnisurteil | Objection to Default Judgment | event_trigger only — ZPO §339 — DE-only, DE slug |
| `hinweisbeschluss-stellungnahme` | Stellungnahme zum Hinweisbeschluss | Response to Court's Preliminary Opinion | (DE_NULL, …) — DE-only, DE slug |
≈ 30 concepts cover the entire seed corpus after Phase B coverage migrations (≈ +85 rules grouped into those 30 concepts). ≈ 30 concepts cover the entire seed corpus after Phase B coverage migrations (≈ +85 rules grouped into those 30 concepts).
@@ -534,7 +534,7 @@ Cross-cutting (best as event_trigger only): R.16(3)(a) / R.27(2) / R.89(2) / R.2
| # | Trigger | Source code | Duration | Anchor | Status | | # | Trigger | Source code | Duration | Anchor | Status |
|---|---|---|---|---|---| |---|---|---|---|---|---|
| 1 | Nichtigkeitsklage | (anchor) | 0 | trigger date | EXISTS | | 1 | Nichtigkeitsklage | (anchor) | 0 | trigger date | EXISTS |
| 2 | Klageerwiderung | `DE.PatG.82.1` | 1 month base + customizable extension (default 1mo) via `with_extension` flag | service of Klage | EXISTS as static 2mo; remodel per Q4 | | 2 | Klageerwiderung | `DE.PatG.82.1` | 2 months (= 1mo base + 1mo typische richterliche Verlängerung; user can override exact date inline per Q4) | service of Klage | EXISTS as 2mo keep |
| 3 | Replik | `DE.PatG.83.2` | court-set, ~2 months | service of Erwiderung | **GAP** | | 3 | Replik | `DE.PatG.83.2` | court-set, ~2 months | service of Erwiderung | **GAP** |
| 4 | Hinweisbeschluss | `DE.PatG.83.1` | court-issued (~6 mo before mündl. Verhandlung) | (court event) | **GAP** | | 4 | Hinweisbeschluss | `DE.PatG.83.1` | court-issued (~6 mo before mündl. Verhandlung) | (court event) | **GAP** |
| 5 | Stellungnahme zum Hinweis | `DE.PatG.83.2` | court-set, ~3 months | service of Hinweisbeschluss | **GAP** | | 5 | Stellungnahme zum Hinweis | `DE.PatG.83.2` | court-set, ~3 months | service of Hinweisbeschluss | **GAP** |
@@ -734,8 +734,9 @@ Per m's "augment, not replace" — Phase E (subsumption) from v1 is dropped.
- Migration A2: add `concept_id` FK on `deadline_rules`, `legal_source` text on `deadline_rules` and `event_deadlines`, `concept_id` slug-text on `trigger_events`. - Migration A2: add `concept_id` FK on `deadline_rules`, `legal_source` text on `deadline_rules` and `event_deadlines`, `concept_id` slug-text on `trigger_events`.
- Migration A3: change `deadline_rules.condition_flag` from `text` to `text[]` (Q3); update existing rows. The `Calculate` function gains a small loop change: instead of `if rule.condition_flag matches one flag in flagSet` it becomes `if rule.condition_flag is empty OR all elements of rule.condition_flag are in flagSet`. - Migration A3: change `deadline_rules.condition_flag` from `text` to `text[]` (Q3); update existing rows. The `Calculate` function gains a small loop change: instead of `if rule.condition_flag matches one flag in flagSet` it becomes `if rule.condition_flag is empty OR all elements of rule.condition_flag are in flagSet`.
- Migration A4: backfill seed the ~30 concepts; UPDATE `deadline_rules` SET `concept_id = …` per row; backfill `legal_source` from existing rule_code mapping (algorithm: `'RoP 23'` `'UPC.RoP.23'`, `'§ 276 ZPO'` `'DE.ZPO.276'`, `'Art. 108 EPÜ'` `'EU.EPÜ.108'`, etc. direct seed, no runtime regex). - Migration A4: backfill seed the ~30 concepts; UPDATE `deadline_rules` SET `concept_id = …` per row; backfill `legal_source` from existing rule_code mapping (algorithm: `'RoP 23'` `'UPC.RoP.23'`, `'§ 276 ZPO'` `'DE.ZPO.276'`, `'Art. 108 EPÜ'` `'EU.EPÜ.108'`, etc. direct seed, no runtime regex).
- Code A5: extend `CalcOptions.Flags` from `[]string` to `map[string]int` (or `[]struct{Code string; Param int}`) the `flag_param` extension needed for Q4 / PatG §82's customizable extension. New columns on `deadline_rules`: `flag_param_code text` (the flag this rule binds its param to, e.g. `'with_extension'`) + `flag_param_unit text` (`'months'` etc.). When the rule has `flag_param_code` set and the caller passes a param value for that flag, effective duration = `duration_value + caller_param`. Default (no param passed) = `duration_value` alone. UI surfaces a number input next to the checkbox when the flag has a `flag_param_unit`. - Code A5: extend `CalcOptions` with `AnchorOverrides map[string]string` (rule_code YYYY-MM-DD). The tree-walk in `Calculate` checks `AnchorOverrides[parent.code]` before reading the `computed[parent.code]` map; if present, the override anchors the child. No DB schema change purely calculator-side. Enables the user-set-custom-date capability per Q4 (m's 23:36 simplification) and reuses for any case where the user knows a real date better than the calculator's projection (court extensions, court-set decisions, post-hoc corrections).
- No user-visible behaviour change in A1A5 *for existing rules* (none use `flag_param_code` until Phase B3 hooks PatG §82). - Code A6: per-row editable date affordance on the result UI. Each rule row's date display becomes click-to-edit (or has a small icon); editing fires a re-fetch with the override added to the request. Court-set placeholder rows (`IsCourtSet=true`) get the same treatment user enters the actual decision date once known, downstream reflows.
- No user-visible behaviour change in A1A5 *for existing rules* A6's editable affordance is the only UI delta. Existing rules without overrides compute identically.
### Phase B — Coverage migrations (the bulk of new content) ### Phase B — Coverage migrations (the bulk of new content)
@@ -858,15 +859,17 @@ All v2 open questions resolved by m on 2026-05-04 23:29. Recorded here as the bi
3. **DE_NULL Berufungsbegründung 1 → 3 months — confirmed.** Ship the fix as part of Phase B3. Test pin: `de_null.beruf_begr` 1mo 3mo, `legal_source = 'DE.PatG.111.1'`. 3. **DE_NULL Berufungsbegründung 1 → 3 months — confirmed.** Ship the fix as part of Phase B3. Test pin: `de_null.beruf_begr` 1mo 3mo, `legal_source = 'DE.PatG.111.1'`.
4. **PatG §82(1) — model as base + extension flag, with customizable extension duration.** Confirmed shape (b) plus a new wrinkle: the extension duration is **user-customizable** at calc-time, not a hard-coded +1 month. UX: 4. **PatG §82(1) — keep simple seed; user overrides the date inline.** m's revised direction (23:36): drop the customizable-extension flag mechanism *"sounds complicated, I just want to be able to set a custom date and following deadlines calculate from there."*
``` Generalised capability instead: **any computed deadline date in the result is user-overridable**, and downstream rules that chain off it re-compute from the override. So PatG §82's "1 month + court extension to 5 weeks" case is handled by the user typing the actual extended date into the result row, and Replik / Duplik re-flow off it.
Klageerwiderung Nichtigkeit:
☑ Mit richterlicher Fristverlängerung
Verlängerung um: [ 1 ] Monat(e) ← editable, defaults to 1
```
Calculator extension required: a new `flag_param` mechanism on flag-conditioned rules. When `with_extension` is on, the rule's effective duration becomes `duration_value + flag_param.with_extension.value` in the same unit as duration_unit, OR a separate `extension_duration` slot that adds independently. Cleanest shape: extend `CalcOptions.Flags` from `[]string` to `[]struct{Code string; Param int}` (or `map[string]int`); rules with a non-nil "extension target" expression `(duration + flags["with_extension"]) months`. Phase A4 must include this calculator extension; Phase B3 hooks PatG §82 to it. **Generalises:** other rules with court-extendable deadlines (DPMA Beschwerdebegründung §75 "1mo, oft +1") can reuse the same flag_param shape later. PatG §82(1) seed stays at 2 months (the practical typical) with `deadline_notes` "1 Monat Grundfrist + bis +1 Monat richterliche Verlängerung typisch". No `with_extension` flag, no `flag_param` mechanism. Cleaner for everyone.
**Calculator change for the override capability:** `CalcOptions` gains an `AnchorOverrides map[string]string` field (rule_code YYYY-MM-DD). The tree-walk loop in `Calculate` checks `AnchorOverrides[parent.code]` before reading `computed[parent.code]` if present, that override anchors the child rule.
**UI change for the override capability:** each result row's date display becomes click-to-edit (or has a small icon). Editing fires a re-fetch with the override added to the request. The court-set placeholder rows (existing `IsCourtSet=true` rendering) get the same treatment the user can type the actual decision date once it's known, and downstream deadlines reflow.
Implementation cost: small. CalcOptions field + 5-line lookup in the tree walk + per-row edit affordance in the timeline / columns view.
5. **Full Appeal Chain — multiple date inputs, decisions need a date.** Confirmed shape (b). When toggle is on, the UI renders **one date input per stage** plus required date inputs for each terminal decision in the chain (LG Urteil, OLG Urteil, BGH Urteil). The intra-stage deadlines compute off the relevant stage anchor; inter-stage handoff is user-entered, never guessed. If the user hasn't yet got a stage's terminal decision date, that stage's downstream deadlines render as IsCourtSet placeholders same semantic as the existing isCourtDeterminedRule path. Worth noting: this means the Full Appeal Chain isn't a single "calculate-from-one-date" tool; it's a multi-stage timeline view that the user fills in as the case progresses. 5. **Full Appeal Chain — multiple date inputs, decisions need a date.** Confirmed shape (b). When toggle is on, the UI renders **one date input per stage** plus required date inputs for each terminal decision in the chain (LG Urteil, OLG Urteil, BGH Urteil). The intra-stage deadlines compute off the relevant stage anchor; inter-stage handoff is user-entered, never guessed. If the user hasn't yet got a stage's terminal decision date, that stage's downstream deadlines render as IsCourtSet placeholders same semantic as the existing isCourtDeterminedRule path. Worth noting: this means the Full Appeal Chain isn't a single "calculate-from-one-date" tool; it's a multi-stage timeline view that the user fills in as the case progresses.