Files
rdb/tools/gen_tax_tables.rb

95 lines
4.6 KiB
Ruby

# frozen_string_literal: true
# Writes the 2026 income tax tables in the format of `tax` (income, tax, percent)
# into demo/. Sources: § 32a EStG (DE), § 33 EStG (AT), Tarif 2026 Art. 36 DBG (CH,
# ESTV Rundschreiben 2-215-D-2025), Art. 19 SteG LR 640.0, Fassung 01.07.2026 (LI).
# ruby tools/gen_tax_tables.rb
DEMO = File.expand_path('../demo', __dir__)
def write_table(name, points)
rows = points.map { |i, t, p| [i.to_s, format('%.2f', t), format('%.6f', p).sub(/0+\z/, '').sub(/\.\z/, '')] }
File.write(File.join(DEMO, name), "income\ttax\tpercent\n------\t---\t-------\n#{rows.map { |r| r.join("\t") }.join("\n")}\n")
end
# --- Germany: single assessment, § 32a Abs. 1 (the zones 2 and 3 are curves, so
# the table approximates them with short straight pieces; error below one euro)
def de_tax(x)
if x <= 12_348 then 0.0
elsif x <= 17_799 then y = (x - 12_348) / 10_000.0; ((914.51 * y) + 1400) * y
elsif x <= 69_878 then z = (x - 17_799) / 10_000.0; (((173.10 * z) + 2397) * z) + 1034.87
elsif x <= 277_825 then (0.42 * x) - 11_135.63
else (0.45 * x) - 19_470.38
end
end
de = [12_348] + (12_598..17_798).step(250).to_a + [17_799] + (18_049..69_549).step(250).to_a + [69_879, 277_826]
pts = de.each_with_index.map do |i, n|
nxt = de[n + 1]
slope = nxt ? (de_tax(nxt) - de_tax(i)) / (nxt - i) * 100 : 45.0
[i, de_tax(i), slope]
end
write_table('taxDE2026', [[0, 0.0, 0.0]] + pts)
# --- Austria: § 33 EStG, the tariff before the Absetzbeträge
at = [[13_539, 20], [21_992, 30], [36_458, 40], [70_365, 48], [104_859, 50], [1_000_000, 55]]
acc = 0.0
prev = [0, 0]
pts = [[0, 0.0, 0.0]]
at.each do |limit, rate|
acc += (limit - prev[0]) * prev[1] / 100.0
pts << [limit, acc, rate]
prev = [limit, rate]
end
write_table('taxAT2026', pts)
# --- Switzerland: direct federal tax, Art. 36 DBG, single persons (without canton and municipality)
ch = [[0, 0, 0], [15_200, 0, 0.77], [33_200, 138.60, 0.88], [43_500, 229.20, 2.64], [58_000, 612.00, 2.97],
[76_200, 1152.50, 5.94], [82_100, 1502.95, 6.60], [108_900, 3271.75, 8.80], [141_500, 6140.55, 11.00],
[185_100, 10_936.55, 13.20], [793_900, 91_298.15, 11.85], [794_000, 91_310.00, 11.50]]
write_table('taxCH2026', ch)
# --- Liechtenstein: Landessteuer, Art. 19 Abs. 1 Bst. a SteG; the municipal surcharge
# (150 % to 250 % of it) comes on top: total = Landessteuer * (1 + surcharge / 100)
li_rates = [[15_900, 1], [21_100, 3], [42_300, 4], [74_000, 5], [105_700, 6], [137_400, 6.5], [169_200, 7], [211_400, 8]]
acc = 0.0
prev = [15_855, 0]
pts = [[0, 0.0, 0.0]]
li_rates.each do |limit, rate|
acc += (limit - prev[0]) * prev[1] / 100.0 if prev[1].positive?
pts << [limit, acc, rate]
prev = [limit, rate]
end
write_table('taxLI2026', pts)
# --- Swiss cantons: the "einfache Staatssteuer" for single persons, before the Steuerfuss and
# without municipal and church tax. Source: ESTV Kantonsblätter (Steuerinformationen 2026).
# Each tariff is a list of [width, rate] pieces; above the last piece one flat rate applies to
# the whole income (the cantons call this "für das ganze Einkommen").
def staircase(pieces, flat_rate, expect_limit)
acc = 0.0
start = 0
pts = []
pieces.each do |width, rate|
pts << [start, acc, rate]
acc += width * rate / 100.0
start += width
end
raise "limit #{start} != #{expect_limit}" unless start == expect_limit
pts << [start, start * flat_rate / 100.0, flat_rate]
end
# AI: Art. 38 StG
write_table('taxCH_AI2026', staircase([[3_000, 0], [3_000, 1], [3_000, 2], [3_000, 3], [3_000, 4], [3_000, 5], [4_000, 6],
[4_000, 7], [4_000, 7.5], [10_000, 8], [34_000, 8.5], [66_000, 9], [60_000, 8.5]], 8, 200_000))
# AR: Art. 39 Abs. 1 lit. b StG (Alleinstehendentarif, Beträge ab Steuerperiode 2024); multiplied by the Steuereinheiten
write_table('taxCH_AR2026', staircase([[8_300, 0], [1_600, 0.6], [1_600, 1.0], [4_200, 1.5], [11_500, 1.8], [14_600, 2.2],
[12_500, 2.4], [19_800, 2.6], [14_600, 2.7], [36_500, 2.8], [135_600, 2.9]], 2.6, 260_800))
# SG: Art. 50 StG (Beträge ab Steuerperiode 2026)
write_table('taxCH_SG2026', staircase([[11_600, 0], [4_200, 4], [18_000, 6], [26_500, 8], [38_100, 9.2], [166_100, 9.4]], 8.5, 264_500))
# GR: Art. 39 Abs. 1 StG (indexierte Beträge ab Steuerperiode 2025)
write_table('taxCH_GR2026', staircase([[16_895, 0], [1_090, 2.5], [1_090, 4], [1_090, 5], [1_090, 6], [1_090, 6.5], [2_180, 7],
[6_540, 8], [4_360, 8.5], [4_360, 9], [4_360, 9.5], [21_800, 10.3], [21_800, 10.6],
[21_800, 10.7], [109_000, 11.2], [109_000, 11.3], [109_000, 11.4], [343_895, 11.6]], 11.0, 780_440))