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:
m
2026-05-04 20:58:08 +02:00
parent 7fdd74ed5d
commit 0e1d4869fb
2 changed files with 76 additions and 0 deletions

View File

@@ -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

View File

@@ -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 {