# 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)
