Merge: t-paliad-348 — port engine semantics to TS calc + manuscript regen (m/paliad#153)
Some checks failed
Paliad CI gate / build (push) Has been cancelled
Paliad CI gate / test-go (push) Has been cancelled
Paliad CI gate / deploy (push) Has been cancelled

This commit is contained in:
mAi
2026-05-28 11:03:58 +02:00
9 changed files with 843 additions and 12 deletions

280
exports/gen-deadline-list.py Executable file
View File

@@ -0,0 +1,280 @@
#!/usr/bin/env python3
"""Generate a markdown deadline-list export for UPC PA training (work/head delegation #2572).
Sorts by proceeding-type display_order then sequence_order. Sections by proceeding.
t-paliad-348 / yoUPC#178 update: matches the engine's `IncludeOptional=false`
default (`pkg/litigationplanner/engine.go`). Optional rules (priority='optional')
are SUPPRESSED by default so the manuscript shows the same "naked proceeding
backbone" the UI now renders. Pass `--include-optional` to opt back in for an
exhaustive catalog dump.
Usage:
uv run exports/gen-deadline-list.py [--include-optional] [-o OUT]
"""
# /// script
# requires-python = ">=3.10"
# dependencies = ["psycopg2-binary"]
# ///
import argparse
import os
import sys
from datetime import date
from pathlib import Path
import psycopg2
import psycopg2.extras
DSN = os.environ.get(
"PALIAD_DEADLINE_EXPORT_DSN",
"postgres://postgres:rpsak3yf4lu1izgefx9p9xweg3qroojw@100.99.98.201:11833/postgres?sslmode=disable",
)
# `priority` filter is wired at the SQL level (not post-filter in Python) so
# the row counter in the markdown header reflects what's actually in the
# manuscript — matching what the lawyer sees on /tools/procedures.
SQL_TEMPLATE = """
SELECT
pt.code AS pt_code,
pt.display_order,
COALESCE(pt.name_en, pt.name) AS pt_label_en,
pt.name AS pt_label_de,
COALESCE(pe.name_en, pe.name) AS event_en,
pe.name AS event_de,
sr.duration_value,
sr.duration_unit,
sr.timing,
sr.alt_duration_value,
sr.alt_duration_unit,
sr.combine_op,
sr.rule_code,
COALESCE(te.name, te.name_de) AS trigger_label,
te.code AS trigger_code,
sr.primary_party,
sr.is_court_set,
sr.is_spawn,
sr.priority,
sr.deadline_notes_en,
sr.deadline_notes,
sr.condition_expr,
sr.sequence_order
FROM paliad.sequencing_rules sr
JOIN paliad.procedural_events pe ON pe.id = sr.procedural_event_id
LEFT JOIN paliad.proceeding_types pt ON pt.id = sr.proceeding_type_id
LEFT JOIN paliad.trigger_events te ON te.id = sr.trigger_event_id
WHERE sr.lifecycle_state = 'published'
AND sr.is_active = true
AND pt.id IS NOT NULL
{priority_filter}
ORDER BY pt.display_order NULLS LAST, pt.code, sr.sequence_order NULLS LAST, sr.rule_code, pe.name;
"""
def format_frist(duration_value, duration_unit, timing, alt_value, alt_unit, combine_op):
"""Format the deadline duration cleanly."""
if duration_value is None or duration_unit is None:
return ""
unit_map = {
"days": "d",
"weeks": "w",
"months": "M",
"years": "y",
"calendar_days": "CD",
"working_days": "WD",
}
unit = unit_map.get(duration_unit, duration_unit)
main = f"{duration_value} {unit}"
if alt_value is not None and alt_unit is not None:
alt_unit_short = unit_map.get(alt_unit, alt_unit)
op = combine_op or "or"
main = f"{main} {op} {alt_value} {alt_unit_short}"
if timing == "before":
main = f"{main} before"
elif timing == "after":
main = f"{main} after"
return main
def format_party(primary_party, is_court_set):
if is_court_set:
return "court-set"
if primary_party == "claimant":
return "claimant"
if primary_party == "defendant":
return "defendant"
if primary_party == "both":
return "either"
if primary_party == "court":
return "court"
return primary_party or ""
def detect_r94(notes_en, notes_de):
"""Flag R.9.4 non-extendable from notes text (heuristic — no DB field)."""
blobs = " ".join(filter(None, [notes_en or "", notes_de or ""])).lower()
if "r.9.4" in blobs or "r 9.4" in blobs or "r9.4" in blobs:
return ""
if "non-extendable" in blobs or "nicht verlängerbar" in blobs or "nicht verlaengerbar" in blobs:
return ""
return ""
def conditional_marker(condition_expr):
if condition_expr in (None, "", {}):
return ""
# condition_expr is JSONB → returns dict
if isinstance(condition_expr, dict):
if "flag" in condition_expr:
return f"if `{condition_expr['flag']}`"
if condition_expr.get("op") == "and" and "args" in condition_expr:
flags = [a.get("flag", "?") for a in condition_expr["args"]]
return "if " + " & ".join(f"`{f}`" for f in flags)
if condition_expr.get("op") == "or" and "args" in condition_expr:
flags = [a.get("flag", "?") for a in condition_expr["args"]]
return "if " + " | ".join(f"`{f}`" for f in flags)
return "cond"
def md_escape(s):
if s is None:
return ""
return str(s).replace("|", "\\|").replace("\n", " ")
def render(rows, *, include_optional: bool, generated_for: str) -> str:
by_pt = {}
for r in rows:
key = (r["display_order"] or 9999, r["pt_code"], r["pt_label_de"], r["pt_label_en"])
by_pt.setdefault(key, []).append(r)
out = []
today = date.today().isoformat()
out.append(f"# UPC + DE/EP Deadline Catalog — Stand {today}")
out.append("")
out.append(f"Source: `paliad.sequencing_rules` (lifecycle_state=published, is_active=true).")
out.append(f"Generated for {generated_for}. {len(rows)} rules across {len(by_pt)} proceedings.")
if include_optional:
out.append("")
out.append(
"**Mode:** `--include-optional` — every published rule, including "
"`priority='optional'` rules suppressed by the engine's default "
"(`IncludeOptional=false`). This is the exhaustive catalog dump."
)
else:
out.append("")
out.append(
"**Mode:** default — matches the engine's `IncludeOptional=false` "
"behaviour (pkg/litigationplanner/engine.go). `priority='optional'` "
"rules are suppressed; the manuscript shows only the mandatory "
"backbone the lawyer sees by default on /tools/procedures. "
"Re-run with `--include-optional` for the full catalog. "
"(t-paliad-348 / yoUPC#178)"
)
out.append("")
out.append("**Spalten:**")
out.append("- **Phase/Event** = procedural event (German primary)")
out.append("- **Frist** = duration + timing (`d` days, `w` weeks, `M` months, `CD` calendar days, `WD` working days; `before` = relative to anchor)")
out.append("- **Rule** = legal source (RoP / § ZPO / § PatG / Art. EPÜ)")
out.append("- **Anchor** = trigger event the deadline runs from")
out.append("- **Seite** = filing party (claimant / defendant / either / court-set)")
out.append("- **Priorität** = mandatory / recommended / optional / informational (only when `--include-optional`)")
out.append("- **R.9.4** = ✗ marked non-extendable in notes (heuristic — confirm against rule text)")
out.append("- **Bedingung** = scenario flag(s) that must be set for the rule to fire (blank = always)")
out.append("")
out.append("---")
out.append("")
for (order, pt_code, pt_de, pt_en) in sorted(by_pt.keys()):
prules = by_pt[(order, pt_code, pt_de, pt_en)]
out.append(f"## {pt_de} · `{pt_code}`")
out.append("")
if pt_en and pt_en != pt_de:
out.append(f"*{pt_en}*")
out.append("")
if include_optional:
out.append("| # | Phase / Event | Frist | Rule | Anchor | Seite | Priorität | R.9.4 | Bedingung |")
out.append("|---:|---|---|---|---|---|---|:---:|---|")
else:
out.append("| # | Phase / Event | Frist | Rule | Anchor | Seite | R.9.4 | Bedingung |")
out.append("|---:|---|---|---|---|---|:---:|---|")
for i, r in enumerate(prules, 1):
event = md_escape(r["event_de"] or r["event_en"] or "")
frist = md_escape(
format_frist(
r["duration_value"], r["duration_unit"], r["timing"],
r["alt_duration_value"], r["alt_duration_unit"], r["combine_op"],
)
)
rule = md_escape(r["rule_code"] or "")
anchor = md_escape(r["trigger_label"] or "")
party = format_party(r["primary_party"], r["is_court_set"])
r94 = detect_r94(r["deadline_notes_en"], r["deadline_notes"])
cond = md_escape(conditional_marker(r["condition_expr"]))
spawn_marker = "" if r["is_spawn"] else ""
if include_optional:
priority = md_escape(r["priority"] or "")
out.append(
f"| {i} | {event}{spawn_marker} | {frist} | {rule} | {anchor} | {party} | {priority} | {r94} | {cond} |"
)
else:
out.append(
f"| {i} | {event}{spawn_marker} | {frist} | {rule} | {anchor} | {party} | {r94} | {cond} |"
)
out.append("")
out.append("---")
out.append("")
out.append("**Lesehilfe:**")
out.append("- ⤴ Spawn-Marker: event opens a sub-proceeding (e.g. CCR forks revocation track)")
out.append("- `with_ccr` = Widerklage auf Nichtigkeit gefilt | `with_amend` = Patentänderungsantrag | `with_cci` = Widerklage auf Verletzung (in rev.cfi)")
out.append("- Catalog ist work-in-progress: 7 compound-name rules + Patentänderung-Duplikation noch in m's split-review backlog (m/paliad#149).")
return "\n".join(out)
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument(
"--include-optional",
action="store_true",
help="Include priority='optional' rules. Default false matches the engine's IncludeOptional=false default.",
)
parser.add_argument(
"-o",
"--out",
default="exports/upc-deadlines-2026-05-28.md",
help="Output path (relative to repo root).",
)
parser.add_argument(
"--generated-for",
default="PA-Schulung 2026-05-28",
help="Free-text label rendered in the markdown header.",
)
args = parser.parse_args()
priority_filter = "" if args.include_optional else "AND sr.priority != 'optional'"
sql = SQL_TEMPLATE.format(priority_filter=priority_filter)
conn = psycopg2.connect(DSN)
try:
cur = conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor)
cur.execute(sql)
rows = cur.fetchall()
finally:
conn.close()
md = render(rows, include_optional=args.include_optional, generated_for=args.generated_for)
# Resolve out path relative to the repo root (= the script's grandparent).
out_path = Path(args.out)
if not out_path.is_absolute():
out_path = Path(__file__).resolve().parent.parent / out_path
out_path.parent.mkdir(parents=True, exist_ok=True)
out_path.write_text(md)
n_pt = len({(r["display_order"] or 9999, r["pt_code"]) for r in rows})
print(
f"WROTE {out_path} ({len(rows)} rules, {n_pt} proceedings, "
f"include_optional={args.include_optional})"
)
if __name__ == "__main__":
sys.exit(main())

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 490 KiB

View File

@@ -0,0 +1,378 @@
# UPC + DE/EP Deadline Catalog — Stand 2026-05-28
Source: `paliad.sequencing_rules` (lifecycle_state=published, is_active=true).
Generated for PA-Schulung 2026-05-28. 178 rules across 25 proceedings.
**Mode:** default — matches the engine's `IncludeOptional=false` behaviour (pkg/litigationplanner/engine.go). `priority='optional'` rules are suppressed; the manuscript shows only the mandatory backbone the lawyer sees by default on /tools/procedures. Re-run with `--include-optional` for the full catalog. (t-paliad-348 / yoUPC#178)
**Spalten:**
- **Phase/Event** = procedural event (German primary)
- **Frist** = duration + timing (`d` days, `w` weeks, `M` months, `CD` calendar days, `WD` working days; `before` = relative to anchor)
- **Rule** = legal source (RoP / § ZPO / § PatG / Art. EPÜ)
- **Anchor** = trigger event the deadline runs from
- **Seite** = filing party (claimant / defendant / either / court-set)
- **Priorität** = mandatory / recommended / optional / informational (only when `--include-optional`)
- **R.9.4** = ✗ marked non-extendable in notes (heuristic — confirm against rule text)
- **Bedingung** = scenario flag(s) that must be set for the rule to fire (blank = always)
---
## Verletzungsverfahren · `upc.inf.cfi`
*Infringement Action*
| # | Phase / Event | Frist | Rule | Anchor | Seite | R.9.4 | Bedingung |
|---:|---|---|---|---|---|:---:|---|
| 1 | Klageerhebung | 0 M after | RoP.013.1 | | claimant | | |
| 2 | Klageerwiderung | 3 M after | RoP.023 | | defendant | | |
| 3 | Replik | 2 M or 2 M after | RoP.029.b | | claimant | | if `with_ccr` |
| 4 | Duplik | 1 M or 2 M after | RoP.029.c | | defendant | | if `with_ccr` |
| 5 | Erwiderung auf Nichtigkeitswiderklage | 2 M after | RoP.029.a | | claimant | | if `with_ccr` |
| 6 | Replik auf Erwiderung zur Nichtigkeitswiderklage | 2 M after | RoP.029.d | | defendant | | if `with_ccr` |
| 7 | Duplik auf Replik zur Erwiderung Nichtigkeitswiderklage | 1 M after | RoP.029.e | | claimant | | if `with_ccr` |
| 8 | Antrag auf Patentänderung | 2 M after | RoP.030.1 | | claimant | | if `with_ccr` & `with_amend` |
| 9 | Erwiderung auf Patentänderungsantrag | 2 M after | RoP.032.1 | | defendant | | if `with_ccr` & `with_amend` |
| 10 | Replik auf Erwiderung zum Patentänderungsantrag | 1 M after | RoP.032.3 | | claimant | | if `with_ccr` & `with_amend` |
| 11 | Duplik auf Replik zum Patentänderungsantrag | 1 M after | RoP.032.3 | | defendant | | if `with_ccr` & `with_amend` |
| 12 | Zwischenanhörung | 0 M after | RoP.105 | | court-set | | |
| 13 | Mitteilung Dolmetscherkosten | 2 w before | RoP.109.4 | Oral hearing | court | | |
| 14 | Übersetzungen einreichen | 2 w after | RoP.109.5 | | either | | |
| 15 | Mündliche Verhandlung | 0 M after | RoP.112 | | court-set | | |
| 16 | Entscheidung | 0 M after | RoP.118.1 | | court-set | | |
| 17 | Duplik zur Replik auf die Erwiderung zum Patentänderungsantrag | 1 M after | RoP.032.3 | Reply to the Defence to an Application to amend the patent | defendant | | if `with_ccr` & `with_amend` |
| 18 | Einreichung von Übersetzungen von Schriftstücken | 1 M after | RoP.007.4 | Order of the judge-rapporteur to lodge translations | either | | |
| 19 | Antrag auf Simultanübersetzung | 1 M before | RoP.109.5 | Oral hearing | either | | |
| 20 | Antrag auf Folgemaßnahmen aus einer rechtskräftigen Validitätsentscheidung | 2 M after | RoP.118.4 | Final decision of the central division, Court of Appeal or EPO on the validity of the patent | either | | if `with_ccr` |
| 21 | Antrag auf Überprüfung einer verfahrensleitenden Anordnung | 15 d after | RoP.333 | Case management order (Service) | either | | |
| 22 | Mängelbeseitigung / Einreichung schriftlicher Stellungnahme | 14 d after | RoP.019 | Preliminary Objection | either | | |
| 23 | Mängelbeseitigung / Zahlung | 14 d after | RoP.016 | Notification by the Registry to correct deficiencies | either | | |
| 24 | Antrag auf Verweisung an die Zentralkammer | 10 d after | RoP.323 | Information by the Court not to approve Application to use the patent's language as language of the proceedings | either | | |
| 25 | Mitteilung über Beauftragung eines Dolmetschers auf Kosten der Partei | 2 w before | RoP.109.5 | Oral hearing | either | | |
| 26 | Klärung von Übersetzungsfragen | 2 w after | RoP.109 | Summons to Oral Hearing | court | | |
| 27 | Antrag auf Vertraulichkeit gegenüber der Öffentlichkeit | 14 d after | RoP.262.2 | Opponent Submission | either | | |
| 28 | Wiedereinsetzungsantrag (UPC R.320) | 2 M after | RoP.320 | Removal of obstacle (UPC R.320) | either | | |
## Verletzungsverfahren (LG) · `de.inf.lg`
*Infringement (Regional Court)*
| # | Phase / Event | Frist | Rule | Anchor | Seite | R.9.4 | Bedingung |
|---:|---|---|---|---|---|:---:|---|
| 1 | Klageerhebung | 0 M after | § 253 ZPO | | claimant | | |
| 2 | Anzeige der Verteidigungsbereitschaft | 2 w after | § 276 ZPO | | defendant | | |
| 3 | Klageerwiderung | 6 w after | § 276 ZPO | | court-set | | |
| 4 | Replik | 4 w after | § 282 ZPO | | court-set | | |
| 5 | Duplik | 4 w after | § 282 ZPO | | court-set | | |
| 6 | Haupttermin | 0 M after | § 279 ZPO | | court-set | | |
| 7 | Urteil | 0 M after | § 300 ZPO | | court-set | | |
| 8 | Berufungsfrist | 1 M after | § 517 ZPO | | either | | |
| 9 | Berufungsbegründung | 2 M after | § 520 ZPO | | either | | |
| 10 | Wiedereinsetzungsantrag (§ 233 ZPO) | 2 w after | § 233 ZPO | Removal of obstacle (ZPO §233) | — | | |
| 11 | Einspruch gegen Versäumnisurteil (§ 339 ZPO) | 2 w after | § 339 ZPO | Service of default judgment | — | | |
| 12 | Schriftsatznachreichung (§ 296a ZPO) | 3 w after | § 296a ZPO | End of oral hearing | — | | |
## Nichtigkeitsverfahren · `upc.rev.cfi`
*Revocation Action*
| # | Phase / Event | Frist | Rule | Anchor | Seite | R.9.4 | Bedingung |
|---:|---|---|---|---|---|:---:|---|
| 1 | Nichtigkeitsklage | 0 M after | RoP.044 | | claimant | | |
| 2 | Klageerwiderung | 2 M after | RoP.049.1 | | defendant | | |
| 3 | Antrag auf Patentänderung | 0 M after | RoP.049.2.a | | defendant | | if `with_amend` |
| 4 | Verletzungswiderklage | 0 M after | RoP.049.2.b | | defendant | | if `with_cci` |
| 5 | Replik | 2 M after | RoP.051 | | claimant | | |
| 6 | Erwiderung auf Patentänderungsantrag | 2 M after | RoP.043.3 | | claimant | | if `with_amend` |
| 7 | Erwiderung auf Verletzungswiderklage | 2 M after | RoP.056.1 | | claimant | | if `with_cci` |
| 8 | Duplik | 1 M after | RoP.052 | | defendant | | |
| 9 | Replik auf Erwiderung zum Patentänderungsantrag | 1 M after | RoP.032.3 | | defendant | | if `with_amend` |
| 10 | Replik auf Erwiderung zur Verletzungswiderklage | 1 M after | RoP.056.3 | | defendant | | if `with_cci` |
| 11 | Duplik auf Replik zum Patentänderungsantrag | 1 M after | RoP.032.3 | | claimant | | if `with_amend` |
| 12 | Duplik auf Replik zur Erwiderung Verletzungswiderklage | 1 M after | RoP.056.4 | | claimant | | if `with_cci` |
| 13 | Zwischenanhörung | 0 M after | RoP.105 | | court-set | | |
| 14 | Mündliche Verhandlung | 0 M after | RoP.112 | | court-set | | |
| 15 | Entscheidung | 0 M after | RoP.118.3 | | court-set | | |
| 16 | Duplik zur Replik auf die Erwiderung zur Nichtigkeitsklage | 1 M after | RoP.052 | Reply to the Defence to revocation | — | | |
| 17 | Verletzungswiderklage | 2 M after | RoP.053 | Statement for Revocation | — | | |
| 18 | Antrag auf Patentänderung | 2 M after | RoP.050 | Statement for Revocation | — | | |
## Nichtigkeitsverfahren (BPatG) · `de.null.bpatg`
*Nullity (Federal Patent Court)*
| # | Phase / Event | Frist | Rule | Anchor | Seite | R.9.4 | Bedingung |
|---:|---|---|---|---|---|:---:|---|
| 1 | Nichtigkeitsklage | 0 M after | § 81 PatG | | claimant | | |
| 2 | Klageerwiderung | 2 M after | § 82 Abs. 3 PatG | | defendant | | |
| 3 | Replik | 2 M after | § 83 PatG | | claimant | | |
| 4 | Hinweisbeschluss | 0 M after | § 83 PatG | | court-set | | |
| 5 | Stellungnahme zum Hinweisbeschluss | 0 M after | § 83 PatG | | either | | |
| 6 | Duplik | 1 M after | § 83 PatG | | defendant | | |
| 7 | Mündliche Verhandlung | 0 M after | § 80 PatG | | court-set | | |
| 8 | Urteil | 0 M after | § 84 PatG | | court-set | | |
| 9 | Berufungsfrist | 1 M after | § 110 PatG | | either | | |
| 10 | Berufungsbegründung | 3 M after | § 111 PatG | | either | | |
| 11 | Wiedereinsetzungsantrag (§ 123 PatG) | 2 M after | § 123 PatG | Removal of obstacle (PatG §123) | — | | |
## Einspruchsverfahren · `epa.opp.opd`
*Opposition Proceedings*
| # | Phase / Event | Frist | Rule | Anchor | Seite | R.9.4 | Bedingung |
|---:|---|---|---|---|---|:---:|---|
| 1 | Veröffentlichung der Erteilung | 0 M after | Art. 97 EPÜ | | either | | |
| 2 | Einspruchsfrist | 9 M after | Art. 99 EPÜ | | either | | |
| 3 | Erwiderung des Patentinhabers | 4 M after | R. 79(1) EPÜ | | court-set | | |
| 4 | Entscheidung | 0 M after | Art. 102 EPÜ | | court-set | | |
| 5 | Beschwerdefrist | 2 M after | Art. 108 EPÜ | | either | | |
| 6 | Beschwerdebegründung | 4 M after | Art. 108 EPÜ | | either | | |
| 7 | Stellungnahme weiterer Beteiligter | 0 M after | R. 79 EPÜ | | either | | |
| 8 | Eingaben vor mündl. Verhandlung | 0 M after | R. 116 EPÜ | | either | | |
| 9 | Wiedereinsetzungsantrag (Art. 122 EPÜ) | 2 M after | Art. 122 EPÜ | Removal of obstacle (EPC Art.122) | — | | |
## Beschwerdeverfahren · `epa.opp.boa`
*Appeal Proceedings*
| # | Phase / Event | Frist | Rule | Anchor | Seite | R.9.4 | Bedingung |
|---:|---|---|---|---|---|:---:|---|
| 1 | Zustellung der Beschwerdeentscheidung | 0 M after | R. 124 EPÜ | | either | | |
| 2 | Beschwerdeeinlegung | 2 M after | Art. 108 EPÜ | | either | | |
| 3 | Beschwerdebegründung | 4 M after | Art. 108 EPÜ | | either | | |
| 4 | Beschwerdeerwiderung | 4 M after | RPBA Art. 12 | | either | | |
| 5 | Mündliche Verhandlung | 0 M after | Art. 116 EPÜ | | court-set | | |
| 6 | Entscheidung | 0 M after | Art. 111 EPÜ | | court-set | | |
| 7 | Eingaben vor mündl. Verhandlung | 0 M after | R. 116 EPÜ | | either | | |
| 8 | Antrag auf Überprüfung | 2 M after | Art. 112a EPÜ | | either | | |
## Einspruchsverfahren DPMA · `dpma.opp.dpma`
*Opposition DPMA*
| # | Phase / Event | Frist | Rule | Anchor | Seite | R.9.4 | Bedingung |
|---:|---|---|---|---|---|:---:|---|
| 1 | Veröffentlichung der Erteilung | 0 M after | § 58 PatG | | either | | |
| 2 | Einspruchsfrist | 9 M after | § 59 PatG | | either | | |
| 3 | Erwiderung des Patentinhabers | 4 M after | § 59(2) PatG | | court-set | | |
| 4 | DPMA-Entscheidung | 0 M after | § 61 PatG | | court-set | | |
| 5 | Wiedereinsetzungsantrag (DPMA) | 2 M after | § 123 PatG | Removal of obstacle (DPMA, PatG §123) | — | | |
## Berufungsverfahren · `upc.apl.merits`
*Appeal*
| # | Phase / Event | Frist | Rule | Anchor | Seite | R.9.4 | Bedingung |
|---:|---|---|---|---|---|:---:|---|
| 1 | Berufungseinlegung | 2 M after | RoP.224.1.a | | either | | |
| 2 | Berufungsbegründung | 4 M after | RoP.224.2.a | | either | | |
| 3 | Berufungserwiderung | 3 M after | RoP.235.1 | | either | | |
| 4 | Mündliche Verhandlung | 0 M after | RoP.240 | | court-set | | |
| 5 | Entscheidung | 0 M after | RoP.235.4 | | court-set | | |
| 6 | Anschlussberufung | 3 M after | RoP.237 | | either | | |
| 7 | Erwiderung Anschlussberufung | 2 M after | RoP.238.1 | | either | | |
| 8 | Berufungsschrift gegen eine in Regel 220.1(a) und (b) genannte Entscheidung | 2 M after | RoP.224.1(a) | Decision referred to in Rule 220.1(a) and (b) | — | | |
| 9 | Berufungsbegründung gegen eine in Regel 220.1(a) und (b) genannte Entscheidung | 4 M after | RoP.224.1(a) | Decision referred to in Rule 220.1(a) and (b) | — | | |
| 10 | Anfechtung einer Entscheidung über die Verwerfung der Berufung als unzulässig | 1 M after | RoP.245 | Decision to reject an appeal as inadmissible | — | | |
| 11 | Antrag auf Wiederaufnahme (schwerwiegender Verfahrensmangel) | 2 M after | RoP.247.2 | Final decision (Service) / Discovery of the fundamental defect (whichever is later) | — | | |
| 12 | Antrag auf Wiederaufnahme (Straftat) | 2 M after | RoP.247.1 | Final decision (Service) / Court decision on criminal offence (whichever is later) | — | | |
## Berufungsverfahren OLG (Verletzung) · `de.inf.olg`
*Appeal OLG (Infringement)*
| # | Phase / Event | Frist | Rule | Anchor | Seite | R.9.4 | Bedingung |
|---:|---|---|---|---|---|:---:|---|
| 1 | Zustellung LG-Urteil | 0 M after | § 540 ZPO | | either | | |
| 2 | Berufungsschrift | 1 M after | § 517 ZPO | | either | | |
| 3 | Berufungsbegründung | 2 M after | § 520 ZPO | | either | | |
| 4 | Berufungserwiderung | 1 M after | § 521 ZPO | | either | | |
| 5 | Anschlussberufung | 0 M after | § 524 ZPO | | either | | |
| 6 | Mündliche Verhandlung | 0 M after | § 540 ZPO | | court-set | | |
| 7 | OLG-Urteil | 0 M after | § 540 ZPO | | court-set | | |
## Revisions-/NZB-Verfahren BGH (Verletzung) · `de.inf.bgh`
*Revision / Non-admission Appeal BGH (Infringement)*
| # | Phase / Event | Frist | Rule | Anchor | Seite | R.9.4 | Bedingung |
|---:|---|---|---|---|---|:---:|---|
| 1 | Zustellung OLG-Urteil | 0 M after | § 555 ZPO | | either | | |
| 2 | Nichtzulassungsbeschwerde | 1 M after | § 544 ZPO | | either | | |
| 3 | Nichtzulassungsbeschwerde-Begründung | 2 M after | § 544 ZPO | | either | | |
| 4 | Revisionsfrist | 1 M after | § 548 ZPO | | either | | |
| 5 | Revisionsbegründung | 2 M after | § 551 ZPO | | either | | |
| 6 | Revisionserwiderung | 1 M after | § 554 ZPO | | either | | |
| 7 | Mündliche Verhandlung BGH | 0 M after | § 555 ZPO | | court-set | | |
| 8 | BGH-Urteil | 0 M after | § 555 ZPO | | court-set | | |
## Berufungsverfahren BGH (Nichtigkeit) · `de.null.bgh`
*Appeal BGH (Nullity)*
| # | Phase / Event | Frist | Rule | Anchor | Seite | R.9.4 | Bedingung |
|---:|---|---|---|---|---|:---:|---|
| 1 | Zustellung BPatG-Urteil | 0 M after | § 110 PatG | | either | | |
| 2 | Berufungsschrift | 1 M after | § 110 PatG | | either | | |
| 3 | Berufungsbegründung | 3 M after | § 520 Abs. 2 ZPO i.V.m. § 117 PatG | | either | | |
| 4 | Berufungserwiderung | 2 M after | § 521 Abs. 2 ZPO i.V.m. § 117 PatG | | court-set | | |
| 5 | Mündliche Verhandlung BGH | 0 M after | § 121 PatG | | court-set | | |
| 6 | BGH-Urteil | 0 M after | § 122 PatG | | court-set | | |
## EP-Erteilungsverfahren · `epa.grant.exa`
*EP Grant Procedure*
| # | Phase / Event | Frist | Rule | Anchor | Seite | R.9.4 | Bedingung |
|---:|---|---|---|---|---|:---:|---|
| 1 | Anmeldung | 0 M after | Art. 75 EPÜ | | claimant | | |
| 2 | Recherchenbericht | 6 M after | Art. 92 EPÜ | | court-set | | |
| 3 | Veröffentlichung (A1) | 18 M after | Art. 93 EPÜ | | court-set | | |
| 4 | Prüfungsantrag | 6 M after | R. 70(1) EPÜ | | claimant | | |
| 5 | Mitteilung nach R. 71(3) | 0 M after | R. 71(3) EPÜ | | court-set | | |
| 6 | Zustimmung + Übersetzung | 4 M after | R. 71(3) EPÜ | | claimant | | |
| 7 | Erteilung (B1) | 0 M after | Art. 97 EPÜ | | court-set | | |
## Beschwerdeverfahren BPatG (DPMA) · `dpma.appeal.bpatg`
*Appeal BPatG (against DPMA Decision)*
| # | Phase / Event | Frist | Rule | Anchor | Seite | R.9.4 | Bedingung |
|---:|---|---|---|---|---|:---:|---|
| 1 | Zustellung DPMA-Entscheidung | 0 M after | § 65 PatG | | either | | |
| 2 | Beschwerde | 1 M after | § 73 PatG | | either | | |
| 3 | Beschwerdebegründung | 1 M after | § 75 PatG | | court-set | | |
| 4 | Mündliche Verhandlung BPatG | 0 M after | § 78 PatG | | court-set | | |
| 5 | BPatG-Entscheidung | 0 M after | § 78 PatG | | court-set | | |
## Rechtsbeschwerdeverfahren BGH · `dpma.appeal.bgh`
*Legal Appeal BGH*
| # | Phase / Event | Frist | Rule | Anchor | Seite | R.9.4 | Bedingung |
|---:|---|---|---|---|---|:---:|---|
| 1 | Zustellung BPatG-Entscheidung | 0 M after | § 100 PatG | | either | | |
| 2 | Rechtsbeschwerde | 1 M after | § 100 PatG | | either | | |
| 3 | Rechtsbeschwerdebegründung | 1 M after | § 102 PatG | | either | | |
| 4 | BGH-Entscheidung | 0 M after | § 100 PatG | | court-set | | |
## Berufungsverfahren Anordnungen · `upc.apl.order`
*Order Appeal (15-day track)*
| # | Phase / Event | Frist | Rule | Anchor | Seite | R.9.4 | Bedingung |
|---:|---|---|---|---|---|:---:|---|
| 1 | Anordnung / angegriffene Entscheidung | 0 M after | RoP.220 | | court-set | | |
| 2 | Berufung mit Zulassung | 15 d after | RoP.220.2 | | either | | |
| 3 | Antrag auf Ermessensüberprüfung | 15 d after | RoP.220.3 | | either | | |
| 4 | Berufungsbegründung (Orders Track) | 15 d after | RoP.224.2.b | | either | | |
| 5 | Anschlussberufung | 15 d after | RoP.237 | | either | | |
| 6 | Erwiderung Anschlussberufung | 15 d after | RoP.238.2 | | either | | |
| 7 | Berufungsschrift gegen eine in Regel 220.1(c) genannte Anordnung oder eine in Regel 220.2 oder 221.3 genannte Entscheidung | 15 d after | RoP.224.1(b) | Order referred to in Rule 220.1(c) or a decision referred to in Rule 220.2 or 221.3 | — | | |
| 8 | Berufungsbegründung gegen eine in Regel 220.1(c) genannte Anordnung oder eine in Regel 220.2 oder 221.3 genannte Entscheidung | 15 d after | RoP.224.1(b) | Order referred to in Rule 220.1(c) or a decision referred to in Rule 220.2 or 221.3 | — | | |
## Schadensbemessungsverfahren · `upc.dmgs.cfi`
*Damages Determination*
| # | Phase / Event | Frist | Rule | Anchor | Seite | R.9.4 | Bedingung |
|---:|---|---|---|---|---|:---:|---|
| 1 | Antrag auf Schadensbemessung | 0 M after | RoP.125 | | claimant | | |
| 2 | Klageerwiderung | 2 M after | RoP.137.2 | | defendant | | |
| 3 | Replik | 1 M after | RoP.139 | | claimant | | |
| 4 | Duplik | 1 M after | RoP.139 | | defendant | | |
## Bucheinsichtsverfahren · `upc.disc.cfi`
*Lay-open Books / Discovery*
| # | Phase / Event | Frist | Rule | Anchor | Seite | R.9.4 | Bedingung |
|---:|---|---|---|---|---|:---:|---|
| 1 | Antrag auf Bucheinsicht | 0 M after | RoP.190 | | claimant | | |
| 2 | Klageerwiderung | 2 M after | RoP.142.2 | | defendant | | |
| 3 | Replik | 14 d after | RoP.142.3 | | claimant | | |
| 4 | Duplik | 14 d after | RoP.142.3 | | defendant | | |
## Einstweilige Maßnahmen · `upc.pi.cfi`
*Provisional Measures*
| # | Phase / Event | Frist | Rule | Anchor | Seite | R.9.4 | Bedingung |
|---:|---|---|---|---|---|:---:|---|
| 1 | Antrag | 0 M after | RoP.206 | | claimant | | |
| 2 | Erwiderung | 0 M after | RoP.211.2 | | court-set | | |
| 3 | Mündliche Verhandlung | 0 M after | RoP.195 | | court-set | | |
| 4 | Mängelbeseitigung Antrag | 14 d after | RoP.207.6.a | | claimant | | |
| 5 | Beschluss | 0 M after | RoP.211 | | court-set | | |
| 6 | Klage in der Hauptsache erheben | 31 d max 20 WD after | RoP.213 | | claimant | | |
## Schutzschrift · `upc.pl.cfi`
*Protective Letter*
| # | Phase / Event | Frist | Rule | Anchor | Seite | R.9.4 | Bedingung |
|---:|---|---|---|---|---|:---:|---|
| 1 | Einreichung der Schutzschrift | 0 M after | RoP.207 | | defendant | | |
| 2 | Erneuerung der Schutzschrift | 6 M after | RoP.207.9 | Protective Letter | — | | |
## Berufungsverfahren Kosten · `upc.apl.cost`
*Cost-Decision Appeal*
| # | Phase / Event | Frist | Rule | Anchor | Seite | R.9.4 | Bedingung |
|---:|---|---|---|---|---|:---:|---|
| 1 | Kostenfestsetzungsbeschluss | 0 M after | RoP.221.4 | | court-set | | |
| 2 | Antrag auf Berufungszulassung | 15 d after | RoP.221.1 | | either | | |
| 3 | Antrag auf Berufungszulassung gegen Kostenentscheidungen | 15 d after | RoP.220.2 | Decision on fixation of costs (Rule 157) | — | | |
## Negative Feststellungsklage · `upc.dni.cfi`
*Declaration of Non-Infringement*
| # | Phase / Event | Frist | Rule | Anchor | Seite | R.9.4 | Bedingung |
|---:|---|---|---|---|---|:---:|---|
| 1 | Klage auf negative Feststellung der Nichtverletzung | 0 M after | RoP.063 | | claimant | | |
| 2 | Erwiderung auf die negative Feststellungsklage | 2 M after | RoP.066 | Statement for a declaration of non-infringement | — | | |
| 3 | Replik auf die Erwiderung zur negativen Feststellungsklage | 1 M after | RoP.067 | Defence to the Statement for a declaration of non-infringement | — | | |
| 4 | Duplik zur Replik auf die Erwiderung zur negativen Feststellungsklage | 1 M after | RoP.068 | Reply to the Defence to the Statement for a declaration of non-infringement | — | | |
## Überprüfung von EPA-Entscheidungen · `upc.epo.review`
*Review of EPO decisions*
| # | Phase / Event | Frist | Rule | Anchor | Seite | R.9.4 | Bedingung |
|---:|---|---|---|---|---|:---:|---|
| 1 | Antrag auf Überprüfung der EPA-Entscheidung | 0 M after | RoP.088 | | — | | |
| 2 | Antrag auf Aufhebung einer Entscheidung des EPA, mit der ein Antrag auf einheitliche Wirkung zurückgewiesen wurde | 3 w after | RoP.097 | Decision of the EPO not to grant unitary effect | — | | |
## Separate Kostenentscheidung · `upc.costs.cfi`
*Separate Cost Decision*
| # | Phase / Event | Frist | Rule | Anchor | Seite | R.9.4 | Bedingung |
|---:|---|---|---|---|---|:---:|---|
| 1 | Antrag auf Kostenfestsetzung | 1 M after | RoP.151 | | claimant | | |
## Beweissicherung / saisie · `upc.bsv.cfi`
*Evidence Preservation*
| # | Phase / Event | Frist | Rule | Anchor | Seite | R.9.4 | Bedingung |
|---:|---|---|---|---|---|:---:|---|
| 1 | Antrag auf Beweissicherung | 0 M after | RoP.192 | | court-set | | |
| 2 | Antrag auf Überprüfung der Beweissicherungsanordnung | 30 d after | RoP.197.3 | Execution of measures to preserve evidence | — | | |
| 3 | Beginn des Hauptsacheverfahrens | 31 d max 20 WD after | RoP.198 | Date specified in the Court's order to preserve evidence | — | | |
## Widerklage auf Nichtigkeit · `upc.ccr.cfi`
*Counterclaim for Revocation*
| # | Phase / Event | Frist | Rule | Anchor | Seite | R.9.4 | Bedingung |
|---:|---|---|---|---|---|:---:|---|
| 1 | Widerklage auf Nichtigkeit | 3 M after | RoP.025 | | defendant | | |
---
**Lesehilfe:**
- ⤴ Spawn-Marker: event opens a sub-proceeding (e.g. CCR forks revocation track)
- `with_ccr` = Widerklage auf Nichtigkeit gefilt | `with_amend` = Patentänderungsantrag | `with_cci` = Widerklage auf Verletzung (in rev.cfi)
- Catalog ist work-in-progress: 7 compound-name rules + Patentänderung-Duplikation noch in m's split-review backlog (m/paliad#149).

View File

@@ -24,6 +24,7 @@ import {
type DeadlineResponse, type DeadlineResponse,
type Side, type Side,
} from "./views/verfahrensablauf-core"; } from "./views/verfahrensablauf-core";
import { filterByDetailMode, type DetailMode } from "./verfahrensablauf-detail-mode";
import { mountAddProceedingPicker, type ProceedingTypeMeta } from "./builder-picker"; import { mountAddProceedingPicker, type ProceedingTypeMeta } from "./builder-picker";
import { overlayEventStates, renderTriplet, type ScenarioFlagCatalogEntry } from "./builder-triplet"; import { overlayEventStates, renderTriplet, type ScenarioFlagCatalogEntry } from "./builder-triplet";
import { import {
@@ -550,15 +551,32 @@ async function hydrateTriplet(
return; return;
} }
const stichtag = proc.stichtag || state.active?.stichtag || todayISO(); const stichtag = proc.stichtag || state.active?.stichtag || todayISO();
// t-paliad-348 — port engine semantics to the Builder triplet calc:
// - `includeOptional` follows the proceeding's detailgrad. The
// "selected" default suppresses optional rules server-side
// (engine drops them); "all_options" opts in so the dimmed
// optional cards can be rendered for the lawyer to opt into.
// - `filterByDetailMode` then runs client-side over what the
// engine emitted, dropping isConditional rows (rules whose
// `trigger_event_id` anchor wasn't supplied) when the lawyer
// is on "selected"/"mandatory_only" — those rules belong to the
// "naked proceeding with options but not always displayed"
// mental model and shouldn't pollute the backbone view.
const detailgrad: DetailMode = (proc.detailgrad as DetailMode) || "selected";
const data: DeadlineResponse | null = await calculateDeadlines({ const data: DeadlineResponse | null = await calculateDeadlines({
proceedingType: meta.code, proceedingType: meta.code,
triggerDate: stichtag, triggerDate: stichtag,
flags: scenarioFlagsToArray(proc.scenario_flags), flags: scenarioFlagsToArray(proc.scenario_flags),
includeOptional: detailgrad === "all_options",
}); });
const side: Side = (proc.primary_party as Side) || null; const side: Side = (proc.primary_party as Side) || null;
const eventsByRule = buildEventsByRule(proc.id); const eventsByRule = buildEventsByRule(proc.id);
const columnsHtml = data const scenarioFlagsBool = scenarioFlagsToBoolMap(proc.scenario_flags);
? renderColumnsBody(data, { editable: false, side, showDurations: false }) const filteredData: DeadlineResponse | null = data
? { ...data, deadlines: filterByDetailMode(data.deadlines, detailgrad, scenarioFlagsBool) }
: null;
const columnsHtml = filteredData
? renderColumnsBody(filteredData, { editable: false, side, showDurations: false })
: ""; : "";
host.innerHTML = renderTriplet({ host.innerHTML = renderTriplet({
proceeding: proc, proceeding: proc,
@@ -927,6 +945,19 @@ function scenarioFlagsToArray(flags: Record<string, unknown>): string[] {
return out; return out;
} }
// scenarioFlagsToBoolMap narrows the jsonb-shape scenario_flags blob
// (`{key: true|false|null|other}`) to the strict `Record<string, boolean>`
// shape filterByDetailMode consumes. The rule:<uuid>=true|false per-rule
// deviation keys flow through verbatim (their truthiness IS the override
// signal isRuleSelected reads).
function scenarioFlagsToBoolMap(flags: Record<string, unknown>): Record<string, boolean> {
const out: Record<string, boolean> = {};
for (const [k, v] of Object.entries(flags)) {
if (typeof v === "boolean") out[k] = v;
}
return out;
}
// ──────────────────────────────────────────────────────────────────────────── // ────────────────────────────────────────────────────────────────────────────
// Actions // Actions
// ──────────────────────────────────────────────────────────────────────────── // ────────────────────────────────────────────────────────────────────────────

View File

@@ -176,6 +176,20 @@ const APPEAL_TARGET_PROCEEDINGS = new Set([
"upc.apl.unified", "upc.apl.unified",
]); ]);
// hasOptionalOptIn — true when any `rule:<uuid>=true` override exists in
// the scenarioFlags map (the per-rule "Aufnehmen" deviation written by
// the detail-mode selection-chip in onRuleSelectionToggle). When set we
// flip the engine's IncludeOptional on so the chosen optional rules
// actually reach the response; the engine has no rule:<uuid> awareness
// of its own so without this layer the pick would silently no-op.
// (t-paliad-348)
function hasOptionalOptIn(flags: Record<string, boolean>): boolean {
for (const [k, v] of Object.entries(flags)) {
if (v === true && k.startsWith("rule:")) return true;
}
return false;
}
function hasAppealTarget(proceedingType: string): boolean { function hasAppealTarget(proceedingType: string): boolean {
return APPEAL_TARGET_PROCEEDINGS.has(proceedingType); return APPEAL_TARGET_PROCEEDINGS.has(proceedingType);
} }
@@ -408,6 +422,16 @@ async function doCalc() {
perCardChoices, perCardChoices,
includeHidden: showHidden, includeHidden: showHidden,
appealTarget, appealTarget,
// t-paliad-348 — match the page-level detail mode to the engine's
// optional-rule suppression. The engine drops optional rules by
// default (IncludeOptional=false); "all_options" mode needs them
// back in the response so filterByDetailMode can dim them. We
// also opt-in whenever any per-rule `rule:<uuid>=true` deviation
// is set on scenarioFlags so an "Aufnehmen"-ed optional in
// "selected" mode still surfaces — the engine doesn't read
// rule:<uuid> overrides, so without this the user's pick would
// silently no-op server-side.
includeOptional: detailMode === "all_options" || hasOptionalOptIn(scenarioFlags),
}); });
if (seq !== calcSeq) return; if (seq !== calcSeq) return;
if (!data) return; if (!data) return;

View File

@@ -1,8 +1,9 @@
import { describe, expect, test } from "bun:test"; import { afterEach, beforeEach, describe, expect, test } from "bun:test";
import { import {
type CalculatedDeadline, type CalculatedDeadline,
type DeadlineResponse, type DeadlineResponse,
bucketDeadlinesIntoColumns, bucketDeadlinesIntoColumns,
calculateDeadlines,
deadlineCardHtml, deadlineCardHtml,
formatDurationLabel, formatDurationLabel,
renderColumnsBody, renderColumnsBody,
@@ -773,3 +774,81 @@ describe("stripLeadingDurationFromNotes — render-side dedup (t-paliad-307)", (
.toBe("Time limit set by the court"); .toBe("Time limit set by the court");
}); });
}); });
// Pin the engine-options plumbing surface (t-paliad-348 / yoUPC#178).
// calculateDeadlines must forward `includeOptional` and
// `triggerEventAnchors` straight into the POST body so the Go handler
// (handleFristenrechnerAPI) can pass them into lp.CalcOptions. If a
// future refactor drops the fields, the Builder triplet silently
// reverts to "engine emits optional rules" and the unified
// /tools/procedures page loses its naked-proceeding default.
describe("calculateDeadlines — forwards engine options into request body", () => {
type CapturedRequest = { url: string; body: Record<string, unknown> };
let captured: CapturedRequest | null;
let originalFetch: typeof globalThis.fetch;
beforeEach(() => {
captured = null;
originalFetch = globalThis.fetch;
globalThis.fetch = (async (input: RequestInfo | URL, init?: RequestInit) => {
const body = typeof init?.body === "string" ? JSON.parse(init.body) : {};
captured = { url: String(input), body };
return new Response(JSON.stringify({
proceedingType: "x", proceedingName: "x", triggerDate: "2026-01-01", deadlines: [],
}), { status: 200, headers: { "Content-Type": "application/json" } });
}) as typeof globalThis.fetch;
});
afterEach(() => {
globalThis.fetch = originalFetch;
});
test("default call omits includeOptional and triggerEventAnchors", async () => {
await calculateDeadlines({ proceedingType: "upc.inf.cfi", triggerDate: "2026-05-26" });
expect(captured).not.toBeNull();
expect(captured!.body.includeOptional).toBeUndefined();
expect(captured!.body.triggerEventAnchors).toBeUndefined();
});
test("includeOptional=true sends includeOptional: true", async () => {
await calculateDeadlines({
proceedingType: "upc.inf.cfi",
triggerDate: "2026-05-26",
includeOptional: true,
});
expect(captured!.body.includeOptional).toBe(true);
});
test("includeOptional=false is omitted (matches engine default)", async () => {
await calculateDeadlines({
proceedingType: "upc.inf.cfi",
triggerDate: "2026-05-26",
includeOptional: false,
});
expect(captured!.body.includeOptional).toBeUndefined();
});
test("triggerEventAnchors forwarded as object", async () => {
await calculateDeadlines({
proceedingType: "upc.inf.cfi",
triggerDate: "2026-05-26",
triggerEventAnchors: {
"upc.inf.cfi.oral": "2026-09-01",
"upc.inf.cfi.decision": "2026-12-15",
},
});
expect(captured!.body.triggerEventAnchors).toEqual({
"upc.inf.cfi.oral": "2026-09-01",
"upc.inf.cfi.decision": "2026-12-15",
});
});
test("empty triggerEventAnchors is omitted", async () => {
await calculateDeadlines({
proceedingType: "upc.inf.cfi",
triggerDate: "2026-05-26",
triggerEventAnchors: {},
});
expect(captured!.body.triggerEventAnchors).toBeUndefined();
});
});

View File

@@ -271,6 +271,12 @@ export interface DeadlineResponse {
// when the toggle is OFF — so users know there's something to // when the toggle is OFF — so users know there's something to
// re-surface. // re-surface.
hiddenCount?: number; hiddenCount?: number;
// rulesAwaitingAnchor (t-paliad-348 / yoUPC#178): number of rules the
// engine suppressed because their `trigger_event_id` anchor wasn't
// supplied via CalcParams.triggerEventAnchors. Mirrors the Go
// Timeline.RulesAwaitingAnchor counter — a single integer surface for
// "N rules waiting on an anchor" UI affordances.
rulesAwaitingAnchor?: number;
} }
export interface CourtRow { export interface CourtRow {
@@ -311,6 +317,20 @@ export interface CalcParams {
// endentscheidung | kostenentscheidung | anordnung | // endentscheidung | kostenentscheidung | anordnung |
// schadensbemessung | bucheinsicht. // schadensbemessung | bucheinsicht.
appealTarget?: string; appealTarget?: string;
// t-paliad-348 / yoUPC#178 — surface the engine's two new CalcOptions
// axes to the HTTP boundary:
//
// includeOptional: when true, the engine returns priority='optional'
// rules in the timeline. Default false matches the engine default
// (mandatory backbone only). The /tools/procedures detailgrad
// toggle ("all_options" mode) drives this to true so the dimmed
// optional cards can be rendered for the lawyer to opt into.
// triggerEventAnchors: per-event-code anchor dates the engine
// consults for rules carrying trigger_event_id. Empty/omitted =
// no anchors → such rules render as IsConditional (the engine
// refuses to fabricate a date off the proceeding's trigger date).
includeOptional?: boolean;
triggerEventAnchors?: Record<string, string>;
} }
const PARTY_CLASS: Record<string, string> = { const PARTY_CLASS: Record<string, string> = {
@@ -1118,6 +1138,10 @@ export async function calculateDeadlines(params: CalcParams): Promise<DeadlineRe
: undefined, : undefined,
includeHidden: params.includeHidden ? true : undefined, includeHidden: params.includeHidden ? true : undefined,
appealTarget: params.appealTarget || undefined, appealTarget: params.appealTarget || undefined,
includeOptional: params.includeOptional ? true : undefined,
triggerEventAnchors: params.triggerEventAnchors && Object.keys(params.triggerEventAnchors).length > 0
? params.triggerEventAnchors
: undefined,
}), }),
}); });
if (!resp.ok) { if (!resp.ok) {

View File

@@ -91,6 +91,19 @@ func handleFristenrechnerAPI(w http.ResponseWriter, r *http.Request) {
// slugs are silently dropped (no filter) so a stale frontend // slugs are silently dropped (no filter) so a stale frontend
// chip doesn't 400 the request. // chip doesn't 400 the request.
AppealTarget string `json:"appealTarget,omitempty"` AppealTarget string `json:"appealTarget,omitempty"`
// t-paliad-348 / yoUPC#178 — surface the engine's two new
// CalcOptions axes to the HTTP boundary:
//
// IncludeOptional: when true, priority='optional' rules
// surface on the timeline. Default false matches the
// engine's default (mandatory backbone only).
// TriggerEventAnchors: per-event-code anchor dates the
// engine consults for rules carrying trigger_event_id.
// When a rule's anchor is absent the engine renders the
// rule as IsConditional rather than fabricating a date
// off the proceeding's trigger date.
IncludeOptional bool `json:"includeOptional,omitempty"`
TriggerEventAnchors map[string]string `json:"triggerEventAnchors,omitempty"`
} }
if err := json.NewDecoder(r.Body).Decode(&req); err != nil { if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "Ungültige Anfrage"}) writeJSON(w, http.StatusBadRequest, map[string]string{"error": "Ungültige Anfrage"})
@@ -130,15 +143,17 @@ func handleFristenrechnerAPI(w http.ResponseWriter, r *http.Request) {
} }
resp, err := dbSvc.fristenrechner.Calculate(r.Context(), req.ProceedingType, req.TriggerDate, services.CalcOptions{ resp, err := dbSvc.fristenrechner.Calculate(r.Context(), req.ProceedingType, req.TriggerDate, services.CalcOptions{
PriorityDateStr: req.PriorityDate, PriorityDateStr: req.PriorityDate,
Flags: req.Flags, Flags: req.Flags,
AnchorOverrides: req.AnchorOverrides, AnchorOverrides: req.AnchorOverrides,
CourtID: req.CourtID, CourtID: req.CourtID,
PerCardAppellant: addendum.PerCardAppellant, PerCardAppellant: addendum.PerCardAppellant,
SkipRules: addendum.SkipRules, SkipRules: addendum.SkipRules,
IncludeCCRFor: addendum.IncludeCCRFor, IncludeCCRFor: addendum.IncludeCCRFor,
IncludeHidden: req.IncludeHidden, IncludeHidden: req.IncludeHidden,
AppealTarget: req.AppealTarget, AppealTarget: req.AppealTarget,
IncludeOptional: req.IncludeOptional,
TriggerEventAnchors: req.TriggerEventAnchors,
}) })
if err != nil { if err != nil {
if errors.Is(err, services.ErrUnknownProceedingType) { if errors.Is(err, services.ErrUnknownProceedingType) {