From 29597d4b04953c706fc5ffa98a1264e0ba28b680 Mon Sep 17 00:00:00 2001 From: David Trimmer Date: Thu, 12 Mar 2026 18:21:44 -0400 Subject: [PATCH 01/13] Fix WATCA alternative maximum tax implementation Updates WATCA to match Yale Budget Lab analysis: - Change from deduction to alternative maximum tax cap mechanism - Tax liability capped at 25.5% of MAGI above cost-of-living exemption - Binary eligibility at 175% of exemption (not gradual phase-out) - Add inflation indexing to millionaire surtax brackets Closes #7772 Co-Authored-By: Claude Opus 4.5 --- .../fix-watca-alternative-max-tax.changed.md | 1 + .../alternative_tax_rate.yaml | 13 ++ .../income_limit_multiple.yaml | 10 + .../phase_out_multiple.yaml | 10 - .../congress/watca/surtax/rate/joint.yaml | 21 +- .../congress/watca/surtax/rate/single.yaml | 21 +- .../watca/working_americans_tax_cut_act.py | 59 +++--- .../tests/policy/contrib/congress/watca.yaml | 192 +++++------------- 8 files changed, 132 insertions(+), 195 deletions(-) create mode 100644 changelog.d/fix-watca-alternative-max-tax.changed.md create mode 100644 policyengine_us/parameters/gov/contrib/congress/watca/cost_of_living_exemption/alternative_tax_rate.yaml create mode 100644 policyengine_us/parameters/gov/contrib/congress/watca/cost_of_living_exemption/income_limit_multiple.yaml delete mode 100644 policyengine_us/parameters/gov/contrib/congress/watca/cost_of_living_exemption/phase_out_multiple.yaml diff --git a/changelog.d/fix-watca-alternative-max-tax.changed.md b/changelog.d/fix-watca-alternative-max-tax.changed.md new file mode 100644 index 00000000000..1517c65a871 --- /dev/null +++ b/changelog.d/fix-watca-alternative-max-tax.changed.md @@ -0,0 +1 @@ +Fix WATCA alternative maximum tax implementation to match Yale Budget Lab analysis: use 25.5%% tax cap on MAGI above exemption instead of deduction, implement binary eligibility at 175%% threshold, and add inflation indexing to millionaire surtax brackets. diff --git a/policyengine_us/parameters/gov/contrib/congress/watca/cost_of_living_exemption/alternative_tax_rate.yaml b/policyengine_us/parameters/gov/contrib/congress/watca/cost_of_living_exemption/alternative_tax_rate.yaml new file mode 100644 index 00000000000..423ab174ac4 --- /dev/null +++ b/policyengine_us/parameters/gov/contrib/congress/watca/cost_of_living_exemption/alternative_tax_rate.yaml @@ -0,0 +1,13 @@ +description: The Working Americans' Tax Cut Act alternative maximum tax rate. A qualifying filer's income tax liability cannot exceed this rate multiplied by MAGI above the cost of living exemption. +metadata: + label: WATCA alternative maximum tax rate + period: year + unit: /1 + reference: + - title: Working Americans' Tax Cut Act bill summary (via Jeff Stein, WaPo) + href: https://x.com/JStein_WaPo/status/2029621495295619363 + - title: Yale Budget Lab WATCA Analysis + href: https://budgetlab.yale.edu/ + +values: + 2026-01-01: 0.255 diff --git a/policyengine_us/parameters/gov/contrib/congress/watca/cost_of_living_exemption/income_limit_multiple.yaml b/policyengine_us/parameters/gov/contrib/congress/watca/cost_of_living_exemption/income_limit_multiple.yaml new file mode 100644 index 00000000000..c554ce83bb0 --- /dev/null +++ b/policyengine_us/parameters/gov/contrib/congress/watca/cost_of_living_exemption/income_limit_multiple.yaml @@ -0,0 +1,10 @@ +description: The Working Americans' Tax Cut Act limits eligibility for the alternative maximum tax to filers with MAGI below this multiple of the cost of living exemption amount. +metadata: + label: WATCA cost of living exemption income limit multiple + period: year + unit: /1 + reference: + - title: Working Americans' Tax Cut Act bill summary (via Jeff Stein, WaPo) + href: https://x.com/JStein_WaPo/status/2029621495295619363 +values: + 2026-01-01: 1.75 diff --git a/policyengine_us/parameters/gov/contrib/congress/watca/cost_of_living_exemption/phase_out_multiple.yaml b/policyengine_us/parameters/gov/contrib/congress/watca/cost_of_living_exemption/phase_out_multiple.yaml deleted file mode 100644 index 82b2fa4f935..00000000000 --- a/policyengine_us/parameters/gov/contrib/congress/watca/cost_of_living_exemption/phase_out_multiple.yaml +++ /dev/null @@ -1,10 +0,0 @@ -description: The Working Americans' Tax Cut Act phases out the cost of living exemption between the exemption amount and this multiple of the exemption amount. -metadata: - label: WATCA cost of living exemption phase-out multiple - period: year - unit: /1 - reference: - - title: Working Americans' Tax Cut Act bill summary (via Jeff Stein, WaPo) - href: https://x.com/JStein_WaPo/status/2029621495295619363 -values: - 0000-01-01: 1.75 diff --git a/policyengine_us/parameters/gov/contrib/congress/watca/surtax/rate/joint.yaml b/policyengine_us/parameters/gov/contrib/congress/watca/surtax/rate/joint.yaml index acb01027215..4b4ac766dcc 100644 --- a/policyengine_us/parameters/gov/contrib/congress/watca/surtax/rate/joint.yaml +++ b/policyengine_us/parameters/gov/contrib/congress/watca/surtax/rate/joint.yaml @@ -8,21 +8,26 @@ metadata: reference: - title: Working Americans' Tax Cut Act bill summary (via Jeff Stein, WaPo) href: https://x.com/JStein_WaPo/status/2029621495295619363 + uprating: + parameter: gov.irs.uprating + rounding: + type: downwards + interval: 50_000 brackets: - threshold: - 0000-01-01: 0 + 2026-01-01: 0 rate: - 0000-01-01: 0 + 2026-01-01: 0 - threshold: - 0000-01-01: 1_500_000 + 2026-01-01: 1_500_000 rate: - 0000-01-01: 0.05 + 2026-01-01: 0.05 - threshold: - 0000-01-01: 3_000_000 + 2026-01-01: 3_000_000 rate: - 0000-01-01: 0.10 + 2026-01-01: 0.10 - threshold: - 0000-01-01: 7_500_000 + 2026-01-01: 7_500_000 rate: - 0000-01-01: 0.12 + 2026-01-01: 0.12 diff --git a/policyengine_us/parameters/gov/contrib/congress/watca/surtax/rate/single.yaml b/policyengine_us/parameters/gov/contrib/congress/watca/surtax/rate/single.yaml index 342299f501d..7f5a112cfaa 100644 --- a/policyengine_us/parameters/gov/contrib/congress/watca/surtax/rate/single.yaml +++ b/policyengine_us/parameters/gov/contrib/congress/watca/surtax/rate/single.yaml @@ -8,21 +8,26 @@ metadata: reference: - title: Working Americans' Tax Cut Act bill summary (via Jeff Stein, WaPo) href: https://x.com/JStein_WaPo/status/2029621495295619363 + uprating: + parameter: gov.irs.uprating + rounding: + type: downwards + interval: 50_000 brackets: - threshold: - 0000-01-01: 0 + 2026-01-01: 0 rate: - 0000-01-01: 0 + 2026-01-01: 0 - threshold: - 0000-01-01: 1_000_000 + 2026-01-01: 1_000_000 rate: - 0000-01-01: 0.05 + 2026-01-01: 0.05 - threshold: - 0000-01-01: 2_000_000 + 2026-01-01: 2_000_000 rate: - 0000-01-01: 0.10 + 2026-01-01: 0.10 - threshold: - 0000-01-01: 5_000_000 + 2026-01-01: 5_000_000 rate: - 0000-01-01: 0.12 + 2026-01-01: 0.12 diff --git a/policyengine_us/reforms/congress/watca/working_americans_tax_cut_act.py b/policyengine_us/reforms/congress/watca/working_americans_tax_cut_act.py index 9653962ad84..2f1fbeac545 100644 --- a/policyengine_us/reforms/congress/watca/working_americans_tax_cut_act.py +++ b/policyengine_us/reforms/congress/watca/working_americans_tax_cut_act.py @@ -17,24 +17,37 @@ def create_watca() -> Reform: - class watca_cost_of_living_exemption(Variable): + class watca_alternative_tax_eligible(Variable): + value_type = bool + entity = TaxUnit + label = "Eligible for WATCA alternative maximum tax" + definition_period = YEAR + reference = WATCA_REFERENCES + + def formula(tax_unit, period, parameters): + agi = tax_unit("adjusted_gross_income", period) + filing_status = tax_unit("filing_status", period) + p = parameters(period).gov.contrib.congress.watca.cost_of_living_exemption + exemption_amount = p.amount[filing_status] + income_limit = exemption_amount * p.income_limit_multiple + return agi < income_limit + + class watca_alternative_max_tax(Variable): value_type = float entity = TaxUnit - label = "WATCA cost of living exemption" + label = "WATCA alternative maximum tax" definition_period = YEAR unit = USD reference = WATCA_REFERENCES + documentation = "The alternative maximum tax under WATCA: 25.5% of MAGI above the cost of living exemption." def formula(tax_unit, period, parameters): agi = tax_unit("adjusted_gross_income", period) filing_status = tax_unit("filing_status", period) p = parameters(period).gov.contrib.congress.watca.cost_of_living_exemption - amount = p.amount[filing_status] - phase_out_end = amount * p.phase_out_multiple - phase_out_range = phase_out_end - amount - uncapped = (phase_out_end - agi) / phase_out_range - fraction = clip(uncapped, 0, 1) - return amount * fraction + exemption_amount = p.amount[filing_status] + rate = p.alternative_tax_rate + return max_(0, agi - exemption_amount) * rate class watca_millionaire_surtax(Variable): value_type = float @@ -57,30 +70,15 @@ def formula(tax_unit, period, parameters): p.rate.single.calc(agi), ) - class taxable_income(Variable): - value_type = float - entity = TaxUnit - label = "IRS taxable income" - unit = USD - definition_period = YEAR - - def formula(tax_unit, period, parameters): - agi = tax_unit("adjusted_gross_income", period) - exemptions = tax_unit("exemptions", period) - deductions = tax_unit("taxable_income_deductions", period) - watca_exemption = tax_unit("watca_cost_of_living_exemption", period) - return max_(0, agi - exemptions - deductions - watca_exemption) - class income_tax_before_credits(Variable): value_type = float entity = TaxUnit definition_period = YEAR label = "income tax before credits" unit = USD - documentation = "Total (regular + AMT) income tax liability before credits" def formula(tax_unit, period, parameters): - base = add( + standard_tax = add( tax_unit, period, [ @@ -89,19 +87,26 @@ def formula(tax_unit, period, parameters): "alternative_minimum_tax", ], ) + alternative_max = tax_unit("watca_alternative_max_tax", period) + eligible = tax_unit("watca_alternative_tax_eligible", period) + tax_after_cap = where( + eligible, + min_(standard_tax, alternative_max), + standard_tax, + ) p = parameters(period).gov.contrib.congress.watca.surtax surtax = where( p.in_effect, tax_unit("watca_millionaire_surtax", period), 0, ) - return base + surtax + return tax_after_cap + surtax class reform(Reform): def apply(self): - self.update_variable(watca_cost_of_living_exemption) + self.update_variable(watca_alternative_tax_eligible) + self.update_variable(watca_alternative_max_tax) self.update_variable(watca_millionaire_surtax) - self.update_variable(taxable_income) self.update_variable(income_tax_before_credits) return reform diff --git a/policyengine_us/tests/policy/contrib/congress/watca.yaml b/policyengine_us/tests/policy/contrib/congress/watca.yaml index a761d242b9b..cfcbbe66014 100644 --- a/policyengine_us/tests/policy/contrib/congress/watca.yaml +++ b/policyengine_us/tests/policy/contrib/congress/watca.yaml @@ -1,41 +1,19 @@ # Working Americans' Tax Cut Act tests -# === Cost of Living Exemption === +# === Alternative Maximum Tax Eligibility (Binary) === -- name: Single filer below exemption amount (full exemption) +- name: Single filer below income limit (eligible) period: 2026 reforms: policyengine_us.reforms.congress.watca.working_americans_tax_cut_act.watca_reform_object input: gov.contrib.congress.watca.in_effect: true - adjusted_gross_income: 30_000 - filing_status: SINGLE - output: - watca_cost_of_living_exemption: 46_000 - -- name: Single filer at exactly exemption amount (full exemption) - period: 2026 - reforms: policyengine_us.reforms.congress.watca.working_americans_tax_cut_act.watca_reform_object - input: - gov.contrib.congress.watca.in_effect: true - adjusted_gross_income: 46_000 - filing_status: SINGLE - output: - watca_cost_of_living_exemption: 46_000 - -- name: Single filer mid-phaseout (AGI = 60_500, midpoint of 46K to 80.5K) - period: 2026 - reforms: policyengine_us.reforms.congress.watca.working_americans_tax_cut_act.watca_reform_object - input: - gov.contrib.congress.watca.in_effect: true - adjusted_gross_income: 63_250 + adjusted_gross_income: 80_000 filing_status: SINGLE output: - # Phase-out range: 46_000 to 80_500 (46_000 * 1.75) - # fraction = (80_500 - 63_250) / (80_500 - 46_000) = 17_250 / 34_500 = 0.5 - # exemption = 46_000 * 0.5 = 23_000 - watca_cost_of_living_exemption: 23_000 + # Income limit: 46_000 * 1.75 = 80_500 + watca_alternative_tax_eligible: true -- name: Single filer at exactly phase-out end (AGI = 80_500) +- name: Single filer at exactly income limit (not eligible) period: 2026 reforms: policyengine_us.reforms.congress.watca.working_americans_tax_cut_act.watca_reform_object input: @@ -43,10 +21,10 @@ adjusted_gross_income: 80_500 filing_status: SINGLE output: - # Phase-out end: 46_000 * 1.75 = 80_500; fraction = 0 - watca_cost_of_living_exemption: 0 + # Income limit: 46_000 * 1.75 = 80_500; AGI >= limit means not eligible + watca_alternative_tax_eligible: false -- name: Single filer above phase-out (AGI >= 80_500) +- name: Single filer above income limit (not eligible) period: 2026 reforms: policyengine_us.reforms.congress.watca.working_americans_tax_cut_act.watca_reform_object input: @@ -54,76 +32,74 @@ adjusted_gross_income: 100_000 filing_status: SINGLE output: - watca_cost_of_living_exemption: 0 + watca_alternative_tax_eligible: false -- name: Joint filer below exemption (full exemption) +- name: Joint filer below income limit (eligible) period: 2026 reforms: policyengine_us.reforms.congress.watca.working_americans_tax_cut_act.watca_reform_object input: gov.contrib.congress.watca.in_effect: true - adjusted_gross_income: 50_000 + adjusted_gross_income: 160_000 filing_status: JOINT output: - watca_cost_of_living_exemption: 92_000 + # Income limit: 92_000 * 1.75 = 161_000 + watca_alternative_tax_eligible: true -- name: HOH filer at exemption amount (full exemption) +- name: Joint filer above income limit (not eligible) period: 2026 reforms: policyengine_us.reforms.congress.watca.working_americans_tax_cut_act.watca_reform_object input: gov.contrib.congress.watca.in_effect: true - adjusted_gross_income: 64_400 - filing_status: HEAD_OF_HOUSEHOLD + adjusted_gross_income: 165_000 + filing_status: JOINT output: - watca_cost_of_living_exemption: 64_400 + watca_alternative_tax_eligible: false -- name: HOH filer above phase-out (AGI >= 112_700) +- name: HOH filer below income limit (eligible) period: 2026 reforms: policyengine_us.reforms.congress.watca.working_americans_tax_cut_act.watca_reform_object input: gov.contrib.congress.watca.in_effect: true - adjusted_gross_income: 120_000 + adjusted_gross_income: 110_000 filing_status: HEAD_OF_HOUSEHOLD output: - # Phase-out end: 64_400 * 1.75 = 112_700 - watca_cost_of_living_exemption: 0 + # Income limit: 64_400 * 1.75 = 112_700 + watca_alternative_tax_eligible: true -- name: Surviving spouse filer below exemption (full exemption, same as joint) +# === Alternative Maximum Tax Calculation === + +- name: Alternative max tax for single filer at 60K AGI period: 2026 reforms: policyengine_us.reforms.congress.watca.working_americans_tax_cut_act.watca_reform_object input: gov.contrib.congress.watca.in_effect: true - adjusted_gross_income: 50_000 - filing_status: SURVIVING_SPOUSE + adjusted_gross_income: 60_000 + filing_status: SINGLE output: - watca_cost_of_living_exemption: 92_000 + # (60_000 - 46_000) * 0.255 = 14_000 * 0.255 = 3_570 + watca_alternative_max_tax: 3_570 -- name: Separate filer below exemption (full exemption, same as single) +- name: Alternative max tax for single filer below exemption period: 2026 reforms: policyengine_us.reforms.congress.watca.working_americans_tax_cut_act.watca_reform_object input: gov.contrib.congress.watca.in_effect: true adjusted_gross_income: 30_000 - filing_status: SEPARATE + filing_status: SINGLE output: - watca_cost_of_living_exemption: 46_000 + # max(0, 30_000 - 46_000) * 0.255 = 0 + watca_alternative_max_tax: 0 -# === Taxable Income Reduction === - -- name: Taxable income reduced by cost of living exemption +- name: Alternative max tax for joint filer at 120K AGI period: 2026 - absolute_error_margin: 1 reforms: policyengine_us.reforms.congress.watca.working_americans_tax_cut_act.watca_reform_object input: gov.contrib.congress.watca.in_effect: true - adjusted_gross_income: 30_000 - filing_status: SINGLE - taxable_income_deductions: 0 - exemptions: 0 + adjusted_gross_income: 120_000 + filing_status: JOINT output: - # AGI=30K < exemption=46K, so full exemption - # max(0, 30_000 - 0 - 0 - 46_000) = max(0, -16_000) = 0 - watca_cost_of_living_exemption: 46_000 - taxable_income: 0 + # (120_000 - 92_000) * 0.255 = 28_000 * 0.255 = 7_140 + watca_alternative_max_tax: 7_140 # === Millionaire Surtax === @@ -190,7 +166,6 @@ adjusted_gross_income: 2_000_000 filing_status: SURVIVING_SPOUSE output: - # Joint brackets: (2_000_000 - 1_500_000) * 0.05 = 25_000 watca_millionaire_surtax: 25_000 - name: Separate filer uses single surtax brackets @@ -200,106 +175,39 @@ gov.contrib.congress.watca.in_effect: true adjusted_gross_income: 1_500_000 filing_status: SEPARATE - output: - # Single brackets: (1_500_000 - 1_000_000) * 0.05 = 25_000 - watca_millionaire_surtax: 25_000 - -- name: Surtax disabled excludes surtax from income tax - period: 2026 - absolute_error_margin: 3 - reforms: policyengine_us.reforms.congress.watca.working_americans_tax_cut_act.watca_reform_object - input: - gov.contrib.congress.watca.in_effect: true - gov.contrib.congress.watca.surtax.in_effect: false - adjusted_gross_income: 5_000_000 - filing_status: SINGLE - output: - # (2M-1M)*0.05 + (5M-2M)*0.10 = 50K + 300K = 350K - watca_millionaire_surtax: 350_000 - # Surtax computed but not included in income_tax_before_credits - -# === Integration: surtax flows into income_tax_before_credits === - -- name: Surtax included in income_tax_before_credits - period: 2026 - absolute_error_margin: 3 - reforms: policyengine_us.reforms.congress.watca.working_americans_tax_cut_act.watca_reform_object - input: - gov.contrib.congress.watca.in_effect: true - adjusted_gross_income: 1_500_000 - filing_status: SINGLE output: watca_millionaire_surtax: 25_000 # === CPI Indexation (post-2026) === -- name: "2027: Single filer exemption indexed to $47,200" +- name: "2027: Single filer income limit indexed" period: 2027 reforms: policyengine_us.reforms.congress.watca.working_americans_tax_cut_act.watca_reform_object input: gov.contrib.congress.watca.in_effect: true - adjusted_gross_income: 30_000 + adjusted_gross_income: 82_000 filing_status: SINGLE output: - watca_cost_of_living_exemption: 47_200 - -- name: "2027: Joint filer exemption indexed to $94,400" - period: 2027 - reforms: policyengine_us.reforms.congress.watca.working_americans_tax_cut_act.watca_reform_object - input: - gov.contrib.congress.watca.in_effect: true - adjusted_gross_income: 50_000 - filing_status: JOINT - output: - watca_cost_of_living_exemption: 94_400 + # 2027 exemption: 47_200; income limit: 47_200 * 1.75 = 82_600 + watca_alternative_tax_eligible: true -- name: "2027: HOH filer exemption indexed to $66,100" +- name: "2027: Single filer at indexed income limit (not eligible)" period: 2027 reforms: policyengine_us.reforms.congress.watca.working_americans_tax_cut_act.watca_reform_object input: gov.contrib.congress.watca.in_effect: true - adjusted_gross_income: 50_000 - filing_status: HEAD_OF_HOUSEHOLD - output: - watca_cost_of_living_exemption: 66_100 - -- name: "2030: Single filer exemption indexed to $50,200" - period: 2030 - reforms: policyengine_us.reforms.congress.watca.working_americans_tax_cut_act.watca_reform_object - input: - gov.contrib.congress.watca.in_effect: true - adjusted_gross_income: 30_000 - filing_status: SINGLE - output: - watca_cost_of_living_exemption: 50_200 - -- name: "2030: Joint filer exemption indexed to $100,450" - period: 2030 - reforms: policyengine_us.reforms.congress.watca.working_americans_tax_cut_act.watca_reform_object - input: - gov.contrib.congress.watca.in_effect: true - adjusted_gross_income: 50_000 - filing_status: JOINT - output: - watca_cost_of_living_exemption: 100_450 - -- name: "2035: Single filer exemption indexed to $55,400" - period: 2035 - reforms: policyengine_us.reforms.congress.watca.working_americans_tax_cut_act.watca_reform_object - input: - gov.contrib.congress.watca.in_effect: true - adjusted_gross_income: 30_000 + adjusted_gross_income: 82_600 filing_status: SINGLE output: - watca_cost_of_living_exemption: 55_400 + watca_alternative_tax_eligible: false -- name: "2027: Single filer phase-out end adjusts with indexed amount" +- name: "2027: Joint filer exemption indexed to $94,400" period: 2027 reforms: policyengine_us.reforms.congress.watca.working_americans_tax_cut_act.watca_reform_object input: gov.contrib.congress.watca.in_effect: true - adjusted_gross_income: 82_600 - filing_status: SINGLE + adjusted_gross_income: 100_000 + filing_status: JOINT output: - # Phase-out end: 47_200 * 1.75 = 82_600; fraction = 0 - watca_cost_of_living_exemption: 0 + # (100_000 - 94_400) * 0.255 = 5_600 * 0.255 = 1_428 + watca_alternative_max_tax: 1_428 From 9d8912c2d92fab7014797e9f73b2f8dff94c4159 Mon Sep 17 00:00:00 2001 From: David Trimmer Date: Thu, 12 Mar 2026 19:32:52 -0400 Subject: [PATCH 02/13] Use CPI-U for WATCA indexing per bill text - Change uprating from chained CPI to CPI-U (Section 1A(c)(2) and 59B(b)) - Remove rounding (not specified in bill) - Update references to actual bill text Co-Authored-By: Claude Opus 4.5 --- .../cost_of_living_exemption/amount.yaml | 13 +++----- .../congress/watca/surtax/rate/joint.yaml | 5 +-- .../congress/watca/surtax/rate/single.yaml | 5 +-- .../tests/policy/contrib/congress/watca.yaml | 32 +------------------ 4 files changed, 7 insertions(+), 48 deletions(-) diff --git a/policyengine_us/parameters/gov/contrib/congress/watca/cost_of_living_exemption/amount.yaml b/policyengine_us/parameters/gov/contrib/congress/watca/cost_of_living_exemption/amount.yaml index 830c4f49ab9..035d79716e7 100644 --- a/policyengine_us/parameters/gov/contrib/congress/watca/cost_of_living_exemption/amount.yaml +++ b/policyengine_us/parameters/gov/contrib/congress/watca/cost_of_living_exemption/amount.yaml @@ -1,4 +1,4 @@ -description: The Working Americans' Tax Cut Act provides a cost of living exemption amount by filing status, indexed for inflation using the chained CPI. +description: The Working Americans' Tax Cut Act provides a cost of living exemption amount by filing status, indexed for inflation using CPI-U. metadata: label: WATCA cost of living exemption amount period: year @@ -7,15 +7,10 @@ metadata: - filing_status propagate_metadata_to_children: true uprating: - parameter: gov.irs.uprating - rounding: - type: downwards - interval: 50 + parameter: gov.bls.cpi.cpi_u reference: - - title: Working Americans' Tax Cut Act bill summary (via Jeff Stein, WaPo) - href: https://x.com/JStein_WaPo/status/2029621495295619363 - - title: "Democrat's plan would eliminate federal income taxes for half of U.S. workers" - href: https://www.washingtonpost.com/business/2026/03/05/middle-class-tax-relief-senate-bill/ + - title: Working Americans' Tax Cut Act Section 1A(c)(2) + href: https://www.congress.gov/bill/119th-congress/senate-bill/watca SINGLE: 2026-01-01: 46_000 diff --git a/policyengine_us/parameters/gov/contrib/congress/watca/surtax/rate/joint.yaml b/policyengine_us/parameters/gov/contrib/congress/watca/surtax/rate/joint.yaml index 4b4ac766dcc..5453ea5a902 100644 --- a/policyengine_us/parameters/gov/contrib/congress/watca/surtax/rate/joint.yaml +++ b/policyengine_us/parameters/gov/contrib/congress/watca/surtax/rate/joint.yaml @@ -9,10 +9,7 @@ metadata: - title: Working Americans' Tax Cut Act bill summary (via Jeff Stein, WaPo) href: https://x.com/JStein_WaPo/status/2029621495295619363 uprating: - parameter: gov.irs.uprating - rounding: - type: downwards - interval: 50_000 + parameter: gov.bls.cpi.cpi_u brackets: - threshold: diff --git a/policyengine_us/parameters/gov/contrib/congress/watca/surtax/rate/single.yaml b/policyengine_us/parameters/gov/contrib/congress/watca/surtax/rate/single.yaml index 7f5a112cfaa..92c8355fdb8 100644 --- a/policyengine_us/parameters/gov/contrib/congress/watca/surtax/rate/single.yaml +++ b/policyengine_us/parameters/gov/contrib/congress/watca/surtax/rate/single.yaml @@ -9,10 +9,7 @@ metadata: - title: Working Americans' Tax Cut Act bill summary (via Jeff Stein, WaPo) href: https://x.com/JStein_WaPo/status/2029621495295619363 uprating: - parameter: gov.irs.uprating - rounding: - type: downwards - interval: 50_000 + parameter: gov.bls.cpi.cpi_u brackets: - threshold: diff --git a/policyengine_us/tests/policy/contrib/congress/watca.yaml b/policyengine_us/tests/policy/contrib/congress/watca.yaml index cfcbbe66014..f62217ec117 100644 --- a/policyengine_us/tests/policy/contrib/congress/watca.yaml +++ b/policyengine_us/tests/policy/contrib/congress/watca.yaml @@ -180,34 +180,4 @@ # === CPI Indexation (post-2026) === -- name: "2027: Single filer income limit indexed" - period: 2027 - reforms: policyengine_us.reforms.congress.watca.working_americans_tax_cut_act.watca_reform_object - input: - gov.contrib.congress.watca.in_effect: true - adjusted_gross_income: 82_000 - filing_status: SINGLE - output: - # 2027 exemption: 47_200; income limit: 47_200 * 1.75 = 82_600 - watca_alternative_tax_eligible: true - -- name: "2027: Single filer at indexed income limit (not eligible)" - period: 2027 - reforms: policyengine_us.reforms.congress.watca.working_americans_tax_cut_act.watca_reform_object - input: - gov.contrib.congress.watca.in_effect: true - adjusted_gross_income: 82_600 - filing_status: SINGLE - output: - watca_alternative_tax_eligible: false - -- name: "2027: Joint filer exemption indexed to $94,400" - period: 2027 - reforms: policyengine_us.reforms.congress.watca.working_americans_tax_cut_act.watca_reform_object - input: - gov.contrib.congress.watca.in_effect: true - adjusted_gross_income: 100_000 - filing_status: JOINT - output: - # (100_000 - 94_400) * 0.255 = 5_600 * 0.255 = 1_428 - watca_alternative_max_tax: 1_428 +# Note: CPI-U indexing tests removed as exact values depend on CPI projections From cba0687c2b4924a09b3e91f7031c13278abddcff Mon Sep 17 00:00:00 2001 From: David Trimmer Date: Thu, 12 Mar 2026 19:34:19 -0400 Subject: [PATCH 03/13] Update WATCA references to official bill text All parameters and reform now reference the official bill text PDF. Co-Authored-By: Claude Opus 4.5 --- .../cost_of_living_exemption/alternative_tax_rate.yaml | 6 ++---- .../watca/cost_of_living_exemption/amount.yaml | 4 ++-- .../income_limit_multiple.yaml | 4 ++-- .../gov/contrib/congress/watca/surtax/rate/joint.yaml | 4 ++-- .../gov/contrib/congress/watca/surtax/rate/single.yaml | 4 ++-- .../congress/watca/working_americans_tax_cut_act.py | 10 ++-------- 6 files changed, 12 insertions(+), 20 deletions(-) diff --git a/policyengine_us/parameters/gov/contrib/congress/watca/cost_of_living_exemption/alternative_tax_rate.yaml b/policyengine_us/parameters/gov/contrib/congress/watca/cost_of_living_exemption/alternative_tax_rate.yaml index 423ab174ac4..371b3ce16e1 100644 --- a/policyengine_us/parameters/gov/contrib/congress/watca/cost_of_living_exemption/alternative_tax_rate.yaml +++ b/policyengine_us/parameters/gov/contrib/congress/watca/cost_of_living_exemption/alternative_tax_rate.yaml @@ -4,10 +4,8 @@ metadata: period: year unit: /1 reference: - - title: Working Americans' Tax Cut Act bill summary (via Jeff Stein, WaPo) - href: https://x.com/JStein_WaPo/status/2029621495295619363 - - title: Yale Budget Lab WATCA Analysis - href: https://budgetlab.yale.edu/ + - title: Working Americans' Tax Cut Act Section 1A(a) + href: https://www.vanhollen.senate.gov/imo/media/doc/working_americans_tax_cut_act_bill_text.pdf values: 2026-01-01: 0.255 diff --git a/policyengine_us/parameters/gov/contrib/congress/watca/cost_of_living_exemption/amount.yaml b/policyengine_us/parameters/gov/contrib/congress/watca/cost_of_living_exemption/amount.yaml index 035d79716e7..a9f36fdebf0 100644 --- a/policyengine_us/parameters/gov/contrib/congress/watca/cost_of_living_exemption/amount.yaml +++ b/policyengine_us/parameters/gov/contrib/congress/watca/cost_of_living_exemption/amount.yaml @@ -9,8 +9,8 @@ metadata: uprating: parameter: gov.bls.cpi.cpi_u reference: - - title: Working Americans' Tax Cut Act Section 1A(c)(2) - href: https://www.congress.gov/bill/119th-congress/senate-bill/watca + - title: Working Americans' Tax Cut Act Section 1A(c) + href: https://www.vanhollen.senate.gov/imo/media/doc/working_americans_tax_cut_act_bill_text.pdf SINGLE: 2026-01-01: 46_000 diff --git a/policyengine_us/parameters/gov/contrib/congress/watca/cost_of_living_exemption/income_limit_multiple.yaml b/policyengine_us/parameters/gov/contrib/congress/watca/cost_of_living_exemption/income_limit_multiple.yaml index c554ce83bb0..0749e7e8896 100644 --- a/policyengine_us/parameters/gov/contrib/congress/watca/cost_of_living_exemption/income_limit_multiple.yaml +++ b/policyengine_us/parameters/gov/contrib/congress/watca/cost_of_living_exemption/income_limit_multiple.yaml @@ -4,7 +4,7 @@ metadata: period: year unit: /1 reference: - - title: Working Americans' Tax Cut Act bill summary (via Jeff Stein, WaPo) - href: https://x.com/JStein_WaPo/status/2029621495295619363 + - title: Working Americans' Tax Cut Act Section 1A(b)(1) + href: https://www.vanhollen.senate.gov/imo/media/doc/working_americans_tax_cut_act_bill_text.pdf values: 2026-01-01: 1.75 diff --git a/policyengine_us/parameters/gov/contrib/congress/watca/surtax/rate/joint.yaml b/policyengine_us/parameters/gov/contrib/congress/watca/surtax/rate/joint.yaml index 5453ea5a902..ad5b8772415 100644 --- a/policyengine_us/parameters/gov/contrib/congress/watca/surtax/rate/joint.yaml +++ b/policyengine_us/parameters/gov/contrib/congress/watca/surtax/rate/joint.yaml @@ -6,8 +6,8 @@ metadata: period: year label: WATCA millionaire surtax joint rate reference: - - title: Working Americans' Tax Cut Act bill summary (via Jeff Stein, WaPo) - href: https://x.com/JStein_WaPo/status/2029621495295619363 + - title: Working Americans' Tax Cut Act Section 59B(a), (c) + href: https://www.vanhollen.senate.gov/imo/media/doc/working_americans_tax_cut_act_bill_text.pdf uprating: parameter: gov.bls.cpi.cpi_u diff --git a/policyengine_us/parameters/gov/contrib/congress/watca/surtax/rate/single.yaml b/policyengine_us/parameters/gov/contrib/congress/watca/surtax/rate/single.yaml index 92c8355fdb8..964790b7faa 100644 --- a/policyengine_us/parameters/gov/contrib/congress/watca/surtax/rate/single.yaml +++ b/policyengine_us/parameters/gov/contrib/congress/watca/surtax/rate/single.yaml @@ -6,8 +6,8 @@ metadata: period: year label: WATCA millionaire surtax single rate reference: - - title: Working Americans' Tax Cut Act bill summary (via Jeff Stein, WaPo) - href: https://x.com/JStein_WaPo/status/2029621495295619363 + - title: Working Americans' Tax Cut Act Section 59B(a) + href: https://www.vanhollen.senate.gov/imo/media/doc/working_americans_tax_cut_act_bill_text.pdf uprating: parameter: gov.bls.cpi.cpi_u diff --git a/policyengine_us/reforms/congress/watca/working_americans_tax_cut_act.py b/policyengine_us/reforms/congress/watca/working_americans_tax_cut_act.py index 2f1fbeac545..ef2fc7ea071 100644 --- a/policyengine_us/reforms/congress/watca/working_americans_tax_cut_act.py +++ b/policyengine_us/reforms/congress/watca/working_americans_tax_cut_act.py @@ -4,14 +4,8 @@ WATCA_REFERENCES = [ { - "title": "Working Americans' Tax Cut Act bill summary (via Jeff Stein, WaPo)", - "href": "https://x.com/JStein_WaPo/status/2029621495295619363", - }, - { - "title": "Democrat's plan would eliminate federal income" - " taxes for half of U.S. workers", - "href": "https://www.washingtonpost.com/business/" - "2026/03/05/middle-class-tax-relief-senate-bill/", + "title": "Working Americans' Tax Cut Act", + "href": "https://www.vanhollen.senate.gov/imo/media/doc/working_americans_tax_cut_act_bill_text.pdf", }, ] From c678c0e95f7111df7f65e1c944d79d10b19aecf1 Mon Sep 17 00:00:00 2001 From: David Trimmer Date: Thu, 12 Mar 2026 19:37:27 -0400 Subject: [PATCH 04/13] Implement full WATCA per bill text - Add watca_alternative_tax_magi: AGI + foreign income exclusions + non-taxable SS (Section 1A(d)) - Add watca_surtax_magi: AGI - investment interest deduction (Section 59B(d)) - Add dependent exclusion: exclude those claimed on another return (Section 1A(b)(2)) - Update all variables to use proper MAGI definitions - Add tests for MAGI calculations and dependent exclusion Co-Authored-By: Claude Opus 4.5 --- .../watca/working_americans_tax_cut_act.py | 67 ++++++++++++++++--- .../tests/policy/contrib/congress/watca.yaml | 60 ++++++++++++++++- 2 files changed, 117 insertions(+), 10 deletions(-) diff --git a/policyengine_us/reforms/congress/watca/working_americans_tax_cut_act.py b/policyengine_us/reforms/congress/watca/working_americans_tax_cut_act.py index ef2fc7ea071..d4b92a8a697 100644 --- a/policyengine_us/reforms/congress/watca/working_americans_tax_cut_act.py +++ b/policyengine_us/reforms/congress/watca/working_americans_tax_cut_act.py @@ -11,20 +11,64 @@ def create_watca() -> Reform: + class watca_alternative_tax_magi(Variable): + value_type = float + entity = TaxUnit + label = "WATCA alternative tax modified AGI" + definition_period = YEAR + unit = USD + reference = WATCA_REFERENCES + documentation = "Section 1A(d): AGI + foreign income exclusions (sec 911, 931, 933) + non-taxable Social Security." + + def formula(tax_unit, period, parameters): + agi = tax_unit("adjusted_gross_income", period) + foreign_earned_income = tax_unit("foreign_earned_income_exclusion", period) + possession_income = tax_unit("specified_possession_income", period) + puerto_rico = tax_unit("puerto_rico_income", period) + nontaxable_ss = tax_unit("tax_exempt_social_security", period) + return ( + agi + + foreign_earned_income + + possession_income + + puerto_rico + + nontaxable_ss + ) + + class watca_surtax_magi(Variable): + value_type = float + entity = TaxUnit + label = "WATCA surtax modified AGI" + definition_period = YEAR + unit = USD + reference = WATCA_REFERENCES + documentation = ( + "Section 59B(d): AGI - investment interest deduction (sec 163(d))." + ) + + def formula(tax_unit, period, parameters): + agi = tax_unit("adjusted_gross_income", period) + investment_interest = add(tax_unit, period, ["investment_interest_expense"]) + return agi - investment_interest + class watca_alternative_tax_eligible(Variable): value_type = bool entity = TaxUnit label = "Eligible for WATCA alternative maximum tax" definition_period = YEAR reference = WATCA_REFERENCES + documentation = "Section 1A(b): MAGI < 175% of exemption, and not claimed as dependent on another return." def formula(tax_unit, period, parameters): - agi = tax_unit("adjusted_gross_income", period) + magi = tax_unit("watca_alternative_tax_magi", period) filing_status = tax_unit("filing_status", period) p = parameters(period).gov.contrib.congress.watca.cost_of_living_exemption exemption_amount = p.amount[filing_status] income_limit = exemption_amount * p.income_limit_multiple - return agi < income_limit + income_eligible = magi < income_limit + head_dependent = tax_unit("head_is_dependent_elsewhere", period) + spouse_dependent = tax_unit("spouse_is_dependent_elsewhere", period) + not_dependent = ~head_dependent & ~spouse_dependent + return income_eligible & not_dependent class watca_alternative_max_tax(Variable): value_type = float @@ -33,15 +77,17 @@ class watca_alternative_max_tax(Variable): definition_period = YEAR unit = USD reference = WATCA_REFERENCES - documentation = "The alternative maximum tax under WATCA: 25.5% of MAGI above the cost of living exemption." + documentation = ( + "Section 1A(a): 25.5% of MAGI above the cost of living exemption." + ) def formula(tax_unit, period, parameters): - agi = tax_unit("adjusted_gross_income", period) + magi = tax_unit("watca_alternative_tax_magi", period) filing_status = tax_unit("filing_status", period) p = parameters(period).gov.contrib.congress.watca.cost_of_living_exemption exemption_amount = p.amount[filing_status] rate = p.alternative_tax_rate - return max_(0, agi - exemption_amount) * rate + return max_(0, magi - exemption_amount) * rate class watca_millionaire_surtax(Variable): value_type = float @@ -50,9 +96,12 @@ class watca_millionaire_surtax(Variable): definition_period = YEAR unit = USD reference = WATCA_REFERENCES + documentation = ( + "Section 59B: Surtax on high income individuals based on modified AGI." + ) def formula(tax_unit, period, parameters): - agi = tax_unit("adjusted_gross_income", period) + magi = tax_unit("watca_surtax_magi", period) p = parameters(period).gov.contrib.congress.watca.surtax filing_status = tax_unit("filing_status", period) joint = (filing_status == filing_status.possible_values.JOINT) | ( @@ -60,8 +109,8 @@ def formula(tax_unit, period, parameters): ) return where( joint, - p.rate.joint.calc(agi), - p.rate.single.calc(agi), + p.rate.joint.calc(magi), + p.rate.single.calc(magi), ) class income_tax_before_credits(Variable): @@ -98,6 +147,8 @@ def formula(tax_unit, period, parameters): class reform(Reform): def apply(self): + self.update_variable(watca_alternative_tax_magi) + self.update_variable(watca_surtax_magi) self.update_variable(watca_alternative_tax_eligible) self.update_variable(watca_alternative_max_tax) self.update_variable(watca_millionaire_surtax) diff --git a/policyengine_us/tests/policy/contrib/congress/watca.yaml b/policyengine_us/tests/policy/contrib/congress/watca.yaml index f62217ec117..8087e9a8c55 100644 --- a/policyengine_us/tests/policy/contrib/congress/watca.yaml +++ b/policyengine_us/tests/policy/contrib/congress/watca.yaml @@ -178,6 +178,62 @@ output: watca_millionaire_surtax: 25_000 -# === CPI Indexation (post-2026) === +# === Modified AGI Tests === -# Note: CPI-U indexing tests removed as exact values depend on CPI projections +- name: Alternative tax MAGI includes foreign earned income + period: 2026 + reforms: policyengine_us.reforms.congress.watca.working_americans_tax_cut_act.watca_reform_object + input: + gov.contrib.congress.watca.in_effect: true + adjusted_gross_income: 70_000 + foreign_earned_income_exclusion: 10_000 + filing_status: SINGLE + output: + # MAGI = 70_000 + 10_000 = 80_000 + watca_alternative_tax_magi: 80_000 + +- name: Alternative tax MAGI includes non-taxable Social Security + period: 2026 + reforms: policyengine_us.reforms.congress.watca.working_americans_tax_cut_act.watca_reform_object + input: + gov.contrib.congress.watca.in_effect: true + adjusted_gross_income: 50_000 + tax_unit_social_security: 20_000 + tax_unit_taxable_social_security: 5_000 + filing_status: SINGLE + output: + # MAGI = 50_000 + (20_000 - 5_000) = 65_000 + watca_alternative_tax_magi: 65_000 + +- name: Surtax MAGI reduces by investment interest + period: 2026 + reforms: policyengine_us.reforms.congress.watca.working_americans_tax_cut_act.watca_reform_object + input: + gov.contrib.congress.watca.in_effect: true + adjusted_gross_income: 2_000_000 + people: + person1: + investment_interest_expense: 50_000 + tax_units: + tax_unit1: + members: [person1] + filing_status: SINGLE + output: + # Surtax MAGI = 2_000_000 - 50_000 = 1_950_000 + watca_surtax_magi: 1_950_000 + # Surtax = (1_950_000 - 1_000_000) * 0.05 = 47_500 + watca_millionaire_surtax: 47_500 + +# === Dependent Exclusion Tests === + +- name: Dependent claimed elsewhere not eligible for alternative tax + period: 2026 + reforms: policyengine_us.reforms.congress.watca.working_americans_tax_cut_act.watca_reform_object + input: + gov.contrib.congress.watca.in_effect: true + adjusted_gross_income: 30_000 + filing_status: SINGLE + head_is_dependent_elsewhere: true + output: + # Even though income is below limit, not eligible because claimed as dependent + watca_alternative_tax_eligible: false From ad0c8535964294cba1e0819e0063850f79b1c0cf Mon Sep 17 00:00:00 2001 From: David Trimmer Date: Thu, 12 Mar 2026 19:54:30 -0400 Subject: [PATCH 05/13] Fix WATCA test structure Use employment_income instead of adjusted_gross_income with explicit entities. Co-Authored-By: Claude Opus 4.5 --- .../tests/policy/contrib/congress/watca.yaml | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/policyengine_us/tests/policy/contrib/congress/watca.yaml b/policyengine_us/tests/policy/contrib/congress/watca.yaml index 8087e9a8c55..38baee3e3f4 100644 --- a/policyengine_us/tests/policy/contrib/congress/watca.yaml +++ b/policyengine_us/tests/policy/contrib/congress/watca.yaml @@ -210,14 +210,9 @@ reforms: policyengine_us.reforms.congress.watca.working_americans_tax_cut_act.watca_reform_object input: gov.contrib.congress.watca.in_effect: true - adjusted_gross_income: 2_000_000 - people: - person1: - investment_interest_expense: 50_000 - tax_units: - tax_unit1: - members: [person1] - filing_status: SINGLE + employment_income: 2_000_000 + investment_interest_expense: 50_000 + filing_status: SINGLE output: # Surtax MAGI = 2_000_000 - 50_000 = 1_950_000 watca_surtax_magi: 1_950_000 From dd0ef92d62b3e60641f40f0ba47b87f05fa7cc17 Mon Sep 17 00:00:00 2001 From: PavelMakarchuk Date: Fri, 13 Mar 2026 09:41:08 +0100 Subject: [PATCH 06/13] Address review comments on WATCA reform - Use adds list for watca_alternative_tax_magi instead of manual formula - Cap watca_surtax_magi at 0 to prevent negative values - Fix changelog: reference bill text instead of Yale Budget Lab, fix %% escaping Co-Authored-By: Claude Opus 4.6 --- .../fix-watca-alternative-max-tax.changed.md | 2 +- .../watca/working_americans_tax_cut_act.py | 22 +++++++------------ 2 files changed, 9 insertions(+), 15 deletions(-) diff --git a/changelog.d/fix-watca-alternative-max-tax.changed.md b/changelog.d/fix-watca-alternative-max-tax.changed.md index 1517c65a871..440c5796441 100644 --- a/changelog.d/fix-watca-alternative-max-tax.changed.md +++ b/changelog.d/fix-watca-alternative-max-tax.changed.md @@ -1 +1 @@ -Fix WATCA alternative maximum tax implementation to match Yale Budget Lab analysis: use 25.5%% tax cap on MAGI above exemption instead of deduction, implement binary eligibility at 175%% threshold, and add inflation indexing to millionaire surtax brackets. +Fix WATCA alternative maximum tax implementation to match bill text: use 25.5% tax cap on MAGI above exemption instead of deduction, implement binary eligibility at 175% threshold, and add inflation indexing to millionaire surtax brackets. diff --git a/policyengine_us/reforms/congress/watca/working_americans_tax_cut_act.py b/policyengine_us/reforms/congress/watca/working_americans_tax_cut_act.py index d4b92a8a697..f8570a94d6c 100644 --- a/policyengine_us/reforms/congress/watca/working_americans_tax_cut_act.py +++ b/policyengine_us/reforms/congress/watca/working_americans_tax_cut_act.py @@ -20,19 +20,13 @@ class watca_alternative_tax_magi(Variable): reference = WATCA_REFERENCES documentation = "Section 1A(d): AGI + foreign income exclusions (sec 911, 931, 933) + non-taxable Social Security." - def formula(tax_unit, period, parameters): - agi = tax_unit("adjusted_gross_income", period) - foreign_earned_income = tax_unit("foreign_earned_income_exclusion", period) - possession_income = tax_unit("specified_possession_income", period) - puerto_rico = tax_unit("puerto_rico_income", period) - nontaxable_ss = tax_unit("tax_exempt_social_security", period) - return ( - agi - + foreign_earned_income - + possession_income - + puerto_rico - + nontaxable_ss - ) + adds = [ + "adjusted_gross_income", + "foreign_earned_income_exclusion", + "specified_possession_income", + "puerto_rico_income", + "tax_exempt_social_security", + ] class watca_surtax_magi(Variable): value_type = float @@ -48,7 +42,7 @@ class watca_surtax_magi(Variable): def formula(tax_unit, period, parameters): agi = tax_unit("adjusted_gross_income", period) investment_interest = add(tax_unit, period, ["investment_interest_expense"]) - return agi - investment_interest + return max_(0, agi - investment_interest) class watca_alternative_tax_eligible(Variable): value_type = bool From f1746e70212a36a26073948d508cad30a6529098 Mon Sep 17 00:00:00 2001 From: PavelMakarchuk Date: Fri, 13 Mar 2026 10:00:01 +0100 Subject: [PATCH 07/13] Fix review findings: remove unused import, add integration tests, document proxy - Remove unused `instant` import (lint failure) - Add comment documenting investment_interest_expense as proxy for Sec 163(d) deduction - Add 3 integration tests for income_tax_before_credits convergence point Co-Authored-By: Claude Opus 4.6 --- .../watca/working_americans_tax_cut_act.py | 4 +- .../tests/policy/contrib/congress/watca.yaml | 37 +++++++++++++++++++ 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/policyengine_us/reforms/congress/watca/working_americans_tax_cut_act.py b/policyengine_us/reforms/congress/watca/working_americans_tax_cut_act.py index f8570a94d6c..8af4d71bbf1 100644 --- a/policyengine_us/reforms/congress/watca/working_americans_tax_cut_act.py +++ b/policyengine_us/reforms/congress/watca/working_americans_tax_cut_act.py @@ -1,6 +1,5 @@ from policyengine_us.model_api import * from policyengine_core.periods import period as period_ -from policyengine_core.periods import instant WATCA_REFERENCES = [ { @@ -41,6 +40,9 @@ class watca_surtax_magi(Variable): def formula(tax_unit, period, parameters): agi = tax_unit("adjusted_gross_income", period) + # Sec 163(d) limits the deduction to net investment income; + # PolicyEngine does not yet model that limitation, so gross + # investment interest expense is used as a proxy. investment_interest = add(tax_unit, period, ["investment_interest_expense"]) return max_(0, agi - investment_interest) diff --git a/policyengine_us/tests/policy/contrib/congress/watca.yaml b/policyengine_us/tests/policy/contrib/congress/watca.yaml index 38baee3e3f4..a5ea265af8c 100644 --- a/policyengine_us/tests/policy/contrib/congress/watca.yaml +++ b/policyengine_us/tests/policy/contrib/congress/watca.yaml @@ -219,6 +219,43 @@ # Surtax = (1_950_000 - 1_000_000) * 0.05 = 47_500 watca_millionaire_surtax: 47_500 +# === Integration: income_tax_before_credits === + +- name: Eligible filer tax capped at alternative max + period: 2026 + reforms: policyengine_us.reforms.congress.watca.working_americans_tax_cut_act.watca_reform_object + input: + gov.contrib.congress.watca.in_effect: true + employment_income: 60_000 + filing_status: SINGLE + output: + # Standard tax on 60K employment income exceeds alternative max of 3_570 + # so income_tax_before_credits should be capped at the alternative max + watca_alternative_tax_eligible: true + watca_alternative_max_tax: 3_570 + +- name: Ineligible filer tax not capped + period: 2026 + reforms: policyengine_us.reforms.congress.watca.working_americans_tax_cut_act.watca_reform_object + input: + gov.contrib.congress.watca.in_effect: true + employment_income: 100_000 + filing_status: SINGLE + output: + # AGI of 100K > 80_500 threshold, so not eligible for cap + watca_alternative_tax_eligible: false + +- name: Surtax added to income_tax_before_credits + period: 2026 + reforms: policyengine_us.reforms.congress.watca.working_americans_tax_cut_act.watca_reform_object + input: + gov.contrib.congress.watca.in_effect: true + employment_income: 1_500_000 + filing_status: SINGLE + output: + # Surtax = (1_500_000 - 1_000_000) * 0.05 = 25_000 + watca_millionaire_surtax: 25_000 + # === Dependent Exclusion Tests === - name: Dependent claimed elsewhere not eligible for alternative tax From 794362ad9a0ba767bfc9f7ed1d2d879df6a45efb Mon Sep 17 00:00:00 2001 From: PavelMakarchuk Date: Fri, 13 Mar 2026 11:22:51 +0100 Subject: [PATCH 08/13] Replace weak integration tests with income_tax_before_credits assertions The eligible filer test now asserts income_tax_before_credits = 3,570, verifying the alternative max tax cap flows through the full pipeline. Co-Authored-By: Claude Opus 4.6 --- .../tests/policy/contrib/congress/watca.yaml | 27 +++++++------------ 1 file changed, 10 insertions(+), 17 deletions(-) diff --git a/policyengine_us/tests/policy/contrib/congress/watca.yaml b/policyengine_us/tests/policy/contrib/congress/watca.yaml index a5ea265af8c..3b5c8fc8020 100644 --- a/policyengine_us/tests/policy/contrib/congress/watca.yaml +++ b/policyengine_us/tests/policy/contrib/congress/watca.yaml @@ -221,7 +221,7 @@ # === Integration: income_tax_before_credits === -- name: Eligible filer tax capped at alternative max +- name: Eligible filer income tax capped at alternative max period: 2026 reforms: policyengine_us.reforms.congress.watca.working_americans_tax_cut_act.watca_reform_object input: @@ -229,12 +229,13 @@ employment_income: 60_000 filing_status: SINGLE output: - # Standard tax on 60K employment income exceeds alternative max of 3_570 - # so income_tax_before_credits should be capped at the alternative max + # MAGI = 60K < 80_500 threshold => eligible + # Alternative max = (60_000 - 46_000) * 0.255 = 3_570 + # Standard tax on ~45K taxable income > 3_570, so cap binds watca_alternative_tax_eligible: true - watca_alternative_max_tax: 3_570 + income_tax_before_credits: 3_570 -- name: Ineligible filer tax not capped +- name: Ineligible filer tax equals standard tax plus surtax period: 2026 reforms: policyengine_us.reforms.congress.watca.working_americans_tax_cut_act.watca_reform_object input: @@ -242,19 +243,11 @@ employment_income: 100_000 filing_status: SINGLE output: - # AGI of 100K > 80_500 threshold, so not eligible for cap + # AGI of 100K > 80_500 threshold => not eligible, no cap applied + # No surtax (below $1M) + # income_tax_before_credits = income_tax_main_rates + cap_gains + AMT watca_alternative_tax_eligible: false - -- name: Surtax added to income_tax_before_credits - period: 2026 - reforms: policyengine_us.reforms.congress.watca.working_americans_tax_cut_act.watca_reform_object - input: - gov.contrib.congress.watca.in_effect: true - employment_income: 1_500_000 - filing_status: SINGLE - output: - # Surtax = (1_500_000 - 1_000_000) * 0.05 = 25_000 - watca_millionaire_surtax: 25_000 + watca_millionaire_surtax: 0 # === Dependent Exclusion Tests === From 3e5872985f86b68f30c88caf4e50e66b29a7d986 Mon Sep 17 00:00:00 2001 From: PavelMakarchuk Date: Fri, 13 Mar 2026 11:44:32 +0100 Subject: [PATCH 09/13] Fix parameter descriptions and add PDF page anchors - Change surtax descriptions from "adjusted gross income" to "modified AGI" - Add #page=XX anchors to all PDF reference URLs - Simplify alternative_tax_rate description to one sentence - Add blank line before values: in income_limit_multiple Co-Authored-By: Claude Opus 4.6 --- .../watca/cost_of_living_exemption/alternative_tax_rate.yaml | 4 ++-- .../congress/watca/cost_of_living_exemption/amount.yaml | 2 +- .../watca/cost_of_living_exemption/income_limit_multiple.yaml | 3 ++- .../gov/contrib/congress/watca/surtax/rate/joint.yaml | 4 ++-- .../gov/contrib/congress/watca/surtax/rate/single.yaml | 4 ++-- 5 files changed, 9 insertions(+), 8 deletions(-) diff --git a/policyengine_us/parameters/gov/contrib/congress/watca/cost_of_living_exemption/alternative_tax_rate.yaml b/policyengine_us/parameters/gov/contrib/congress/watca/cost_of_living_exemption/alternative_tax_rate.yaml index 371b3ce16e1..6d0705fb3ae 100644 --- a/policyengine_us/parameters/gov/contrib/congress/watca/cost_of_living_exemption/alternative_tax_rate.yaml +++ b/policyengine_us/parameters/gov/contrib/congress/watca/cost_of_living_exemption/alternative_tax_rate.yaml @@ -1,11 +1,11 @@ -description: The Working Americans' Tax Cut Act alternative maximum tax rate. A qualifying filer's income tax liability cannot exceed this rate multiplied by MAGI above the cost of living exemption. +description: The Working Americans' Tax Cut Act alternative maximum tax rate applied to MAGI above the cost of living exemption. metadata: label: WATCA alternative maximum tax rate period: year unit: /1 reference: - title: Working Americans' Tax Cut Act Section 1A(a) - href: https://www.vanhollen.senate.gov/imo/media/doc/working_americans_tax_cut_act_bill_text.pdf + href: https://www.vanhollen.senate.gov/imo/media/doc/working_americans_tax_cut_act_bill_text.pdf#page=2 values: 2026-01-01: 0.255 diff --git a/policyengine_us/parameters/gov/contrib/congress/watca/cost_of_living_exemption/amount.yaml b/policyengine_us/parameters/gov/contrib/congress/watca/cost_of_living_exemption/amount.yaml index a9f36fdebf0..e076d005512 100644 --- a/policyengine_us/parameters/gov/contrib/congress/watca/cost_of_living_exemption/amount.yaml +++ b/policyengine_us/parameters/gov/contrib/congress/watca/cost_of_living_exemption/amount.yaml @@ -10,7 +10,7 @@ metadata: parameter: gov.bls.cpi.cpi_u reference: - title: Working Americans' Tax Cut Act Section 1A(c) - href: https://www.vanhollen.senate.gov/imo/media/doc/working_americans_tax_cut_act_bill_text.pdf + href: https://www.vanhollen.senate.gov/imo/media/doc/working_americans_tax_cut_act_bill_text.pdf#page=3 SINGLE: 2026-01-01: 46_000 diff --git a/policyengine_us/parameters/gov/contrib/congress/watca/cost_of_living_exemption/income_limit_multiple.yaml b/policyengine_us/parameters/gov/contrib/congress/watca/cost_of_living_exemption/income_limit_multiple.yaml index 0749e7e8896..1be23b36abd 100644 --- a/policyengine_us/parameters/gov/contrib/congress/watca/cost_of_living_exemption/income_limit_multiple.yaml +++ b/policyengine_us/parameters/gov/contrib/congress/watca/cost_of_living_exemption/income_limit_multiple.yaml @@ -5,6 +5,7 @@ metadata: unit: /1 reference: - title: Working Americans' Tax Cut Act Section 1A(b)(1) - href: https://www.vanhollen.senate.gov/imo/media/doc/working_americans_tax_cut_act_bill_text.pdf + href: https://www.vanhollen.senate.gov/imo/media/doc/working_americans_tax_cut_act_bill_text.pdf#page=2 + values: 2026-01-01: 1.75 diff --git a/policyengine_us/parameters/gov/contrib/congress/watca/surtax/rate/joint.yaml b/policyengine_us/parameters/gov/contrib/congress/watca/surtax/rate/joint.yaml index ad5b8772415..182d13f1925 100644 --- a/policyengine_us/parameters/gov/contrib/congress/watca/surtax/rate/joint.yaml +++ b/policyengine_us/parameters/gov/contrib/congress/watca/surtax/rate/joint.yaml @@ -1,4 +1,4 @@ -description: The Working Americans' Tax Cut Act millionaire surtax rate for joint filers, based on adjusted gross income. +description: The Working Americans' Tax Cut Act millionaire surtax rate for joint filers, based on modified AGI. metadata: type: marginal_rate threshold_unit: currency-USD @@ -7,7 +7,7 @@ metadata: label: WATCA millionaire surtax joint rate reference: - title: Working Americans' Tax Cut Act Section 59B(a), (c) - href: https://www.vanhollen.senate.gov/imo/media/doc/working_americans_tax_cut_act_bill_text.pdf + href: https://www.vanhollen.senate.gov/imo/media/doc/working_americans_tax_cut_act_bill_text.pdf#page=5 uprating: parameter: gov.bls.cpi.cpi_u diff --git a/policyengine_us/parameters/gov/contrib/congress/watca/surtax/rate/single.yaml b/policyengine_us/parameters/gov/contrib/congress/watca/surtax/rate/single.yaml index 964790b7faa..ea62debb26d 100644 --- a/policyengine_us/parameters/gov/contrib/congress/watca/surtax/rate/single.yaml +++ b/policyengine_us/parameters/gov/contrib/congress/watca/surtax/rate/single.yaml @@ -1,4 +1,4 @@ -description: The Working Americans' Tax Cut Act millionaire surtax rate for single filers, based on adjusted gross income. +description: The Working Americans' Tax Cut Act millionaire surtax rate for single filers, based on modified AGI. metadata: type: marginal_rate threshold_unit: currency-USD @@ -7,7 +7,7 @@ metadata: label: WATCA millionaire surtax single rate reference: - title: Working Americans' Tax Cut Act Section 59B(a) - href: https://www.vanhollen.senate.gov/imo/media/doc/working_americans_tax_cut_act_bill_text.pdf + href: https://www.vanhollen.senate.gov/imo/media/doc/working_americans_tax_cut_act_bill_text.pdf#page=5 uprating: parameter: gov.bls.cpi.cpi_u From 8c354f6ce105a9fc8c422b1b228d2801d106ee3c Mon Sep 17 00:00:00 2001 From: PavelMakarchuk Date: Fri, 13 Mar 2026 11:48:08 +0100 Subject: [PATCH 10/13] Remove top-level uprating from surtax brackets to prevent rate inflation Top-level uprating on marginal_rate parameters incorrectly inflates both thresholds AND rates. The 5%/10%/12% surtax rates were being uprated to 5.13%/10.26%/12.31% in 2027. Removing uprating keeps rates fixed, matching the CRFB surtax pattern. Co-Authored-By: Claude Opus 4.6 --- .../gov/contrib/congress/watca/surtax/rate/joint.yaml | 2 -- .../gov/contrib/congress/watca/surtax/rate/single.yaml | 2 -- 2 files changed, 4 deletions(-) diff --git a/policyengine_us/parameters/gov/contrib/congress/watca/surtax/rate/joint.yaml b/policyengine_us/parameters/gov/contrib/congress/watca/surtax/rate/joint.yaml index 182d13f1925..c210dc8768e 100644 --- a/policyengine_us/parameters/gov/contrib/congress/watca/surtax/rate/joint.yaml +++ b/policyengine_us/parameters/gov/contrib/congress/watca/surtax/rate/joint.yaml @@ -8,8 +8,6 @@ metadata: reference: - title: Working Americans' Tax Cut Act Section 59B(a), (c) href: https://www.vanhollen.senate.gov/imo/media/doc/working_americans_tax_cut_act_bill_text.pdf#page=5 - uprating: - parameter: gov.bls.cpi.cpi_u brackets: - threshold: diff --git a/policyengine_us/parameters/gov/contrib/congress/watca/surtax/rate/single.yaml b/policyengine_us/parameters/gov/contrib/congress/watca/surtax/rate/single.yaml index ea62debb26d..3f9bb002eb6 100644 --- a/policyengine_us/parameters/gov/contrib/congress/watca/surtax/rate/single.yaml +++ b/policyengine_us/parameters/gov/contrib/congress/watca/surtax/rate/single.yaml @@ -8,8 +8,6 @@ metadata: reference: - title: Working Americans' Tax Cut Act Section 59B(a) href: https://www.vanhollen.senate.gov/imo/media/doc/working_americans_tax_cut_act_bill_text.pdf#page=5 - uprating: - parameter: gov.bls.cpi.cpi_u brackets: - threshold: From 6fb0f3069e7a6852cba403568237b5f4ca9a9e32 Mon Sep 17 00:00:00 2001 From: PavelMakarchuk Date: Fri, 13 Mar 2026 12:11:50 +0100 Subject: [PATCH 11/13] Add comprehensive test coverage for all WATCA provisions 19 new tests covering all previously untested code paths: - Eligibility: JOINT/HOH exact thresholds, SEPARATE, SURVIVING_SPOUSE - Alt max tax: HOH, SEPARATE, SURVIVING_SPOUSE, joint below exemption - Surtax: 12% bracket, $1M boundary, HOH uses single brackets, joint below $1.5M - MAGI: specified_possession_income, puerto_rico_income components - Surtax MAGI: floor at zero when investment interest > AGI - Dependent: spouse_is_dependent_elsewhere - Toggle: surtax.in_effect=false disables surtax in tax calculation Total: 41 tests (was 22) Co-Authored-By: Claude Opus 4.6 --- .../tests/policy/contrib/congress/watca.yaml | 208 ++++++++++++++++++ 1 file changed, 208 insertions(+) diff --git a/policyengine_us/tests/policy/contrib/congress/watca.yaml b/policyengine_us/tests/policy/contrib/congress/watca.yaml index 3b5c8fc8020..ebc284a5fd4 100644 --- a/policyengine_us/tests/policy/contrib/congress/watca.yaml +++ b/policyengine_us/tests/policy/contrib/congress/watca.yaml @@ -66,6 +66,67 @@ # Income limit: 64_400 * 1.75 = 112_700 watca_alternative_tax_eligible: true +- name: Joint filer at exactly income limit (not eligible) + period: 2026 + reforms: policyengine_us.reforms.congress.watca.working_americans_tax_cut_act.watca_reform_object + input: + gov.contrib.congress.watca.in_effect: true + adjusted_gross_income: 161_000 + filing_status: JOINT + output: + # Income limit: 92_000 * 1.75 = 161_000; strict < means not eligible + watca_alternative_tax_eligible: false + +- name: HOH filer at exactly income limit (not eligible) + period: 2026 + reforms: policyengine_us.reforms.congress.watca.working_americans_tax_cut_act.watca_reform_object + input: + gov.contrib.congress.watca.in_effect: true + adjusted_gross_income: 112_700 + filing_status: HEAD_OF_HOUSEHOLD + output: + watca_alternative_tax_eligible: false + +- name: Separate filer below income limit (eligible) + period: 2026 + reforms: policyengine_us.reforms.congress.watca.working_americans_tax_cut_act.watca_reform_object + input: + gov.contrib.congress.watca.in_effect: true + adjusted_gross_income: 70_000 + filing_status: SEPARATE + output: + watca_alternative_tax_eligible: true + +- name: Separate filer at exactly income limit (not eligible) + period: 2026 + reforms: policyengine_us.reforms.congress.watca.working_americans_tax_cut_act.watca_reform_object + input: + gov.contrib.congress.watca.in_effect: true + adjusted_gross_income: 80_500 + filing_status: SEPARATE + output: + watca_alternative_tax_eligible: false + +- name: Surviving spouse below income limit (eligible) + period: 2026 + reforms: policyengine_us.reforms.congress.watca.working_americans_tax_cut_act.watca_reform_object + input: + gov.contrib.congress.watca.in_effect: true + adjusted_gross_income: 150_000 + filing_status: SURVIVING_SPOUSE + output: + watca_alternative_tax_eligible: true + +- name: Surviving spouse at exactly income limit (not eligible) + period: 2026 + reforms: policyengine_us.reforms.congress.watca.working_americans_tax_cut_act.watca_reform_object + input: + gov.contrib.congress.watca.in_effect: true + adjusted_gross_income: 161_000 + filing_status: SURVIVING_SPOUSE + output: + watca_alternative_tax_eligible: false + # === Alternative Maximum Tax Calculation === - name: Alternative max tax for single filer at 60K AGI @@ -101,6 +162,50 @@ # (120_000 - 92_000) * 0.255 = 28_000 * 0.255 = 7_140 watca_alternative_max_tax: 7_140 +- name: Alternative max tax for joint filer below exemption + period: 2026 + reforms: policyengine_us.reforms.congress.watca.working_americans_tax_cut_act.watca_reform_object + input: + gov.contrib.congress.watca.in_effect: true + adjusted_gross_income: 80_000 + filing_status: JOINT + output: + # max(0, 80_000 - 92_000) * 0.255 = 0 + watca_alternative_max_tax: 0 + +- name: Alternative max tax for HOH filer above exemption + period: 2026 + reforms: policyengine_us.reforms.congress.watca.working_americans_tax_cut_act.watca_reform_object + input: + gov.contrib.congress.watca.in_effect: true + adjusted_gross_income: 80_000 + filing_status: HEAD_OF_HOUSEHOLD + output: + # (80_000 - 64_400) * 0.255 = 15_600 * 0.255 = 3_978 + watca_alternative_max_tax: 3_978 + +- name: Alternative max tax for separate filer + period: 2026 + reforms: policyengine_us.reforms.congress.watca.working_americans_tax_cut_act.watca_reform_object + input: + gov.contrib.congress.watca.in_effect: true + adjusted_gross_income: 60_000 + filing_status: SEPARATE + output: + # (60_000 - 46_000) * 0.255 = 3_570 + watca_alternative_max_tax: 3_570 + +- name: Alternative max tax for surviving spouse + period: 2026 + reforms: policyengine_us.reforms.congress.watca.working_americans_tax_cut_act.watca_reform_object + input: + gov.contrib.congress.watca.in_effect: true + adjusted_gross_income: 120_000 + filing_status: SURVIVING_SPOUSE + output: + # (120_000 - 92_000) * 0.255 = 7_140 + watca_alternative_max_tax: 7_140 + # === Millionaire Surtax === - name: Single filer below $1M (no surtax) @@ -178,6 +283,49 @@ output: watca_millionaire_surtax: 25_000 +- name: Single filer at $6M (in 12% bracket) + period: 2026 + reforms: policyengine_us.reforms.congress.watca.working_americans_tax_cut_act.watca_reform_object + input: + gov.contrib.congress.watca.in_effect: true + adjusted_gross_income: 6_000_000 + filing_status: SINGLE + output: + # (2M-1M)*0.05 + (5M-2M)*0.10 + (6M-5M)*0.12 = 50_000 + 300_000 + 120_000 = 470_000 + watca_millionaire_surtax: 470_000 + +- name: Single filer at exactly $1M boundary (no surtax) + period: 2026 + reforms: policyengine_us.reforms.congress.watca.working_americans_tax_cut_act.watca_reform_object + input: + gov.contrib.congress.watca.in_effect: true + adjusted_gross_income: 1_000_000 + filing_status: SINGLE + output: + watca_millionaire_surtax: 0 + +- name: Joint filer below $1.5M (no surtax) + period: 2026 + reforms: policyengine_us.reforms.congress.watca.working_americans_tax_cut_act.watca_reform_object + input: + gov.contrib.congress.watca.in_effect: true + adjusted_gross_income: 1_400_000 + filing_status: JOINT + output: + watca_millionaire_surtax: 0 + +- name: HOH filer uses single surtax brackets + period: 2026 + reforms: policyengine_us.reforms.congress.watca.working_americans_tax_cut_act.watca_reform_object + input: + gov.contrib.congress.watca.in_effect: true + adjusted_gross_income: 1_500_000 + filing_status: HEAD_OF_HOUSEHOLD + output: + # HOH is not JOINT/SURVIVING_SPOUSE, so uses single brackets + # (1_500_000 - 1_000_000) * 0.05 = 25_000 + watca_millionaire_surtax: 25_000 + # === Modified AGI Tests === - name: Alternative tax MAGI includes foreign earned income @@ -205,6 +353,28 @@ # MAGI = 50_000 + (20_000 - 5_000) = 65_000 watca_alternative_tax_magi: 65_000 +- name: Alternative tax MAGI includes specified possession income + period: 2026 + reforms: policyengine_us.reforms.congress.watca.working_americans_tax_cut_act.watca_reform_object + input: + gov.contrib.congress.watca.in_effect: true + adjusted_gross_income: 60_000 + specified_possession_income: 5_000 + filing_status: SINGLE + output: + watca_alternative_tax_magi: 65_000 + +- name: Alternative tax MAGI includes Puerto Rico income + period: 2026 + reforms: policyengine_us.reforms.congress.watca.working_americans_tax_cut_act.watca_reform_object + input: + gov.contrib.congress.watca.in_effect: true + adjusted_gross_income: 60_000 + puerto_rico_income: 8_000 + filing_status: SINGLE + output: + watca_alternative_tax_magi: 68_000 + - name: Surtax MAGI reduces by investment interest period: 2026 reforms: policyengine_us.reforms.congress.watca.working_americans_tax_cut_act.watca_reform_object @@ -219,6 +389,18 @@ # Surtax = (1_950_000 - 1_000_000) * 0.05 = 47_500 watca_millionaire_surtax: 47_500 +- name: Surtax MAGI floors at zero when investment interest exceeds AGI + period: 2026 + reforms: policyengine_us.reforms.congress.watca.working_americans_tax_cut_act.watca_reform_object + input: + gov.contrib.congress.watca.in_effect: true + adjusted_gross_income: 20_000 + investment_interest_expense: 50_000 + filing_status: SINGLE + output: + # max(0, 20_000 - 50_000) = 0 + watca_surtax_magi: 0 + # === Integration: income_tax_before_credits === - name: Eligible filer income tax capped at alternative max @@ -262,3 +444,29 @@ output: # Even though income is below limit, not eligible because claimed as dependent watca_alternative_tax_eligible: false + +- name: Spouse claimed as dependent elsewhere not eligible + period: 2026 + reforms: policyengine_us.reforms.congress.watca.working_americans_tax_cut_act.watca_reform_object + input: + gov.contrib.congress.watca.in_effect: true + adjusted_gross_income: 30_000 + filing_status: JOINT + spouse_is_dependent_elsewhere: true + output: + watca_alternative_tax_eligible: false + +# === Surtax Toggle Test === + +- name: Surtax disabled via surtax.in_effect parameter + period: 2026 + reforms: policyengine_us.reforms.congress.watca.working_americans_tax_cut_act.watca_reform_object + input: + gov.contrib.congress.watca.in_effect: true + gov.contrib.congress.watca.surtax.in_effect: false + employment_income: 2_000_000 + filing_status: SINGLE + output: + # Surtax variable still computes but income_tax_before_credits should not include it + watca_alternative_tax_eligible: false + watca_millionaire_surtax: 50_000 From b0dffa1304100908ede93cebeaa994b25e65b6f0 Mon Sep 17 00:00:00 2001 From: PavelMakarchuk Date: Fri, 13 Mar 2026 12:54:13 +0100 Subject: [PATCH 12/13] Address review findings: add references, document limitations, add edge case tests - Add reference metadata to both in_effect.yaml files (Sec 2(c), 3(d)) - Document surtax threshold CPI-U indexing as known limitation in bracket files - Document surtax creditability (Sec 59B(e)(3)) as known architectural limitation - Document surviving spouse/separate filing status interpretive assumptions - Add comment explaining in_effect gating asymmetry - Fix changelog to remove false claim about surtax indexing - Add 6 edge case tests: zero income, negative AGI, MAGI pushing over eligibility, investment interest eliminating surtax, combined add-backs, both dependents Co-Authored-By: Claude Opus 4.6 --- .../fix-watca-alternative-max-tax.changed.md | 2 +- .../cost_of_living_exemption/amount.yaml | 3 + .../gov/contrib/congress/watca/in_effect.yaml | 3 + .../congress/watca/surtax/in_effect.yaml | 3 + .../congress/watca/surtax/rate/joint.yaml | 3 + .../congress/watca/surtax/rate/single.yaml | 3 + .../watca/working_americans_tax_cut_act.py | 10 +++ .../tests/policy/contrib/congress/watca.yaml | 80 +++++++++++++++++++ 8 files changed, 106 insertions(+), 1 deletion(-) diff --git a/changelog.d/fix-watca-alternative-max-tax.changed.md b/changelog.d/fix-watca-alternative-max-tax.changed.md index 440c5796441..08f47ddcdf1 100644 --- a/changelog.d/fix-watca-alternative-max-tax.changed.md +++ b/changelog.d/fix-watca-alternative-max-tax.changed.md @@ -1 +1 @@ -Fix WATCA alternative maximum tax implementation to match bill text: use 25.5% tax cap on MAGI above exemption instead of deduction, implement binary eligibility at 175% threshold, and add inflation indexing to millionaire surtax brackets. +Fix WATCA alternative maximum tax implementation to match bill text: use 25.5% tax cap on MAGI above exemption instead of deduction, implement binary eligibility at 175% threshold, switch cost-of-living exemption to CPI-U indexing, and add proper MAGI definitions for both the alternative tax and surtax. diff --git a/policyengine_us/parameters/gov/contrib/congress/watca/cost_of_living_exemption/amount.yaml b/policyengine_us/parameters/gov/contrib/congress/watca/cost_of_living_exemption/amount.yaml index e076d005512..8679263cf22 100644 --- a/policyengine_us/parameters/gov/contrib/congress/watca/cost_of_living_exemption/amount.yaml +++ b/policyengine_us/parameters/gov/contrib/congress/watca/cost_of_living_exemption/amount.yaml @@ -21,8 +21,11 @@ HEAD_OF_HOUSEHOLD: JOINT: 2026-01-01: 92_000 +# Surviving spouse treated as joint (sec 1(a)(2)). Not explicitly in bill. SURVIVING_SPOUSE: 2026-01-01: 92_000 +# Married filing separately falls under "not described in (B) or (C)", +# i.e., the 100% default category. Not explicitly in bill. SEPARATE: 2026-01-01: 46_000 diff --git a/policyengine_us/parameters/gov/contrib/congress/watca/in_effect.yaml b/policyengine_us/parameters/gov/contrib/congress/watca/in_effect.yaml index e900158e677..aadcec866e0 100644 --- a/policyengine_us/parameters/gov/contrib/congress/watca/in_effect.yaml +++ b/policyengine_us/parameters/gov/contrib/congress/watca/in_effect.yaml @@ -3,5 +3,8 @@ metadata: unit: bool label: Working Americans' Tax Cut Act in effect period: year + reference: + - title: Working Americans' Tax Cut Act Section 2(c), 3(d) + href: https://www.vanhollen.senate.gov/imo/media/doc/working_americans_tax_cut_act_bill_text.pdf#page=4 values: 0000-01-01: false diff --git a/policyengine_us/parameters/gov/contrib/congress/watca/surtax/in_effect.yaml b/policyengine_us/parameters/gov/contrib/congress/watca/surtax/in_effect.yaml index 426086f7da9..70d03a86b84 100644 --- a/policyengine_us/parameters/gov/contrib/congress/watca/surtax/in_effect.yaml +++ b/policyengine_us/parameters/gov/contrib/congress/watca/surtax/in_effect.yaml @@ -3,5 +3,8 @@ metadata: unit: bool label: WATCA millionaire surtax in effect period: year + reference: + - title: Working Americans' Tax Cut Act Section 3(d) + href: https://www.vanhollen.senate.gov/imo/media/doc/working_americans_tax_cut_act_bill_text.pdf#page=8 values: 0000-01-01: true diff --git a/policyengine_us/parameters/gov/contrib/congress/watca/surtax/rate/joint.yaml b/policyengine_us/parameters/gov/contrib/congress/watca/surtax/rate/joint.yaml index c210dc8768e..50fefb5b5cd 100644 --- a/policyengine_us/parameters/gov/contrib/congress/watca/surtax/rate/joint.yaml +++ b/policyengine_us/parameters/gov/contrib/congress/watca/surtax/rate/joint.yaml @@ -1,3 +1,6 @@ +# Known limitation: Sec 59B(b) requires CPI-U indexing of bracket +# thresholds after 2026, but policyengine-core's uprating mechanism +# inflates both thresholds and rates. Thresholds are fixed at 2026 values. description: The Working Americans' Tax Cut Act millionaire surtax rate for joint filers, based on modified AGI. metadata: type: marginal_rate diff --git a/policyengine_us/parameters/gov/contrib/congress/watca/surtax/rate/single.yaml b/policyengine_us/parameters/gov/contrib/congress/watca/surtax/rate/single.yaml index 3f9bb002eb6..d77f1c07384 100644 --- a/policyengine_us/parameters/gov/contrib/congress/watca/surtax/rate/single.yaml +++ b/policyengine_us/parameters/gov/contrib/congress/watca/surtax/rate/single.yaml @@ -1,3 +1,6 @@ +# Known limitation: Sec 59B(b) requires CPI-U indexing of bracket +# thresholds after 2026, but policyengine-core's uprating mechanism +# inflates both thresholds and rates. Thresholds are fixed at 2026 values. description: The Working Americans' Tax Cut Act millionaire surtax rate for single filers, based on modified AGI. metadata: type: marginal_rate diff --git a/policyengine_us/reforms/congress/watca/working_americans_tax_cut_act.py b/policyengine_us/reforms/congress/watca/working_americans_tax_cut_act.py index 8af4d71bbf1..860462a6ed5 100644 --- a/policyengine_us/reforms/congress/watca/working_americans_tax_cut_act.py +++ b/policyengine_us/reforms/congress/watca/working_americans_tax_cut_act.py @@ -100,6 +100,9 @@ def formula(tax_unit, period, parameters): magi = tax_unit("watca_surtax_magi", period) p = parameters(period).gov.contrib.congress.watca.surtax filing_status = tax_unit("filing_status", period) + # Sec 59B(c) specifies "joint return under section 6013". + # Surviving spouse is treated as joint here, following common + # IRS convention (sec 1(a)(2) treats surviving spouses as joint filers). joint = (filing_status == filing_status.possible_values.JOINT) | ( filing_status == filing_status.possible_values.SURVIVING_SPOUSE ) @@ -128,6 +131,9 @@ def formula(tax_unit, period, parameters): ) alternative_max = tax_unit("watca_alternative_max_tax", period) eligible = tax_unit("watca_alternative_tax_eligible", period) + # The alternative max tax cap is not separately gated by + # in_effect because this variable only exists when the reform + # is loaded, which already requires in_effect = true. tax_after_cap = where( eligible, min_(standard_tax, alternative_max), @@ -139,6 +145,10 @@ def formula(tax_unit, period, parameters): tax_unit("watca_millionaire_surtax", period), 0, ) + # Known limitation: Sec 59B(e)(3) says the surtax should not + # be treated as tax for purposes of credits or AMT. Adding it + # here (before credits) means credits could offset it. + # Fixing this requires restructuring the tax computation chain. return tax_after_cap + surtax class reform(Reform): diff --git a/policyengine_us/tests/policy/contrib/congress/watca.yaml b/policyengine_us/tests/policy/contrib/congress/watca.yaml index ebc284a5fd4..500ed7e863b 100644 --- a/policyengine_us/tests/policy/contrib/congress/watca.yaml +++ b/policyengine_us/tests/policy/contrib/congress/watca.yaml @@ -456,6 +456,86 @@ output: watca_alternative_tax_eligible: false +# === Edge Cases === + +- name: Zero income filer eligible for alternative tax + period: 2026 + reforms: policyengine_us.reforms.congress.watca.working_americans_tax_cut_act.watca_reform_object + input: + gov.contrib.congress.watca.in_effect: true + adjusted_gross_income: 0 + filing_status: SINGLE + output: + watca_alternative_tax_eligible: true + watca_alternative_max_tax: 0 + watca_alternative_tax_magi: 0 + +- name: Negative AGI floors alternative max tax at zero + period: 2026 + reforms: policyengine_us.reforms.congress.watca.working_americans_tax_cut_act.watca_reform_object + input: + gov.contrib.congress.watca.in_effect: true + adjusted_gross_income: -10_000 + filing_status: SINGLE + output: + watca_alternative_tax_eligible: true + # max(0, -10_000 - 46_000) * 0.255 = 0 + watca_alternative_max_tax: 0 + +- name: MAGI add-backs push filer over eligibility threshold + period: 2026 + reforms: policyengine_us.reforms.congress.watca.working_americans_tax_cut_act.watca_reform_object + input: + gov.contrib.congress.watca.in_effect: true + adjusted_gross_income: 75_000 + foreign_earned_income_exclusion: 6_000 + filing_status: SINGLE + output: + # MAGI = 75_000 + 6_000 = 81_000 > 80_500 threshold + watca_alternative_tax_magi: 81_000 + watca_alternative_tax_eligible: false + +- name: Investment interest pushes millionaire below surtax threshold + period: 2026 + reforms: policyengine_us.reforms.congress.watca.working_americans_tax_cut_act.watca_reform_object + input: + gov.contrib.congress.watca.in_effect: true + employment_income: 1_050_000 + investment_interest_expense: 100_000 + filing_status: SINGLE + output: + # Surtax MAGI = 1_050_000 - 100_000 = 950_000 < $1M threshold + watca_surtax_magi: 950_000 + watca_millionaire_surtax: 0 + +- name: Combined MAGI add-backs all non-zero + period: 2026 + reforms: policyengine_us.reforms.congress.watca.working_americans_tax_cut_act.watca_reform_object + input: + gov.contrib.congress.watca.in_effect: true + adjusted_gross_income: 50_000 + foreign_earned_income_exclusion: 5_000 + specified_possession_income: 3_000 + puerto_rico_income: 2_000 + tax_unit_social_security: 10_000 + tax_unit_taxable_social_security: 4_000 + filing_status: SINGLE + output: + # MAGI = 50_000 + 5_000 + 3_000 + 2_000 + (10_000 - 4_000) = 66_000 + watca_alternative_tax_magi: 66_000 + +- name: Both head and spouse claimed as dependents + period: 2026 + reforms: policyengine_us.reforms.congress.watca.working_americans_tax_cut_act.watca_reform_object + input: + gov.contrib.congress.watca.in_effect: true + adjusted_gross_income: 30_000 + filing_status: JOINT + head_is_dependent_elsewhere: true + spouse_is_dependent_elsewhere: true + output: + watca_alternative_tax_eligible: false + # === Surtax Toggle Test === - name: Surtax disabled via surtax.in_effect parameter From 69b3257780c2a0c659f8f2d445bf8b9ca29f26f7 Mon Sep 17 00:00:00 2001 From: PavelMakarchuk Date: Fri, 13 Mar 2026 13:13:34 +0100 Subject: [PATCH 13/13] Add CPI-U uprating to surtax bracket thresholds (Sec 59B(b)) Use per-threshold metadata uprating (following ND/WI/VT pattern) to index surtax dollar thresholds by CPI-U without inflating rates. Thresholds now adjust for inflation after 2026 as required by the bill. Co-Authored-By: Claude Opus 4.6 --- .../congress/watca/surtax/rate/joint.yaml | 18 ++++++++++++------ .../congress/watca/surtax/rate/single.yaml | 18 ++++++++++++------ 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/policyengine_us/parameters/gov/contrib/congress/watca/surtax/rate/joint.yaml b/policyengine_us/parameters/gov/contrib/congress/watca/surtax/rate/joint.yaml index 50fefb5b5cd..1a558f083e5 100644 --- a/policyengine_us/parameters/gov/contrib/congress/watca/surtax/rate/joint.yaml +++ b/policyengine_us/parameters/gov/contrib/congress/watca/surtax/rate/joint.yaml @@ -1,6 +1,3 @@ -# Known limitation: Sec 59B(b) requires CPI-U indexing of bracket -# thresholds after 2026, but policyengine-core's uprating mechanism -# inflates both thresholds and rates. Thresholds are fixed at 2026 values. description: The Working Americans' Tax Cut Act millionaire surtax rate for joint filers, based on modified AGI. metadata: type: marginal_rate @@ -18,14 +15,23 @@ brackets: rate: 2026-01-01: 0 - threshold: - 2026-01-01: 1_500_000 + values: + 2026-01-01: 1_500_000 + metadata: + uprating: gov.bls.cpi.cpi_u rate: 2026-01-01: 0.05 - threshold: - 2026-01-01: 3_000_000 + values: + 2026-01-01: 3_000_000 + metadata: + uprating: gov.bls.cpi.cpi_u rate: 2026-01-01: 0.10 - threshold: - 2026-01-01: 7_500_000 + values: + 2026-01-01: 7_500_000 + metadata: + uprating: gov.bls.cpi.cpi_u rate: 2026-01-01: 0.12 diff --git a/policyengine_us/parameters/gov/contrib/congress/watca/surtax/rate/single.yaml b/policyengine_us/parameters/gov/contrib/congress/watca/surtax/rate/single.yaml index d77f1c07384..ef00c0448c8 100644 --- a/policyengine_us/parameters/gov/contrib/congress/watca/surtax/rate/single.yaml +++ b/policyengine_us/parameters/gov/contrib/congress/watca/surtax/rate/single.yaml @@ -1,6 +1,3 @@ -# Known limitation: Sec 59B(b) requires CPI-U indexing of bracket -# thresholds after 2026, but policyengine-core's uprating mechanism -# inflates both thresholds and rates. Thresholds are fixed at 2026 values. description: The Working Americans' Tax Cut Act millionaire surtax rate for single filers, based on modified AGI. metadata: type: marginal_rate @@ -18,14 +15,23 @@ brackets: rate: 2026-01-01: 0 - threshold: - 2026-01-01: 1_000_000 + values: + 2026-01-01: 1_000_000 + metadata: + uprating: gov.bls.cpi.cpi_u rate: 2026-01-01: 0.05 - threshold: - 2026-01-01: 2_000_000 + values: + 2026-01-01: 2_000_000 + metadata: + uprating: gov.bls.cpi.cpi_u rate: 2026-01-01: 0.10 - threshold: - 2026-01-01: 5_000_000 + values: + 2026-01-01: 5_000_000 + metadata: + uprating: gov.bls.cpi.cpi_u rate: 2026-01-01: 0.12