Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/hub-crfb-surtax-pretax-premiums-1010.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Correct CRFB expanded surtax premium addbacks to use employer-paid coverage and employee pretax premiums without double-counting after-tax employee premiums.
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,13 @@ values:
- foreign_earned_income_exclusion
# Tax-free bond interest
- tax_exempt_interest_income
# Employer-provided health insurance (premiums)
- health_insurance_premiums
# Employer-provided health insurance: employer-paid premiums and the
# employee's pretax payroll contribution. Including the employee's
# pretax contribution is inferred from the broad income definition and
# IRS Rev. Rul. 2002-3's treatment of salary-reduction premiums as
# employer-provided coverage; after-tax premiums use income in AGI.
- employer_sponsored_insurance_premiums
- pre_tax_health_insurance_premiums

metadata:
unit: list
Expand All @@ -37,3 +42,5 @@ metadata:
reference:
- title: "Committee for a Responsible Federal Budget, Break Glass: A Plan for the Next Economic Shock, footnote 2"
href: https://www.crfb.org/papers/break-glass-plan-next-economic-shock
- title: "IRS Revenue Ruling 2002-3, salary-reduction health insurance premiums #page 1-2"
href: https://www.irs.gov/pub/irs-drop/rr-02-3.pdf#page=1
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@
expanded base adds amounts to this return's AGI, so it counts the head's and
spouse's own contributions, exclusions and deductions too.

Each case runs every tax unit twice in one vectorized simulation: once with
the dependents' amounts as drawn and once with them set to zero. For every
tax unit:
Dependent-exclusion cases run every tax unit twice in one vectorized simulation:
once with the dependents' amounts as drawn and once with them set to zero. For
every tax unit:

1. A dependent's wages, self-employment income, retirement contributions,
tax-exempt interest, Social Security benefits and health insurance
Expand All @@ -29,6 +29,9 @@
3. Differential: `ctc_social_security_tax`, the Puerto Rico equivalent,
`additional_medicare_tax` and the surtax equal an independent numpy
calculation over the head and spouse.
4. With AGI held fixed, employer-paid and qualified salary-reduction premiums
add the same amount to the surtax base; after-tax premiums add nothing.
Generated payment-method cases cover both surtax thresholds.

Invariant 2 leaves out three kinds of dependent amount, which invariant 1
checks with AGI and its taxable Social Security given as inputs:
Expand Down Expand Up @@ -67,12 +70,15 @@
"traditional_403b_contributions_desired",
"social_security_dependents",
]
# Dependent amounts that reach the filer's return elsewhere: see the module
# docstring.
# Additional dependent amounts checked with AGI held fixed, including all
# three premium payment methods. Some reach the return elsewhere: see the
# module docstring.
OTHER_INPUTS = [
"self_employment_income",
"tax_exempt_interest_income",
"health_insurance_premiums",
"employer_sponsored_insurance_premiums",
"pre_tax_health_insurance_premiums",
]
PERSON_PAYROLL_TAXES = [
"employee_social_security_tax",
Expand All @@ -85,7 +91,8 @@
"traditional_403b_contributions",
"student_loan_interest_ald",
"tax_exempt_interest_income",
"health_insurance_premiums",
"employer_sponsored_insurance_premiums",
"pre_tax_health_insurance_premiums",
]

money = st.integers(0, 60_000).map(float)
Expand Down Expand Up @@ -116,6 +123,12 @@ def filer(draw):
"traditional_401k_contributions_desired": draw(maybe(money)),
"tax_exempt_interest_income": draw(maybe(money)),
"health_insurance_premiums": draw(maybe(st.integers(1, 9_000).map(float))),
"employer_sponsored_insurance_premiums": draw(
maybe(st.integers(1, 9_000).map(float))
),
"pre_tax_health_insurance_premiums": draw(
maybe(st.integers(1, 9_000).map(float))
),
}


Expand All @@ -140,6 +153,12 @@ def dependent(draw, *, every_amount):
person["health_insurance_premiums"] = draw(
maybe(st.integers(1, 9_000).map(float))
)
person["employer_sponsored_insurance_premiums"] = draw(
maybe(st.integers(1, 9_000).map(float))
)
person["pre_tax_health_insurance_premiums"] = draw(
maybe(st.integers(1, 9_000).map(float))
)
return person


Expand Down Expand Up @@ -394,6 +413,82 @@ def test_dependents_amounts_never_change_payroll_taxes_or_surtax_additions(units
check_every_amount_with_agi_given(units, year)


def test_after_tax_premiums_do_not_increase_surtax_with_agi_given():
year = 2026
unit = {
"head": {"age": 40, "health_insurance_premiums": 4_000.0},
"spouse": None,
"dependents": [{"age": 17, "employment_income": 0.0}],
"excess_payroll_tax_withheld": 0.0,
"adjusted_gross_income": 150_000.0,
}
situation, filers, unit_index = build([unit], year)
situation["tax_units"]["tax_unit_0"]["filing_status"] = {year: "SINGLE"}
simulation = Simulation(situation=situation, tax_benefit_system=surtax_system())
# After-tax premiums are already financed from the supplied AGI.
# The stale oracle added $4,000 again, incorrectly requiring $540.
np.testing.assert_allclose(
calculate(simulation, "agi_surtax", year), [500.0], atol=TOLERANCE
)
assert_surtax_matches_reference(simulation, filers, unit_index, year)


@SETTINGS
@given(
amount=st.integers(10, 60_000).map(float),
dependent_premiums=st.tuples(
st.integers(10, 60_000).map(float),
st.integers(10, 60_000).map(float),
st.integers(10, 60_000).map(float),
),
married=st.booleans(),
year=st.sampled_from(YEARS),
)
def test_surtax_premium_payment_methods_with_agi_given(
amount, dependent_premiums, married, year
):
system = surtax_system()
p = system.parameters(year).gov.contrib.crfb.surtax
scale = p.rate.joint if married else p.rate.single
# Every draw covers a below-threshold base, crossing the first threshold,
# the 1% bracket and crossing the top threshold.
agi = np.array(
[
0.0,
scale.thresholds[1] - amount / 2,
scale.thresholds[1] + amount,
scale.thresholds[2] - amount / 2,
]
)
methods = (
"employer_sponsored_insurance_premiums",
"pre_tax_health_insurance_premiums",
"health_insurance_premiums",
)
units = [
{
"head": {"age": 40, method: amount},
"spouse": {"age": 40} if married else None,
"dependents": [{"age": 17, **dict(zip(methods, dependent_premiums))}],
"excess_payroll_tax_withheld": 0.0,
"adjusted_gross_income": float(income),
}
for method in methods
for income in agi
]
situation, filers, unit_index = build(units, year)
simulation = Simulation(situation=situation, tax_benefit_system=system)
employer_paid, salary_reduction, after_tax = np.split(
calculate(simulation, "agi_surtax", year), 3
)
np.testing.assert_allclose(employer_paid, salary_reduction, atol=TOLERANCE)
# With AGI fixed, switching to after-tax payments lowers the expanded
# base by exactly the moved amount. Apply the scale across both brackets.
np.testing.assert_allclose(employer_paid, scale.calc(agi + amount), atol=TOLERANCE)
np.testing.assert_allclose(after_tax, scale.calc(agi), atol=TOLERANCE)
assert_surtax_matches_reference(simulation, filers, unit_index, year)


def low_wage_family(married, dependent_wages):
"""Three qualifying children, the EITC barred by interest income and
wages low enough that payroll taxes decide the refundable CTC."""
Expand Down
56 changes: 56 additions & 0 deletions policyengine_us/tests/policy/contrib/crfb/agi_surtax.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,62 @@
output:
agi_surtax: 500

- name: Expanded base restores employee pretax premiums and adds employer-paid premiums.
period: 2026
reforms: policyengine_us.reforms.crfb.agi_surtax.agi_surtax_reform_object
input:
gov.contrib.crfb.surtax.in_effect: true
gov.contrib.crfb.surtax.increased_base.in_effect: true
employment_income: 150_000
health_insurance_premiums: 4_000
pre_tax_health_insurance_premiums: 6_000
employer_sponsored_insurance_premiums: 10_000
filing_status: SINGLE
output:
adjusted_gross_income: 144_000
# 144,000 AGI + 6,000 pretax + 10,000 employer premiums = 160,000.
agi_surtax: 600

- name: Expanded base is unchanged when employee premiums are paid entirely after tax.
period: 2026
reforms: policyengine_us.reforms.crfb.agi_surtax.agi_surtax_reform_object
input:
gov.contrib.crfb.surtax.in_effect: true
gov.contrib.crfb.surtax.increased_base.in_effect: true
employment_income: 150_000
health_insurance_premiums: 10_000
employer_sponsored_insurance_premiums: 10_000
filing_status: SINGLE
output:
adjusted_gross_income: 150_000
# 150,000 AGI + 10,000 employer premiums = the same 160,000 base.
agi_surtax: 600

- name: Expanded base does not add after-tax employee premiums to supplied AGI.
period: 2026
reforms: policyengine_us.reforms.crfb.agi_surtax.agi_surtax_reform_object
input:
gov.contrib.crfb.surtax.in_effect: true
gov.contrib.crfb.surtax.increased_base.in_effect: true
adjusted_gross_income: 110_000
health_insurance_premiums: 20_000
filing_status: SINGLE
output:
agi_surtax: 100

- name: Expanded base restores pretax employee premiums excluded from supplied AGI.
period: 2026
reforms: policyengine_us.reforms.crfb.agi_surtax.agi_surtax_reform_object
input:
gov.contrib.crfb.surtax.in_effect: true
gov.contrib.crfb.surtax.increased_base.in_effect: true
adjusted_gross_income: 110_000
pre_tax_health_insurance_premiums: 20_000
filing_status: SINGLE
output:
agi_surtax: 300
income_tax_before_credits: 15_670

- name: Joint couple below threshold
period: 2026
reforms: policyengine_us.reforms.crfb.agi_surtax.agi_surtax_reform_object
Expand Down