feat: add Patentprozesskostenrechner fee calculation engine + API

Pure Go implementation of patent litigation cost calculator with:
- Step-based GKG/RVG fee accumulator across 4 historical schedules (2005/2013/2021/2025 + Aktuell alias)
- Instance multiplier tables for 8 court types (LG, OLG, BGH NZB/Rev, BPatG, BGH Null, DPMA, BPatG Canc)
- Full attorney fee calculation (VG, TG, Erhöhungsgebühr Nr. 1008 VV RVG, Auslagenpauschale)
- Prozesskostensicherheit computation
- UPC fee data (pre-2026 and 2026 schedules with value-based brackets, recoverable costs ceilings)
- Public API: POST /api/fees/calculate, GET /api/fees/schedules (no auth required)
- 22 unit tests covering all calculation paths

Fixes 3 Excel bugs:
- Bug 1: Prozesskostensicherheit VAT formula (subtract → add)
- Bug 2: Security for costs uses GKG base for court fee, not RVG
- Bug 3: Expert fees included in BPatG instance total
This commit is contained in:
m
2026-03-31 17:43:17 +02:00
parent d4092acc33
commit 850f3a62c8
6 changed files with 1247 additions and 0 deletions

View File

@@ -0,0 +1,249 @@
package services
import "math"
// feeBracket defines one bracket in the GKG/RVG fee schedule.
// Each bracket covers a range of Streitwert values.
type feeBracket struct {
UpperBound float64 // upper bound of this bracket
StepSize float64 // step size within this bracket
GKGIncrement float64 // GKG fee increment per step
RVGIncrement float64 // RVG fee increment per step
}
// feeScheduleData holds the bracket data for a fee schedule version.
type feeScheduleData struct {
Label string
ValidFrom string
Brackets []feeBracket
AliasOf string // if non-empty, this schedule is an alias for another
}
// feeSchedules contains all historical GKG/RVG fee schedule versions.
// Data extracted from Patentprozesskostenrechner.xlsm ListObjects.
var feeSchedules = map[string]*feeScheduleData{
"2005": {
Label: "GKG/RVG 2006-09-01",
ValidFrom: "2006-09-01",
Brackets: []feeBracket{
{300, 300, 25, 25},
{1500, 300, 10, 20},
{5000, 500, 8, 28},
{10000, 1000, 15, 37},
{25000, 3000, 23, 40},
{50000, 5000, 29, 72},
{200000, 15000, 100, 77},
{500000, 30000, 150, 118},
{math.MaxFloat64, 50000, 150, 150},
},
},
"2013": {
Label: "GKG/RVG 2013-08-01",
ValidFrom: "2013-08-01",
Brackets: []feeBracket{
{500, 300, 35, 45},
{2000, 500, 18, 35},
{10000, 1000, 19, 51},
{25000, 3000, 26, 46},
{50000, 5000, 35, 75},
{200000, 15000, 120, 85},
{500000, 30000, 179, 120},
{math.MaxFloat64, 50000, 180, 150},
},
},
"2021": {
Label: "GKG/RVG 2021-01-01",
ValidFrom: "2021-01-01",
Brackets: []feeBracket{
{500, 300, 38, 49},
{2000, 500, 20, 39},
{10000, 1000, 21, 56},
{25000, 3000, 29, 52},
{50000, 5000, 38, 81},
{200000, 15000, 132, 94},
{500000, 30000, 198, 132},
{math.MaxFloat64, 50000, 198, 165},
},
},
"2025": {
Label: "GKG/RVG 2025-06-01",
ValidFrom: "2025-06-01",
Brackets: []feeBracket{
{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},
{math.MaxFloat64, 50000, 210, 175},
},
},
"Aktuell": {
Label: "Aktuell (= 2025-06-01)",
ValidFrom: "2025-06-01",
AliasOf: "2025",
},
}
// instanceConfig holds the multipliers and fee basis for each court instance.
type instanceConfig struct {
Label string
CourtFactor float64 // multiplier for base court fee
CourtSource string // "GKG", "PatKostG", or "fixed"
FixedCourtFee float64 // only used when CourtSource is "fixed"
RAVGFactor float64 // Rechtsanwalt Verfahrensgebühr factor
RATGFactor float64 // Rechtsanwalt Terminsgebühr factor
PAVGFactor float64 // Patentanwalt Verfahrensgebühr factor
PATGFactor float64 // Patentanwalt Terminsgebühr factor
HasPA bool // whether patent attorneys are applicable
}
// instanceConfigs defines the fee parameters for each court instance type.
var instanceConfigs = map[string]instanceConfig{
"LG": {"LG (Verletzung 1. Instanz)", 3.0, "GKG", 0, 1.3, 1.2, 1.3, 1.2, true},
"OLG": {"OLG (Berufung)", 4.0, "GKG", 0, 1.6, 1.2, 1.6, 1.2, true},
"BGH_NZB": {"BGH (Nichtzulassungsbeschwerde)", 2.0, "GKG", 0, 2.3, 1.2, 1.6, 1.2, true},
"BGH_Rev": {"BGH (Revision)", 5.0, "GKG", 0, 2.3, 1.5, 1.6, 1.5, true},
"BPatG": {"BPatG (Nichtigkeitsverfahren)", 4.5, "PatKostG", 0, 1.3, 1.2, 1.3, 1.2, true},
"BGH_Null": {"BGH (Nichtigkeitsberufung)", 6.0, "GKG", 0, 1.6, 1.5, 1.6, 1.5, true},
"DPMA": {"DPMA (Löschungsverfahren)", 0, "fixed", 300, 1.3, 1.2, 0, 0, false},
"BPatG_Canc": {"BPatG (Löschungsbeschwerde)", 0, "fixed", 500, 1.3, 1.2, 0, 0, false},
}
// --- UPC Fee Data ---
// upcFeeBracket defines one bracket in a UPC fee table.
// MaxValue 0 means unlimited (last bracket).
type upcFeeBracket struct {
MaxValue float64
Fee float64
}
type upcFixedFees struct {
Infringement float64
CounterclaimInfringement float64
NonInfringement float64
LicenseCompensation float64
DetermineDamages float64
RevocationStandalone float64
CounterclaimRevocation float64
ProvisionalMeasures float64
}
type upcScheduleData struct {
Label string
ValidFrom string
FixedFees upcFixedFees
ValueBased []upcFeeBracket
Recoverable []upcFeeBracket
SMReduction float64
}
// upcSchedules contains UPC fee data for pre-2026 and 2026+ schedules.
var upcSchedules = map[string]*upcScheduleData{
"pre2026": {
Label: "UPC (vor 2026)",
ValidFrom: "2023-06-01",
FixedFees: upcFixedFees{
Infringement: 11000,
CounterclaimInfringement: 11000,
NonInfringement: 11000,
LicenseCompensation: 11000,
DetermineDamages: 3000,
RevocationStandalone: 20000,
CounterclaimRevocation: 20000,
ProvisionalMeasures: 11000,
},
ValueBased: []upcFeeBracket{
{500000, 0},
{750000, 2500},
{1000000, 4000},
{1500000, 8000},
{2000000, 13000},
{3000000, 20000},
{4000000, 26000},
{5000000, 32000},
{6000000, 39000},
{7000000, 46000},
{8000000, 52000},
{9000000, 58000},
{10000000, 65000},
{15000000, 75000},
{20000000, 100000},
{25000000, 125000},
{30000000, 150000},
{50000000, 250000},
{0, 325000},
},
Recoverable: []upcFeeBracket{
{250000, 38000},
{500000, 56000},
{1000000, 112000},
{2000000, 200000},
{4000000, 400000},
{8000000, 600000},
{16000000, 800000},
{30000000, 1200000},
{50000000, 1500000},
{0, 2000000},
},
SMReduction: 0.40,
},
"2026": {
Label: "UPC (ab 2026)",
ValidFrom: "2026-01-01",
FixedFees: upcFixedFees{
Infringement: 14600,
CounterclaimInfringement: 14600,
NonInfringement: 14600,
LicenseCompensation: 14600,
DetermineDamages: 4000,
RevocationStandalone: 26500,
CounterclaimRevocation: 26500,
ProvisionalMeasures: 14600,
},
// Estimated ~32% increase on pre-2026 values; replace with official data when available.
ValueBased: []upcFeeBracket{
{500000, 0},
{750000, 3300},
{1000000, 5300},
{1500000, 10600},
{2000000, 17200},
{3000000, 26400},
{4000000, 34300},
{5000000, 42200},
{6000000, 51500},
{7000000, 60700},
{8000000, 68600},
{9000000, 76600},
{10000000, 85800},
{15000000, 99000},
{20000000, 132000},
{25000000, 165000},
{30000000, 198000},
{50000000, 330000},
{0, 429000},
},
Recoverable: []upcFeeBracket{
{250000, 38000},
{500000, 56000},
{1000000, 112000},
{2000000, 200000},
{4000000, 400000},
{8000000, 600000},
{16000000, 800000},
{30000000, 1200000},
{50000000, 1500000},
{0, 2000000},
},
SMReduction: 0.50,
},
}
// Fee calculation constants (RVG)
const (
erhoehungsfaktor = 0.3 // Nr. 1008 VV RVG increase per additional client
erhoehungsfaktorMax = 2.0 // maximum increase factor
auslagenpauschale = 20.0 // Nr. 7002 VV RVG flat expense allowance (EUR)
)