fix(t-paliad-130): cap GKG/RVG Streitwert at €30M (§34 GKG / §22(2) RVG)
ComputeBaseFee walked the bracket table indefinitely, so a Streitwert of e.g. €100M produced fees far above what German law actually permits. §34 GKG / §22(2) RVG cap the table at €30M — above that the fee stays at the 30M-row value. Surgical fix: clamp streitwert to GermanFeeStreitwertCap (30M) at the top of ComputeBaseFee. Applies to all GKG/RVG fee versions (2005, 2013, 2021, 2025); UPC value-based fees use a separate code path (lookupUPCValueFee against UPCFeeSchedule.ValueBased) and stay uncapped — UPC has its own statutory tier structure with explicit 50M and unlimited brackets. Tests: cap holds across all four versions for both GKG and RVG; values below 30M continue to scale as before; UPC remains uncapped. Spot check (GKG / RVG base, 2025 schedule): 1M EUR → 6278.00 / 5553.50 5M EUR → 23078.00 / 19553.50 30M EUR → 128078.00 / 107053.50 50M EUR → 128078.00 / 107053.50 (capped) 100M EUR → 128078.00 / 107053.50 (capped) 1B EUR → 128078.00 / 107053.50 (capped)
This commit is contained in:
@@ -114,6 +114,12 @@ func resolveFeeVersion(version string) ([]FeeBracket, error) {
|
||||
return schedule.Brackets, nil
|
||||
}
|
||||
|
||||
// GermanFeeStreitwertCap is the §34 GKG / §22(2) RVG hard ceiling: above
|
||||
// 30M EUR Streitwert, German court and attorney fees stay at the 30M-row
|
||||
// value. UPC value-based fees use a separate code path (lookupUPCValueFee)
|
||||
// and are not affected by this cap.
|
||||
const GermanFeeStreitwertCap = 30_000_000.0
|
||||
|
||||
// ComputeBaseFee calculates the 1.0x base fee using the step-based accumulator.
|
||||
// isRVG=true for attorney fees (RVG), false for court fees (GKG).
|
||||
func ComputeBaseFee(streitwert float64, isRVG bool, version string) (float64, error) {
|
||||
@@ -122,6 +128,10 @@ func ComputeBaseFee(streitwert float64, isRVG bool, version string) (float64, er
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if streitwert > GermanFeeStreitwertCap {
|
||||
streitwert = GermanFeeStreitwertCap
|
||||
}
|
||||
|
||||
remaining := streitwert
|
||||
fee := 0.0
|
||||
lowerBound := 0.0
|
||||
|
||||
@@ -36,6 +36,72 @@ func TestComputeBaseFee_GKG_2025(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestComputeBaseFee_CapsAt30M(t *testing.T) {
|
||||
// §34 GKG / §22(2) RVG: above 30M Streitwert the fee is fixed at the
|
||||
// 30M-row value. Verify cap holds for both GKG and RVG, across all
|
||||
// known fee versions.
|
||||
versions := []string{"2025", "2021", "2013", "2005"}
|
||||
for _, v := range versions {
|
||||
for _, isRVG := range []bool{false, true} {
|
||||
fee30M, err := ComputeBaseFee(30_000_000, isRVG, v)
|
||||
if err != nil {
|
||||
t.Fatalf("ComputeBaseFee(30M, %v, %s): %v", isRVG, v, err)
|
||||
}
|
||||
fee100M, err := ComputeBaseFee(100_000_000, isRVG, v)
|
||||
if err != nil {
|
||||
t.Fatalf("ComputeBaseFee(100M, %v, %s): %v", isRVG, v, err)
|
||||
}
|
||||
fee1B, err := ComputeBaseFee(1_000_000_000, isRVG, v)
|
||||
if err != nil {
|
||||
t.Fatalf("ComputeBaseFee(1B, %v, %s): %v", isRVG, v, err)
|
||||
}
|
||||
if fee100M != fee30M {
|
||||
t.Errorf("version=%s isRVG=%v: fee at 100M (%.2f) must equal fee at 30M (%.2f) — cap not applied", v, isRVG, fee100M, fee30M)
|
||||
}
|
||||
if fee1B != fee30M {
|
||||
t.Errorf("version=%s isRVG=%v: fee at 1B (%.2f) must equal fee at 30M (%.2f) — cap not applied", v, isRVG, fee1B, fee30M)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestComputeBaseFee_BelowCapUnaffected(t *testing.T) {
|
||||
// Streitwert < 30M must continue to scale exactly as before.
|
||||
// Values are chosen comfortably below 30M — note the 500K+ bracket has
|
||||
// a 50,000-EUR step, so values within the same step ceil identically.
|
||||
values := []float64{100_000, 1_000_000, 5_000_000, 25_000_000}
|
||||
for _, sw := range values {
|
||||
feeBelow, err := ComputeBaseFee(sw, false, "2025")
|
||||
if err != nil {
|
||||
t.Fatalf("ComputeBaseFee(%v): %v", sw, err)
|
||||
}
|
||||
fee30M, err := ComputeBaseFee(30_000_000, false, "2025")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if feeBelow >= fee30M {
|
||||
t.Errorf("fee at %v (%.2f) should be < fee at 30M (%.2f) — cap leaking below threshold", sw, feeBelow, fee30M)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestComputeUPCInstance_NotCappedByGermanLimit(t *testing.T) {
|
||||
// UPC has explicit value-based tiers up to 50M and an unlimited bracket.
|
||||
// The §34 GKG cap must not bleed into UPC fee computation.
|
||||
input := InstanceInput{Enabled: true, FeeVersion: "2026"}
|
||||
r30M, err := ComputeUPCInstance(30_000_000, input, "UPC_FIRST")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
r50M, err := ComputeUPCInstance(50_000_000, input, "UPC_FIRST")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if r50M.ValueBasedFee <= r30M.ValueBasedFee {
|
||||
t.Errorf("UPC fee must keep scaling above 30M: 30M=%v 50M=%v", r30M.ValueBasedFee, r50M.ValueBasedFee)
|
||||
}
|
||||
}
|
||||
|
||||
func TestComputeBaseFee_Aktuell_Alias(t *testing.T) {
|
||||
v2025, err := ComputeBaseFee(1000000, false, "2025")
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user