# 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))