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:
125
backend/internal/models/fee.go
Normal file
125
backend/internal/models/fee.go
Normal file
@@ -0,0 +1,125 @@
|
||||
package models
|
||||
|
||||
// FeeScheduleVersion identifies a fee schedule version.
|
||||
type FeeScheduleVersion string
|
||||
|
||||
const (
|
||||
FeeVersion2005 FeeScheduleVersion = "2005"
|
||||
FeeVersion2013 FeeScheduleVersion = "2013"
|
||||
FeeVersion2021 FeeScheduleVersion = "2021"
|
||||
FeeVersion2025 FeeScheduleVersion = "2025"
|
||||
FeeVersionAktuell FeeScheduleVersion = "Aktuell"
|
||||
)
|
||||
|
||||
// InstanceType identifies a court instance.
|
||||
type InstanceType string
|
||||
|
||||
const (
|
||||
InstanceLG InstanceType = "LG"
|
||||
InstanceOLG InstanceType = "OLG"
|
||||
InstanceBGHNZB InstanceType = "BGH_NZB"
|
||||
InstanceBGHRev InstanceType = "BGH_Rev"
|
||||
InstanceBPatG InstanceType = "BPatG"
|
||||
InstanceBGHNull InstanceType = "BGH_Null"
|
||||
InstanceDPMA InstanceType = "DPMA"
|
||||
InstanceBPatGCanc InstanceType = "BPatG_Canc"
|
||||
)
|
||||
|
||||
// ProceedingPath identifies the type of patent litigation proceeding.
|
||||
type ProceedingPath string
|
||||
|
||||
const (
|
||||
PathInfringement ProceedingPath = "infringement"
|
||||
PathNullity ProceedingPath = "nullity"
|
||||
PathCancellation ProceedingPath = "cancellation"
|
||||
)
|
||||
|
||||
// --- Request ---
|
||||
|
||||
// FeeCalculateRequest is the request body for POST /api/fees/calculate.
|
||||
type FeeCalculateRequest struct {
|
||||
Streitwert float64 `json:"streitwert"`
|
||||
VATRate float64 `json:"vat_rate"`
|
||||
ProceedingPath ProceedingPath `json:"proceeding_path"`
|
||||
Instances []InstanceInput `json:"instances"`
|
||||
IncludeSecurityCosts bool `json:"include_security_costs"`
|
||||
}
|
||||
|
||||
// InstanceInput configures one court instance in the calculation request.
|
||||
type InstanceInput struct {
|
||||
Type InstanceType `json:"type"`
|
||||
Enabled bool `json:"enabled"`
|
||||
FeeVersion FeeScheduleVersion `json:"fee_version"`
|
||||
NumAttorneys int `json:"num_attorneys"`
|
||||
NumPatentAttorneys int `json:"num_patent_attorneys"`
|
||||
NumClients int `json:"num_clients"`
|
||||
OralHearing bool `json:"oral_hearing"`
|
||||
ExpertFees float64 `json:"expert_fees"`
|
||||
}
|
||||
|
||||
// --- Response ---
|
||||
|
||||
// FeeCalculateResponse is the response for POST /api/fees/calculate.
|
||||
type FeeCalculateResponse struct {
|
||||
Instances []InstanceResult `json:"instances"`
|
||||
Totals []FeeTotal `json:"totals"`
|
||||
SecurityForCosts *SecurityForCosts `json:"security_for_costs,omitempty"`
|
||||
}
|
||||
|
||||
// InstanceResult contains the cost breakdown for one court instance.
|
||||
type InstanceResult struct {
|
||||
Type InstanceType `json:"type"`
|
||||
Label string `json:"label"`
|
||||
CourtFeeBase float64 `json:"court_fee_base"`
|
||||
CourtFeeMultiplier float64 `json:"court_fee_multiplier"`
|
||||
CourtFeeSource string `json:"court_fee_source"`
|
||||
CourtFee float64 `json:"court_fee"`
|
||||
ExpertFees float64 `json:"expert_fees"`
|
||||
CourtSubtotal float64 `json:"court_subtotal"`
|
||||
AttorneyBreakdown *AttorneyBreakdown `json:"attorney_breakdown,omitempty"`
|
||||
PatentAttorneyBreakdown *AttorneyBreakdown `json:"patent_attorney_breakdown,omitempty"`
|
||||
AttorneySubtotal float64 `json:"attorney_subtotal"`
|
||||
PatentAttorneySubtotal float64 `json:"patent_attorney_subtotal"`
|
||||
InstanceTotal float64 `json:"instance_total"`
|
||||
}
|
||||
|
||||
// AttorneyBreakdown details the fee computation for one attorney type.
|
||||
type AttorneyBreakdown struct {
|
||||
BaseFee float64 `json:"base_fee"`
|
||||
VGFactor float64 `json:"vg_factor"`
|
||||
VGFee float64 `json:"vg_fee"`
|
||||
IncreaseFee float64 `json:"increase_fee"`
|
||||
TGFactor float64 `json:"tg_factor"`
|
||||
TGFee float64 `json:"tg_fee"`
|
||||
Pauschale float64 `json:"pauschale"`
|
||||
SubtotalNet float64 `json:"subtotal_net"`
|
||||
VAT float64 `json:"vat"`
|
||||
SubtotalGross float64 `json:"subtotal_gross"`
|
||||
Count int `json:"count"`
|
||||
TotalGross float64 `json:"total_gross"`
|
||||
}
|
||||
|
||||
// FeeTotal is a labeled total amount.
|
||||
type FeeTotal struct {
|
||||
Label string `json:"label"`
|
||||
Total float64 `json:"total"`
|
||||
}
|
||||
|
||||
// SecurityForCosts is the Prozesskostensicherheit calculation result.
|
||||
type SecurityForCosts struct {
|
||||
Instance1 float64 `json:"instance_1"`
|
||||
Instance2 float64 `json:"instance_2"`
|
||||
NZB float64 `json:"nzb"`
|
||||
SubtotalNet float64 `json:"subtotal_net"`
|
||||
VAT float64 `json:"vat"`
|
||||
TotalGross float64 `json:"total_gross"`
|
||||
}
|
||||
|
||||
// FeeScheduleInfo describes a fee schedule version for the schedules endpoint.
|
||||
type FeeScheduleInfo struct {
|
||||
Key string `json:"key"`
|
||||
Label string `json:"label"`
|
||||
ValidFrom string `json:"valid_from"`
|
||||
IsAlias bool `json:"is_alias,omitempty"`
|
||||
AliasOf string `json:"alias_of,omitempty"`
|
||||
}
|
||||
Reference in New Issue
Block a user