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:
882
docs/design-prozesskostenrechner-fristenrechner.md
Normal file
882
docs/design-prozesskostenrechner-fristenrechner.md
Normal 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)
|
||||
Reference in New Issue
Block a user