feat: design document for Prozesskostenrechner + Fristenrechner

Comprehensive design for two interactive tools:
- Prozesskostenrechner: DE (LG/OLG/BGH/BPatG), UPC, and EPA cost estimates
- Fristenrechner: Patent deadline calculator with holiday adjustment

Covers UI layout, data models, API contracts, calculation logic,
fee tables (GKG/RVG/PatKostG/UPC/EPA), deadline rules for all
proceeding types, and phased implementation plan.

Key differentiator: EPA proceedings coverage (not in KanzlAI).
This commit is contained in:
m
2026-04-14 17:12:16 +02:00
parent 3a157594c2
commit 2919e1afc4

View File

@@ -0,0 +1,882 @@
# Design: Prozesskostenrechner + Fristenrechner
**Author:** cronus (inventor)
**Date:** 2026-04-14
**Task:** t-patholo-006
**Status:** Design complete — ready for implementation
---
## 1. Executive Summary
Two interactive calculator tools for patholo.de, tailored for HL patent lawyers:
1. **Prozesskostenrechner** — Estimates litigation costs across DE courts, UPC, and EPA proceedings
2. **Fristenrechner** — Calculates patent-related deadlines with holiday/weekend adjustment
Both tools follow patholo's existing architecture (SSR page shell via Bun/TSX, client-side JS for interactivity, Go API for calculation logic) and extend it with a new `/tools/` section.
**Key differentiator vs KanzlAI:** patholo adds EPA proceeding costs and deadlines — directly relevant for HL's patent prosecution practice, which KanzlAI doesn't cover.
---
## 2. Architecture
### Existing Pattern
patholo uses a three-layer architecture:
```
[Bun/TSX build] → static HTML pages (dist/*.html)
[Client JS] → bundled per page (dist/assets/*.js), POSTs to Go API
[Go backend] → serves pages + API endpoints, Supabase auth middleware
```
### How Calculators Fit
Each calculator gets:
- **One TSX page** (SSR shell with form structure and empty result containers)
- **One client JS bundle** (handles form interaction, calls API, renders results)
- **Go API endpoints** (pure calculation logic, returns JSON)
This matches the login page pattern (`login.tsx` + `client/login.ts` + `POST /api/login`).
### New Files
```
frontend/
src/
kostenrechner.tsx # SSR page shell
fristenrechner.tsx # SSR page shell
client/
kostenrechner.ts # Client-side form logic + API calls
fristenrechner.ts # Client-side form logic + API calls
internal/
handlers/
kostenrechner.go # API endpoint + page serving
fristenrechner.go # API endpoint + page serving
calc/
fees.go # GKG/RVG/PatKostG step-based fee calculation
fee_tables.go # Fee schedule data (brackets, factors, UPC/EPA fees)
deadlines.go # Deadline calculation + holiday adjustment
deadline_rules.go # Proceeding types + rule definitions
holidays.go # German federal holidays (Easter algorithm)
```
### Routes
```
GET /tools/kostenrechner → protected, serves dist/kostenrechner.html
POST /api/tools/kostenrechner → protected, JSON calculation
GET /tools/fristenrechner → protected, serves dist/fristenrechner.html
POST /api/tools/fristenrechner → protected, JSON calculation
```
### Build Changes
`frontend/build.ts` adds two new entrypoints and two new page renders:
```typescript
// New client bundles
entrypoints: [
"src/client/login.ts",
"src/client/kostenrechner.ts",
"src/client/fristenrechner.ts",
]
// New page renders
Bun.write(join(DIST, "kostenrechner.html"), renderKostenrechner());
Bun.write(join(DIST, "fristenrechner.html"), renderFristenrechner());
```
---
## 3. Prozesskostenrechner
### 3.1 Scope
Three proceeding families, covering what HL patent lawyers encounter daily:
| Family | Instances | Fee Basis |
|--------|-----------|-----------|
| **DE Verletzung** | LG → OLG → BGH (NZB) → BGH (Revision) | GKG + RVG |
| **DE Nichtigkeit** | BPatG → BGH (Nichtigkeitsberufung) | PatKostG/GKG + RVG |
| **UPC** | UPC 1. Instanz → UPC Berufung | Fixed + value-based |
| **EPA** | Einspruch → Beschwerde | Fixed fees |
### 3.2 UI Layout
Two-column layout on desktop (stacks on mobile):
```
┌─────────────────────────────────────────────────────────┐
│ Header: patholo + nav │
├─────────────────────────────────────────────────────────┤
│ Page Title: Prozesskostenrechner │
│ Subtitle: Patent Litigation Cost Calculator │
├──────────────────────────────┬──────────────────────────┤
│ LEFT PANEL (inputs) │ RIGHT PANEL (results) │
│ │ [sticky on scroll] │
│ ┌──────────────────────────┐ │ │
│ │ Streitwert │ │ ┌──────────────────────┐ │
│ │ [slider + input field] │ │ │ Gesamtkosten │ │
│ │ Presets: 500k 1M 5M 10M │ │ │ EUR XX.XXX,XX │ │
│ └──────────────────────────┘ │ │ (highlighted, large) │ │
│ │ └──────────────────────┘ │
│ ┌──────────────────────────┐ │ │
│ │ MwSt: [19% ▼] │ │ ┌──────────────────────┐ │
│ └──────────────────────────┘ │ │ Per-Instance Breakdown│ │
│ │ │ │ │
│ ── DE Verletzungsverfahren ──│ │ LG: EUR XX.XXX │ │
│ ☑ LG [details ▼] │ │ Gericht: EUR X.XXX │ │
│ ☑ OLG [details ▼] │ │ RA: EUR X.XXX │ │
│ ☐ BGH NZB │ │ PA: EUR X.XXX │ │
│ ☐ BGH Revision │ │ │ │
│ │ │ OLG: EUR XX.XXX │ │
│ ── DE Nichtigkeitsverfahren ─│ │ ... │ │
│ ☐ BPatG │ │ │ │
│ ☐ BGH Nichtigkeit │ │ UPC 1: EUR XX.XXX │ │
│ │ │ Fixed: EUR X.XXX │ │
│ ── UPC ──────────────────────│ │ Value: EUR X.XXX │ │
│ ☐ UPC 1. Instanz │ │ Recov.: EUR X.XXX │ │
│ ☐ UPC Berufung │ │ │ │
│ │ │ EPA: EUR X.XXX │ │
│ ── EPA ──────────────────────│ │ ... │ │
│ ☐ Einspruch │ └──────────────────────┘ │
│ ☐ Beschwerde │ │
│ │ [Drucken / Print] │
├──────────────────────────────┴──────────────────────────┤
│ Footer │
└─────────────────────────────────────────────────────────┘
```
**Instance Detail Expansion** (when user clicks ▼ on an enabled instance):
```
☑ LG (Verletzung 1. Instanz) [▼ expanded]
┌────────────────────────────────────────────┐
│ Gebührenordnung: [Aktuell (2025) ▼] │
│ Rechtsanwälte: [1 ▼] │
│ Patentanwälte: [1 ▼] │
│ Mandanten: [1 ▼] │
│ Mündl. Verhandlung: [☑ Ja] │
└────────────────────────────────────────────┘
```
### 3.3 Calculation Logic
#### 3.3.1 GKG/RVG Base Fee (Step-Based Accumulator)
The fee schedules use brackets with step sizes. Algorithm:
```go
func ComputeBaseFee(streitwert float64, isRVG bool, version string) float64 {
brackets := FeeSchedules[version]
remaining := streitwert
fee := 0.0
lowerBound := 0.0
for _, b := range brackets {
upperBound, stepSize, gkgInc, rvgInc := b[0], b[1], b[2], b[3]
increment := gkgInc
if isRVG { increment = rvgInc }
bracketSize := upperBound - lowerBound
if upperBound == math.Inf(1) { bracketSize = remaining }
portion := math.Min(remaining, bracketSize)
if portion <= 0 { break }
if lowerBound == 0 {
// First bracket: minimum = one increment
fee += increment
stepsAfterFirst := math.Max(0, math.Ceil((portion - stepSize) / stepSize))
fee += stepsAfterFirst * increment
} else {
steps := math.Ceil(portion / stepSize)
fee += steps * increment
}
remaining -= portion
lowerBound = upperBound
if remaining <= 0 { break }
}
return fee
}
```
#### 3.3.2 Court Fees
```
courtFee = courtFeeFactor × ComputeBaseFee(streitwert, false, version)
```
Factors per instance:
- LG: 3.0× GKG
- OLG: 4.0× GKG
- BGH NZB: 2.0× GKG
- BGH Revision: 5.0× GKG
- BPatG: 4.5× PatKostG (same brackets as GKG)
- BGH Nichtigkeit: 6.0× GKG
#### 3.3.3 Attorney Fees (per attorney)
```
baseFee = ComputeBaseFee(streitwert, true, version) // RVG base
vgFee = vgFactor × baseFee // Verfahrensgebühr
erhöhung = min((numClients - 1) × 0.3, 2.0) × baseFee // Erhöhungsgebühr
tgFee = oralHearing ? tgFactor × baseFee : 0 // Terminsgebühr
pauschale = 20.00 // Auslagenpauschale
nettoTotal = vgFee + erhöhung + tgFee + pauschale
mwst = nettoTotal × vatRate
bruttoTotal = nettoTotal + mwst
```
VG/TG factors per instance:
| Instance | RA VG | RA TG | PA VG | PA TG |
|----------|-------|-------|-------|-------|
| LG | 1.3 | 1.2 | 1.3 | 1.2 |
| OLG | 1.6 | 1.2 | 1.6 | 1.2 |
| BGH NZB | 2.3 | 1.2 | 1.6 | 1.2 |
| BGH Rev | 2.3 | 1.5 | 1.6 | 1.5 |
| BPatG | 1.3 | 1.2 | 1.3 | 1.2 |
| BGH Null | 1.6 | 1.5 | 1.6 | 1.5 |
#### 3.3.4 UPC Fees
```
fixedFee = UPCFixedFees[version][proceedingType]
valueBasedFee = lookupBracket(streitwert, UPCValueBrackets[version])
courtTotal = fixedFee + valueBasedFee
smeTotal = courtTotal × (1 - smeReduction)
recoverableCostsCeiling = lookupBracket(streitwert, UPCRecoverableTable[version])
```
Two fee versions:
- **Pre-2026**: infringement EUR 11,000, revocation EUR 20,000, SME reduction 40%
- **2026+**: infringement EUR 14,600, revocation EUR 26,500, SME reduction 50%
Value-based brackets (19 tiers from EUR 500k to EUR 50M+).
Recoverable costs ceiling (10 tiers from EUR 250k to EUR 50M+).
#### 3.3.5 EPA Fees (patholo-specific, not in KanzlAI)
EPA proceedings use fixed official fees — no Streitwert-based calculation:
| Proceeding | Official Fee | Notes |
|------------|-------------|-------|
| Einspruch (Opposition) | EUR 880 | Per opponent |
| Einspruchsbeschwerde (Appeal from Opposition) | EUR 2,255 | EUR 1,880 for SME |
| Beschwerde gegen Prüfungsentscheid | EUR 2,255 | Appeal against examination decision |
| Beschränkungsantrag (Limitation) | EUR 1,175 | — |
| Widerrufsantrag (Revocation by proprietor) | EUR — | Free |
Attorney costs for EPA proceedings are not RVG-based (EPA representatives are typically Patentanwälte billing hourly). We show only the official fees, with a note that attorney costs are typically agreed on a time-spent basis.
### 3.4 Fee Schedule Data (2025/Aktuell)
```
Brackets: [upperBound, stepSize, gkgIncrement, rvgIncrement]
[500, 300, 40, 51.5]
[2000, 500, 21, 41.5]
[10000, 1000, 22.5, 59.5]
[25000, 3000, 30.5, 55]
[50000, 5000, 40.5, 86]
[200000, 15000, 140, 99.5]
[500000, 30000, 210, 140]
[Infinity, 50000, 210, 175]
```
Additional versions (2005, 2013, 2021) available as dropdown — data from GKG Anlage 2 / RVG Anlage 2.
### 3.5 API Contract
**Request: `POST /api/tools/kostenrechner`**
```json
{
"streitwert": 1000000,
"vatRate": 0.19,
"instances": {
"LG": { "enabled": true, "feeVersion": "Aktuell", "numAttorneys": 1, "numPatentAttorneys": 1, "numClients": 1, "oralHearing": true },
"OLG": { "enabled": true, "feeVersion": "Aktuell", "numAttorneys": 1, "numPatentAttorneys": 1, "numClients": 1, "oralHearing": true },
"BGH_NZB": { "enabled": false },
"BGH_REV": { "enabled": false },
"BPatG": { "enabled": false },
"BGH_NULLITY": { "enabled": false },
"UPC_FIRST": { "enabled": true, "feeVersion": "2026", "isSME": false, "includeRevocation": false },
"UPC_APPEAL": { "enabled": false },
"EPA_OPPOSITION": { "enabled": false },
"EPA_APPEAL": { "enabled": false }
}
}
```
**Response:**
```json
{
"results": [
{
"instance": "LG",
"label": "LG (Verletzung 1. Instanz)",
"courtFee": 2838.00,
"courtFeeBase": 946.00,
"courtFeeFactor": 3.0,
"courtFeeBasis": "GKG",
"attorney": {
"baseFee": 1663.50,
"verfahrensgebuehr": 2162.55,
"erhoehungsgebuehr": 0,
"terminsgebuehr": 1996.20,
"pauschale": 20.00,
"nettoTotal": 4178.75,
"mwst": 793.96,
"bruttoPerAttorney": 4972.71,
"count": 1,
"bruttoTotal": 4972.71
},
"patentAttorney": {
"baseFee": 1663.50,
"verfahrensgebuehr": 2162.55,
"erhoehungsgebuehr": 0,
"terminsgebuehr": 1996.20,
"pauschale": 20.00,
"nettoTotal": 4178.75,
"mwst": 793.96,
"bruttoPerAttorney": 4972.71,
"count": 1,
"bruttoTotal": 4972.71
},
"instanceTotal": 12783.42
}
],
"totals": {
"courtFees": 2838.00,
"attorneyFees": 4972.71,
"patentAttorneyFees": 4972.71,
"grandTotal": 12783.42
}
}
```
### 3.6 Defaults
Sensible defaults for HL patent practice:
- **Streitwert**: EUR 1,000,000 (typical DE patent infringement)
- **MwSt**: 19%
- **Enabled instances**: LG only (user adds more)
- **Per instance**: 1 RA, 1 PA, 1 Mandant, mündliche Verhandlung: yes
- **Fee version**: Aktuell (2025)
---
## 4. Fristenrechner
### 4.1 Scope
Proceeding types grouped by jurisdiction:
**UPC Proceedings:**
| Code | Name (DE) | Name (EN) |
|------|-----------|-----------|
| UPC_INF | Verletzungsverfahren | Infringement Action |
| UPC_REV | Nichtigkeitsklage | Revocation Action |
| UPC_CCR | Widerklage auf Nichtigkeit | Counterclaim for Revocation |
| UPC_PI | Einstweilige Maßnahmen | Provisional Measures |
| UPC_APP | Berufung | Appeal |
**DE Court Proceedings:**
| Code | Name (DE) | Name (EN) |
|------|-----------|-----------|
| DE_INF | Verletzungsklage (LG) | Infringement (Regional Court) |
| DE_NULL | Nichtigkeitsverfahren (BPatG) | Nullity (Federal Patent Court) |
**EPA Proceedings (patholo-specific):**
| Code | Name (DE) | Name (EN) |
|------|-----------|-----------|
| EPA_OPP | Einspruchsverfahren | Opposition Proceedings |
| EPA_APP | Beschwerdeverfahren | Appeal Proceedings |
| EP_GRANT | EP-Erteilungsverfahren | EP Grant Procedure |
### 4.2 UI Layout
Three-step wizard layout:
```
┌─────────────────────────────────────────────────────────┐
│ Header: patholo + nav │
├─────────────────────────────────────────────────────────┤
│ Page Title: Fristenrechner │
│ Subtitle: Patent Deadline Calculator │
├─────────────────────────────────────────────────────────┤
│ │
│ STEP 1: Verfahrensart wählen │
│ ───────────────────────────── │
│ │
│ ┌─ UPC ──────────────────────────────────────────────┐ │
│ │ [Verletzung] [Nichtigkeit] [Widerklage] │ │
│ │ [Einstw. Maßn.] [Berufung] │ │
│ └────────────────────────────────────────────────────┘ │
│ │
│ ┌─ Deutsche Gerichte ────────────────────────────────┐ │
│ │ [Verletzungsklage LG] [Nichtigkeitsverfahren] │ │
│ └────────────────────────────────────────────────────┘ │
│ │
│ ┌─ EPA ──────────────────────────────────────────────┐ │
│ │ [Einspruch] [Beschwerde] [EP-Erteilung] │ │
│ └────────────────────────────────────────────────────┘ │
│ │
│ ─────────────────────────────────────────────────────── │
│ │
│ STEP 2: Ausgangsdatum eingeben │
│ ───────────────────────────── │
│ │
│ Auslösendes Ereignis: Klageerhebung │
│ Datum: [2026-04-14 📅] │
│ │
│ [Fristen berechnen] │
│ │
│ ─────────────────────────────────────────────────────── │
│ │
│ STEP 3: Ergebnis │
│ ───────────────── │
│ │
│ ┌────────────────────────────────────────────────────┐ │
│ │ ● Klageerhebung 14.04.2026 │ │
│ │ │ (Ausgangsdatum) │ │
│ │ │ │ │
│ │ ├─ Klageerwiderung 14.07.2026 │ │
│ │ │ Beklagter · 3 Monate · RoP 23 │ │
│ │ │ ⚠ Verschoben: 12.07.2026 → 14.07.2026 (So) │ │
│ │ │ │ │
│ │ ├─ Replik 14.09.2026 │ │
│ │ │ Kläger · 2 Monate · RoP 29b │ │
│ │ │ │ │
│ │ ├─ Duplik 14.10.2026 │ │
│ │ │ Beklagter · 1 Monat · RoP 29c │ │
│ │ │ │ │
│ │ ├─ Zwischenverfahren (vom Gericht) │ │
│ │ │ Gericht │ │
│ │ │ │ │
│ │ ├─ Mündliche Verhandlung (vom Gericht) │ │
│ │ │ Gericht │ │
│ │ │ │ │
│ │ └─ Entscheidung (vom Gericht) │ │
│ │ Gericht │ │
│ └────────────────────────────────────────────────────┘ │
│ │
│ [Drucken / Print] │
│ │
├─────────────────────────────────────────────────────────┤
│ Footer │
└─────────────────────────────────────────────────────────┘
```
### 4.3 Deadline Rules Data
All rules hardcoded in Go (no database needed). Example structure:
```go
type DeadlineRule struct {
Code string // "inf.sod"
Name string // "Klageerwiderung"
NameEN string // "Statement of Defence"
Party string // "defendant" | "claimant" | "court" | "both"
Duration int // 3
Unit string // "months" | "weeks" | "days"
IsMandatory bool // true
RuleRef string // "RoP 23" or "§ 82 PatG"
Notes string // Optional explanation
RelativeTo string // Code of parent rule (empty = root event)
}
type ProceedingType struct {
Code string // "UPC_INF"
Name string // "Verletzungsverfahren"
NameEN string // "Infringement Action"
Group string // "UPC" | "DE" | "EPA"
Rules []DeadlineRule
}
```
#### UPC Infringement Rules (UPC_INF)
| Code | Name | Party | Duration | Rule Ref |
|------|------|-------|----------|----------|
| inf.soc | Klageerhebung | claimant | — | — |
| inf.sod | Klageerwiderung | defendant | 3 months | RoP 23 |
| inf.reply | Replik | claimant | 2 months | RoP 29b |
| inf.rejoin | Duplik | defendant | 1 month | RoP 29c |
| inf.interim | Zwischenverfahren | court | — | — |
| inf.oral | Mündliche Verhandlung | court | — | — |
| inf.decision | Entscheidung | court | — | — |
#### UPC Revocation Rules (UPC_REV)
| Code | Name | Party | Duration | Rule Ref |
|------|------|-------|----------|----------|
| rev.app | Nichtigkeitsklage | claimant | — | — |
| rev.defence | Klageerwiderung | defendant | 3 months | — |
| rev.reply | Replik | claimant | 2 months | — |
| rev.rejoin | Duplik | defendant | 2 months | — |
| rev.interim | Zwischenverfahren | court | — | — |
| rev.oral | Mündliche Verhandlung | court | — | — |
| rev.decision | Entscheidung | court | — | — |
#### UPC Appeal Rules (UPC_APP)
| Code | Name | Party | Duration | Rule Ref |
|------|------|-------|----------|----------|
| app.notice | Berufungseinlegung | both | 2 months | RoP 220.1 |
| app.grounds | Berufungsbegründung | both | 2 months | RoP 220.1 |
| app.response | Berufungserwiderung | both | 2 months | — |
| app.oral | Mündliche Verhandlung | court | — | — |
| app.decision | Entscheidung | court | — | — |
#### UPC Provisional Measures (UPC_PI)
| Code | Name | Party | Duration | Rule Ref |
|------|------|-------|----------|----------|
| pi.app | Antrag | claimant | — | — |
| pi.response | Erwiderung | defendant | — | By court order |
| pi.oral | Mündliche Verhandlung | court | — | — |
| pi.order | Beschluss | court | — | — |
#### DE Infringement (DE_INF)
| Code | Name | Party | Duration | Rule Ref |
|------|------|-------|----------|----------|
| de_inf.klage | Klageerhebung | claimant | — | — |
| de_inf.erwidg | Klageerwiderung | defendant | ~6 weeks | § 276 ZPO |
| de_inf.replik | Replik | claimant | ~4 weeks | By court |
| de_inf.duplik | Duplik | defendant | ~4 weeks | By court |
| de_inf.termin | Haupttermin | court | — | — |
| de_inf.urteil | Urteil | court | — | — |
| de_inf.berufung | Berufungsfrist | both | 1 month | § 517 ZPO |
| de_inf.beruf_begr | Berufungsbegründung | both | 2 months | § 520 ZPO |
#### DE Nullity (DE_NULL)
| Code | Name | Party | Duration | Rule Ref |
|------|------|-------|----------|----------|
| de_null.klage | Nichtigkeitsklage | claimant | — | — |
| de_null.erwidg | Klageerwiderung | defendant | 2 months | § 82 PatG |
| de_null.termin | Mündliche Verhandlung | court | — | — |
| de_null.urteil | Urteil | court | — | — |
| de_null.berufung | Berufungsfrist | both | 1 month | § 110 PatG |
| de_null.beruf_begr | Berufungsbegründung | both | 1 month | § 111 PatG |
#### EPA Opposition (EPA_OPP)
| Code | Name | Party | Duration | Rule Ref |
|------|------|-------|----------|----------|
| epa_opp.grant | Veröffentlichung der Erteilung | — | — | — |
| epa_opp.frist | Einspruchsfrist | both | 9 months | Art. 99 EPÜ |
| epa_opp.erwidg | Erwiderung des Patentinhabers | defendant | 4 months | R. 79(1) EPÜ |
| epa_opp.entsch | Entscheidung | court | — | — |
| epa_opp.beschwerde | Beschwerdefrist | both | 2 months | Art. 108 EPÜ |
| epa_opp.beschwerde_begr | Beschwerdebegründung | both | 4 months | Art. 108 EPÜ |
#### EPA Appeal (EPA_APP)
| Code | Name | Party | Duration | Rule Ref |
|------|------|-------|----------|----------|
| epa_app.entsch | Zustellung der Entscheidung | — | — | — |
| epa_app.beschwerde | Beschwerdeeinlegung | both | 2 months | Art. 108 EPÜ |
| epa_app.begr | Beschwerdebegründung | both | 4 months | Art. 108 EPÜ |
| epa_app.erwidg | Erwiderung | both | — | By Board |
| epa_app.oral | Mündliche Verhandlung | court | — | — |
| epa_app.entsch2 | Entscheidung | court | — | — |
#### EP Grant Procedure (EP_GRANT)
| Code | Name | Party | Duration | Rule Ref |
|------|------|-------|----------|----------|
| ep_grant.filing | Anmeldung | claimant | — | — |
| ep_grant.search | Recherchenberricht | court | ~6 months | — |
| ep_grant.publish | Veröffentlichung (A1) | — | 18 months | Art. 93 EPÜ |
| ep_grant.exam_req | Prüfungsantrag | claimant | 6 months | R. 70(1) EPÜ |
| ep_grant.r71_3 | Mitteilung nach R. 71(3) | court | — | R. 71(3) EPÜ |
| ep_grant.approval | Zustimmung + Übersetzung | claimant | 4 months | R. 71(3) EPÜ |
| ep_grant.grant | Erteilung (B1) | — | — | — |
### 4.4 Calculation Logic
```go
func CalculateDeadline(triggerDate time.Time, rule DeadlineRule) DeadlineResult {
var endDate time.Time
switch rule.Unit {
case "days":
endDate = triggerDate.AddDate(0, 0, rule.Duration)
case "weeks":
endDate = triggerDate.AddDate(0, 0, rule.Duration*7)
case "months":
endDate = triggerDate.AddDate(0, rule.Duration, 0)
}
originalDate := endDate
adjustedDate := AdjustForNonWorkingDays(endDate)
return DeadlineResult{
RuleCode: rule.Code,
DueDate: adjustedDate,
OriginalDate: originalDate,
WasAdjusted: !adjustedDate.Equal(originalDate),
}
}
```
#### Holiday Adjustment
German federal holidays (computed per year):
- Neujahr (1. Jan)
- Karfreitag (Easter - 2)
- Ostermontag (Easter + 1)
- Tag der Arbeit (1. Mai)
- Christi Himmelfahrt (Easter + 39)
- Pfingstmontag (Easter + 50)
- Tag der Deutschen Einheit (3. Okt)
- 1. Weihnachtstag (25. Dez)
- 2. Weihnachtstag (26. Dez)
Easter Sunday computed via Anonymous Gregorian algorithm.
If deadline falls on weekend or holiday → move to next working day (forward up to 30 days).
### 4.5 API Contract
**Request: `POST /api/tools/fristenrechner`**
```json
{
"proceedingType": "UPC_INF",
"triggerDate": "2026-04-14"
}
```
**Response:**
```json
{
"proceedingType": "UPC_INF",
"proceedingName": "Verletzungsverfahren",
"triggerDate": "2026-04-14",
"deadlines": [
{
"code": "inf.soc",
"name": "Klageerhebung",
"nameEN": "Statement of Claim",
"party": "claimant",
"isMandatory": true,
"ruleRef": "",
"dueDate": "2026-04-14",
"originalDate": "2026-04-14",
"wasAdjusted": false,
"isRootEvent": true
},
{
"code": "inf.sod",
"name": "Klageerwiderung",
"nameEN": "Statement of Defence",
"party": "defendant",
"isMandatory": true,
"ruleRef": "RoP 23",
"dueDate": "2026-07-14",
"originalDate": "2026-07-14",
"wasAdjusted": false,
"isRootEvent": false
}
]
}
```
### 4.6 Defaults
- No proceeding type pre-selected (user must choose)
- Trigger date: today
- All rules shown (no filtering)
---
## 5. Shared: Navigation Integration
The home page (`index.tsx`) gets a new "Werkzeuge / Tools" card section linking to both calculators:
```
┌─────────────┐ ┌─────────────┐
│ 🔢 │ │ 📅 │
│ Kosten- │ │ Fristen- │
│ rechner │ │ rechner │
│ │ │ │
│ Cost │ │ Deadline │
│ Calculator │ │ Calculator │
└─────────────┘ └─────────────┘
```
### Navigation Changes
Header gets a "Werkzeuge" dropdown or direct links:
```html
<nav>
<a href="/">Home</a>
<a href="/tools/kostenrechner">Kostenrechner</a>
<a href="/tools/fristenrechner">Fristenrechner</a>
</nav>
```
---
## 6. Styling
Both tools reuse the existing CSS variables and patterns from `global.css`. New CSS classes needed:
### Calculator-Specific Styles
```css
/* Tool page layout */
.tool-page { padding: 2rem 0 4rem; }
.tool-header { margin-bottom: 2rem; }
.tool-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 2rem; }
/* Input panel */
.tool-input { /* left column */ }
.tool-results { position: sticky; top: 4.5rem; /* below header */ }
/* Streitwert slider */
.streitwert-input { /* range + number input combo */ }
.streitwert-presets { display: flex; gap: 0.5rem; }
.streitwert-preset { /* small pill button */ }
/* Instance cards */
.instance-card { border: 1px solid var(--color-border); border-radius: var(--radius); }
.instance-card.enabled { border-color: var(--color-accent); }
.instance-header { display: flex; align-items: center; gap: 0.75rem; cursor: pointer; }
.instance-details { padding: 1rem; border-top: 1px solid var(--color-border); }
/* Results panel */
.result-total { font-size: 1.75rem; font-weight: 700; color: var(--color-accent); }
.result-row { display: flex; justify-content: space-between; padding: 0.5rem 0; }
.result-section { border-bottom: 1px solid var(--color-border); padding: 1rem 0; }
/* Timeline (Fristenrechner) */
.timeline { position: relative; padding-left: 2rem; }
.timeline-item { position: relative; padding: 1rem 0; }
.timeline-dot { width: 10px; height: 10px; border-radius: 50%; background: var(--color-accent); }
.timeline-line { position: absolute; left: 4px; top: 0; bottom: 0; width: 2px; background: var(--color-border); }
.timeline-date { font-weight: 600; font-variant-numeric: tabular-nums; }
.timeline-adjusted { color: #d97706; font-size: 0.85rem; }
/* Party badges */
.party-badge { font-size: 0.75rem; padding: 0.15rem 0.5rem; border-radius: 99px; }
.party-claimant { background: #dbeafe; color: #1e40af; }
.party-defendant { background: #fef3c7; color: #92400e; }
.party-court { background: #f3e8ff; color: #6b21a8; }
.party-both { background: #e5e7eb; color: #374151; }
/* Responsive */
@media (max-width: 768px) {
.tool-grid { grid-template-columns: 1fr; }
.tool-results { position: static; }
}
/* Print */
@media print {
.header, .footer, .tool-input { display: none; }
.tool-results { position: static; }
}
```
---
## 7. Bilingual Approach
Same pattern as existing pages — German primary, English secondary:
```html
<h1>Prozesskostenrechner <span class="card-en">Cost Calculator</span></h1>
```
All labels, section headers, and result descriptions bilingual. Calculation logic language-independent (numbers are numbers).
Instance names bilingual:
- "LG (Verletzung 1. Instanz)" / "Regional Court (Infringement 1st Instance)"
- "Einspruchsverfahren" / "Opposition Proceedings"
Party labels: Kläger/Claimant, Beklagter/Defendant, Gericht/Court
---
## 8. Implementation Plan
### Phase 1: Backend Calculation Engine (Go)
1. `internal/calc/fee_tables.go` — All fee schedule data
2. `internal/calc/fees.go` — GKG/RVG/PatKostG/UPC/EPA calculations
3. `internal/calc/holidays.go` — Holiday computation + adjustment
4. `internal/calc/deadline_rules.go` — All proceeding types + rules
5. `internal/calc/deadlines.go` — Deadline calculation logic
6. Unit tests for all calculations
### Phase 2: API Endpoints (Go)
1. `internal/handlers/kostenrechner.go` — POST /api/tools/kostenrechner + page serving
2. `internal/handlers/fristenrechner.go` — POST /api/tools/fristenrechner + page serving
3. Route registration in `handlers.go`
### Phase 3: Frontend Pages (Bun/TSX)
1. `frontend/src/kostenrechner.tsx` — Page shell with form structure
2. `frontend/src/fristenrechner.tsx` — Page shell with wizard structure
3. Header.tsx — Add tool navigation links
4. CSS additions to `global.css`
### Phase 4: Client-Side Interactivity (TypeScript)
1. `frontend/src/client/kostenrechner.ts` — Form handling, API calls, result rendering
2. `frontend/src/client/fristenrechner.ts` — Wizard flow, API calls, timeline rendering
3. `frontend/build.ts` — Add new entrypoints and page renders
### Phase 5: Integration
1. Home page: Add "Werkzeuge" card section
2. Navigation: Add tool links to header
3. Print styles
4. Mobile testing
### Estimated Complexity
- **Phase 1**: Medium — port KanzlAI's algorithm, add EPA fees, write tests
- **Phase 2**: Low — straightforward JSON API handlers
- **Phase 3**: Low — SSR shells following existing patterns
- **Phase 4**: Medium — real-time UI interaction without React
- **Phase 5**: Low — minor additions to existing components
### Risk: Client-Side Complexity Without React
The main challenge is building interactive UIs (sliders, collapsible panels, real-time updates) with vanilla JS. The existing `login.ts` only handles form submission. The calculators need:
- Checkbox toggling with conditional UI
- Collapsible detail sections
- Real-time result updates on any input change
- Streitwert slider synced with text input
**Mitigation**: Keep all calculation on the server (Go API). Client JS only handles form state → API call → DOM update. No client-side calculation. This keeps the JS simple and the Go code testable.
**Alternative considered**: Moving to a reactive framework (Preact, Solid). Rejected — would require rebuilding existing pages and changing the build pipeline. Not worth it for two tool pages. If patholo grows to 5+ interactive pages, revisit.
---
## 9. Open Questions for Head
1. **EPA fee data**: The official EPA fee schedule changes periodically. Should we hardcode the current (2024/2025) fees, or add a fee version selector like DE/UPC?
2. **Security for costs (Prozesskostensicherheit)**: KanzlAI has this feature. Include in v1 or defer?
3. **Should the inventor implement this?** I have full context on the design. Alternatively, a coder worker can pick it up from this document.
---
## 10. Reference
- **KanzlAI source**: `/home/m/dev/KanzlAI/` — frontend/src/lib/costs/, backend/internal/services/fee_*
- **GKG Anlage 2**: Official German court fee schedule (Gerichtskostengesetz)
- **RVG Anlage 2**: Official German attorney fee schedule (Rechtsanwaltsvergütungsgesetz)
- **PatKostG**: Patentkostengesetz (BPatG fee basis)
- **UPC Rules of Procedure**: https://www.unified-patent-court.org/en/registry/rules-of-procedure
- **EPÜ (EPC)**: European Patent Convention, Art. 99, 108; Rules 71, 79
- **UPC Fee Schedule**: Table of Fees (pre-2026 and 2026 revision)