diff --git a/changelog.d/ptc-refundable-credit-net-income.changed.md b/changelog.d/ptc-refundable-credit-net-income.changed.md new file mode 100644 index 00000000000..1932be01e5a --- /dev/null +++ b/changelog.d/ptc-refundable-credit-net-income.changed.md @@ -0,0 +1 @@ +Count the federal premium tax credit in household net income and household tax as a refundable tax credit (household_refundable_tax_credits), whether or not health benefits count in net income, so changes to it reach net income and the budget. income_tax and SPM resources are unchanged. diff --git a/policyengine_us/parameters/gov/household/household_health_benefits.yaml b/policyengine_us/parameters/gov/household/household_health_benefits.yaml index ba051538c04..6d9c4a2c7f4 100644 --- a/policyengine_us/parameters/gov/household/household_health_benefits.yaml +++ b/policyengine_us/parameters/gov/household/household_health_benefits.yaml @@ -4,7 +4,8 @@ values: - medicaid_cost - msp_cost - chip - - assigned_aca_ptc + # The federal premium tax credit counts as a refundable tax credit + # (household_refundable_tax_credits) whether or not health benefits do. - assigned_ca_premium_subsidy - basic_health_program - co_omnisalud diff --git a/policyengine_us/parameters/gov/household/household_health_costs.yaml b/policyengine_us/parameters/gov/household/household_health_costs.yaml index e19592dc88b..6eb5cd92cbc 100644 --- a/policyengine_us/parameters/gov/household/household_health_costs.yaml +++ b/policyengine_us/parameters/gov/household/household_health_costs.yaml @@ -2,7 +2,10 @@ description: The government counts these sources as household health out-of-pock values: 2022-01-01: - chip_premium - - marketplace_net_premium + # The whole premium: household net income counts the premium tax credit + # that pays part of it as a refundable credit, so the net premium would + # count the credit twice. + - selected_marketplace_plan_premium_proxy metadata: unit: list diff --git a/policyengine_us/tests/core/test_behavioral_response_measurements.py b/policyengine_us/tests/core/test_behavioral_response_measurements.py index 7ef5166ee81..55814bca7fc 100644 --- a/policyengine_us/tests/core/test_behavioral_response_measurements.py +++ b/policyengine_us/tests/core/test_behavioral_response_measurements.py @@ -649,7 +649,8 @@ def test_semi_elasticity_capital_gains_response_end_to_end_from_a_zero_rate(): """The semi-elasticity form through the model, from a 0% baseline rate. A Texas filer whose $30,000 of gains sit in the 0% bracket faces a new 10% - rate there. The semi-elasticity response depends only on the change in the + rate there. The filer has employer coverage, so no premium tax credit + phases out with the gains. The semi-elasticity response depends only on the change in the measured rate, so it is about 29% at 3.4, where the log-rate form's 0.001 floor would remove about 96% (test_capital_gains_response_forms.py covers the forms' arithmetic without a simulation). @@ -659,7 +660,13 @@ def test_semi_elasticity_capital_gains_response_end_to_end_from_a_zero_rate(): year = "2026" gains = 30_000 situation = { - "people": {"p": {"age": {year: 50}, "long_term_capital_gains": {year: gains}}}, + "people": { + "p": { + "age": {year: 50}, + "long_term_capital_gains": {year: gains}, + "has_esi": {year: True}, + } + }, "tax_units": {"tu": {"members": ["p"]}}, "families": {"f": {"members": ["p"]}}, "spm_units": {"s": {"members": ["p"]}}, diff --git a/policyengine_us/tests/core/test_estate_income_invariants.py b/policyengine_us/tests/core/test_estate_income_invariants.py index 4e458c5a79d..271584b08d0 100644 --- a/policyengine_us/tests/core/test_estate_income_invariants.py +++ b/policyengine_us/tests/core/test_estate_income_invariants.py @@ -480,7 +480,8 @@ def test_estate_income_raises_household_net_income_by_its_after_tax_amount( A single Texas filer with $50,000 of wages in 2024 receives $10,000 of estate income, which stays in the 12% bracket. Texas has no income tax and estate income carries no payroll tax, so household net income rises - by the $10,000 less the federal income tax increase. + by the $10,000 less the federal income tax increase. The filer has + employer coverage, so no premium tax credit falls as income rises. """ def net_income(estate): @@ -488,6 +489,7 @@ def net_income(estate): "age": {2024: 40}, "employment_income": {2024: 50_000}, "estate_income": {2024: estate}, + "has_esi": {2024: True}, } if qualified is not None: head["estate_income_would_be_qualified"] = {2024: qualified} diff --git a/policyengine_us/tests/core/test_marginal_tax_rate_coverage.py b/policyengine_us/tests/core/test_marginal_tax_rate_coverage.py index a15eb5199b7..da71909814a 100644 --- a/policyengine_us/tests/core/test_marginal_tax_rate_coverage.py +++ b/policyengine_us/tests/core/test_marginal_tax_rate_coverage.py @@ -179,6 +179,7 @@ def test_raising_the_cap_only_adds_people(mtr_by_cap, variable): # wages less the $14,600 standard deduction ($75,400, $45,400, $15,400), so a # $1,000 raise is taxed at 22%, 12% and 12%, plus 7.65% employee payroll tax. # Nobody crosses a bracket or payroll threshold, and Texas has no income tax. +# Each has employer coverage, so no premium tax credit phases out with a raise. ROOMMATE_WAGES = (90_000, 60_000, 30_000) ROOMMATE_RATES = { "marginal_tax_rate": (0.2965, 0.1965, 0.1965), @@ -193,7 +194,11 @@ def test_roommates_match_hand_computed_rates(cap): names = [f"roommate{i}" for i in range(len(ROOMMATE_WAGES))] situation = { "people": { - name: {"age": {YEAR: 40 - 5 * i}, "employment_income": {YEAR: wages}} + name: { + "age": {YEAR: 40 - 5 * i}, + "employment_income": {YEAR: wages}, + "has_esi": {YEAR: True}, + } for i, (name, wages) in enumerate(zip(names, ROOMMATE_WAGES)) }, "tax_units": {f"{name}_tax_unit": {"members": [name]} for name in names}, diff --git a/policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/military_retirement_pay.yaml b/policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/military_retirement_pay.yaml index befdc3853b7..e8f58b1a6cc 100644 --- a/policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/military_retirement_pay.yaml +++ b/policyengine_us/tests/policy/baseline/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/military_retirement_pay.yaml @@ -7,6 +7,7 @@ - name: Case 1, Military retirement pay flows into federal gross income, AGI, and taxable income. period: 2024 + absolute_error_margin: 0.01 input: people: person1: @@ -28,7 +29,10 @@ household_market_income: 30_000 snap_unearned_income: 30_000 ssi_unearned_income: 30_000 - household_net_income: 28_384 + # With no other coverage, a 2024 marketplace credit under the enhanced + # schedule, which household net income counts as a refundable credit. + premium_tax_credit_in_force: 10_323.72 + household_net_income: 28_384 + 10_323.72 - name: Case 2, Military retirement pay survivors flows into federal gross income, AGI, and taxable income. period: 2024 @@ -56,6 +60,7 @@ - name: Case 3, Military retirement pay adds with employment income. period: 2024 + absolute_error_margin: 0.01 input: people: person1: @@ -77,7 +82,10 @@ taxable_income: 70_000 - 14_600 market_income: 70_000 household_market_income: 70_000 - household_net_income: 59_699 + # The enhanced schedule's credit above 400% FPL: the benchmark premium + # minus 8.5% of income. + premium_tax_credit_in_force: 2_846.77 + household_net_income: 59_699 + 2_846.77 - name: Case 4, Dependent military retirement pay is excluded from the tax unit filer gross income. period: 2024 diff --git a/policyengine_us/tests/policy/baseline/gov/local/tax/local_tax_rollup.yaml b/policyengine_us/tests/policy/baseline/gov/local/tax/local_tax_rollup.yaml index c3baf64f188..271e8be6dac 100644 --- a/policyengine_us/tests/policy/baseline/gov/local/tax/local_tax_rollup.yaml +++ b/policyengine_us/tests/policy/baseline/gov/local/tax/local_tax_rollup.yaml @@ -91,6 +91,7 @@ - name: Indiana county tax flows from earnings to household and SPM totals period: 2021 + absolute_error_margin: 0.01 input: state_code: IN county_str: BOONE_COUNTY_IN @@ -101,8 +102,10 @@ local_income_tax: 735 local_income_tax_before_refundable_credits: 735 local_tax: 735 - household_tax: 9_037.7 - household_net_income: 40_962.3 + # The 2021 premium tax credit lowers household tax, not the SPM's taxes. + premium_tax_credit_in_force: 1_275.24 + household_tax: 9_037.7 - 1_275.24 + household_net_income: 40_962.3 + 1_275.24 spm_unit_local_tax: 735 spm_unit_taxes: 9_037.7 spm_unit_net_income: 36_824.8 diff --git a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/integration.yaml b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/integration.yaml index 437b949c97f..e11bb67c787 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/integration.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mt/tax/income/integration.yaml @@ -698,14 +698,18 @@ # 2,339.51 - 1,250 = 1,089.51. state_income_tax: 1_089.51 household_refundable_state_tax_credits: 1_250 - # Federal 2021 recovery rebate credit 1,400 + MT rebate 1,250 = 2,650. - household_refundable_tax_credits: 2_650 + # With no other coverage, a 2021 marketplace credit under the enhanced + # schedule. + premium_tax_credit_in_force: 2_272.79 + # Federal 2021 recovery rebate credit 1,400 + MT rebate 1,250 + premium + # tax credit 2,272.79 = 4,922.79. + household_refundable_tax_credits: 4_922.79 # Federal income tax 4,295 (TI = 50,000 - 12,550 = 37,450; # 10% x 9,950 + 12% x 27,500 = 995 + 3,300) + employee payroll tax # 3,825 (7.65% x 50,000) + MT line 20 2,339.51 = 10,459.51. household_tax_before_refundable_credits: 10_459.51 - # 50,000 + 2,650 - 10,459.51 = 42,190.49 (40,940.49 without the rebate). - household_net_income: 42_190.49 + # 50,000 + 4,922.79 - 10,459.51 = 44,463.28 (43,213.28 without the rebate). + household_net_income: 44_463.28 # SPM resources rise by the same 1,250 through spm_unit_state_tax: # 36,802.99 without the rebate + 1,250 = 38,052.99. spm_unit_net_income: 38_052.99 @@ -744,14 +748,19 @@ # 2,518.22 - 2,500 = 18.22. state_income_tax: 18.22 household_refundable_state_tax_credits: 2_500 - # Federal 2021 recovery rebate credit 2 x 1,400 + MT rebate 2,500 = 5,300. - household_refundable_tax_credits: 5_300 + # With no other coverage, a 2021 marketplace credit for both under the + # enhanced schedule. + premium_tax_credit_in_force: 8_500.58 + # Federal 2021 recovery rebate credit 2 x 1,400 + MT rebate 2,500 + + # premium tax credit 8,500.58 = 13,800.58. + household_refundable_tax_credits: 13_800.58 # Federal income tax 3,790 (TI = 60,000 - 25,100 = 34,900; # 10% x 19,900 + 12% x 15,000 = 1,990 + 1,800) + employee payroll tax # 4,590 (7.65% x 60,000) + MT line 20 2,518.22 = 10,898.22. household_tax_before_refundable_credits: 10_898.22 - # 60,000 + 5,300 - 10,898.22 = 54,401.78 (51,901.78 without the rebate). - household_net_income: 54_401.78 + # 60,000 + 13,800.58 - 10,898.22 = 62,902.36 (60,402.36 without the + # rebate). + household_net_income: 62_902.36 # SPM resources rise by the same 2,500 through spm_unit_state_tax: # 47,581.78 without the rebate + 2,500 = 50,081.78. spm_unit_net_income: 50_081.78 diff --git a/policyengine_us/tests/policy/baseline/household/household_health_benefits.yaml b/policyengine_us/tests/policy/baseline/household/household_health_benefits.yaml index c02d64382ee..27e54bfb1c6 100644 --- a/policyengine_us/tests/policy/baseline/household/household_health_benefits.yaml +++ b/policyengine_us/tests/policy/baseline/household/household_health_benefits.yaml @@ -12,7 +12,7 @@ household_health_benefits: 0 household_benefits: 0 -- name: Case 2, flag on includes annual canonical ACA PTC and Medicaid value. +- name: Case 2, flag on includes the Medicaid value; the ACA PTC counts as a refundable tax credit instead. period: 2026 absolute_error_margin: 0.01 input: @@ -21,8 +21,8 @@ medicaid_cost: 500 snap: 0 output: - household_health_benefits: 1_500 - household_benefits: 1_500 + household_health_benefits: 500 + household_benefits: 500 - name: Case 3, flag on includes annual MSP and CHIP values. period: 2026 @@ -35,8 +35,8 @@ chip: 300 snap: 0 output: - household_health_benefits: 4_234.8 - household_benefits: 4_234.8 + household_health_benefits: 3_234.8 + household_benefits: 3_234.8 - name: Case 4, legacy wrappers do not double count canonical health values. period: 2026 @@ -52,8 +52,8 @@ msp_cost: 2_434.8 snap: 0 output: - household_health_benefits: 4_234.8 - household_benefits: 4_234.8 + household_health_benefits: 3_234.8 + household_benefits: 3_234.8 - name: Case 5, flag on includes forced Maryland Premium Assistance value. # md_premium_assistance is registered in the household_health_benefits list diff --git a/policyengine_us/tests/policy/baseline/household/household_net_income_including_health_benefits.yaml b/policyengine_us/tests/policy/baseline/household/household_net_income_including_health_benefits.yaml index 3106c50c673..462e56f3035 100644 --- a/policyengine_us/tests/policy/baseline/household/household_net_income_including_health_benefits.yaml +++ b/policyengine_us/tests/policy/baseline/household/household_net_income_including_health_benefits.yaml @@ -21,3 +21,12 @@ healthcare_benefit_value: 2_000 output: household_net_income_including_health_benefits: 4_000 + +- name: The premium tax credit, in both net income and health benefits, counts once + period: 2026 + input: + household_net_income: 3_000 + healthcare_benefit_value: 1_500 + premium_tax_credit_in_force: 1_000 + output: + household_net_income_including_health_benefits: 3_000 + 1_500 - 1_000 diff --git a/policyengine_us/tests/policy/baseline/household/income/household/household_health_costs.yaml b/policyengine_us/tests/policy/baseline/household/income/household/household_health_costs.yaml index 32a7b5f3bcc..7f51040fea0 100644 --- a/policyengine_us/tests/policy/baseline/household/income/household/household_health_costs.yaml +++ b/policyengine_us/tests/policy/baseline/household/income/household/household_health_costs.yaml @@ -14,16 +14,27 @@ output: household_health_costs: 360 -- name: Household health costs include Marketplace net premium when health benefits are included in net income. +- name: Household health costs include the whole Marketplace premium when health benefits are included in net income. period: 2024 input: gov.simulation.include_health_benefits_in_net_income: true chip_premium: 360 - marketplace_net_premium: 2_000 + selected_marketplace_plan_premium_proxy: 2_000 output: household_health_costs: 2_360 -- name: Household health costs include computed unsubsidized Marketplace net premium. +- name: The premium tax credit doesn't lower household health costs, since net income counts it as a refundable credit. + period: 2026 + input: + gov.simulation.include_health_benefits_in_net_income: true + chip_premium: 0 + selected_marketplace_plan_premium_proxy: 5_000 + aca_ptc: 1_000 + output: + marketplace_net_premium: 4_000 + household_health_costs: 5_000 + +- name: Household health costs include a computed unsubsidized Marketplace premium. absolute_error_margin: 0.01 period: 2026 input: diff --git a/policyengine_us/tests/policy/baseline/household/income/household/household_net_income.yaml b/policyengine_us/tests/policy/baseline/household/income/household/household_net_income.yaml index ea555b85a89..611ae4f1c87 100644 --- a/policyengine_us/tests/policy/baseline/household/income/household/household_net_income.yaml +++ b/policyengine_us/tests/policy/baseline/household/income/household/household_net_income.yaml @@ -184,3 +184,104 @@ output: spm_unit_net_income: 500_000 + 1_000 + 200 household_net_income: 500_000 + 1_000 + 200 + +- name: Household net income counts the premium tax credit as a refundable tax credit. + period: 2026 + input: + household_market_income: 30_000 + household_benefits: 0 + income_tax_refundable_credits_in_force: 0 + household_refundable_state_tax_credits: 0 + household_refundable_local_tax_credits: 0 + used_aca_ptc: 4_000 + household_tax_before_refundable_credits: 2_000 + household_health_costs: 0 + output: + household_tax: 2_000 - 4_000 + household_net_income: 30_000 + 4_000 - 2_000 + +# With health benefits counted, health costs subtract the whole Marketplace +# premium, so a credit that is $1,000 larger raises net income by $1,000, not +# $2,000 (once as a refundable credit and again through a smaller net premium). +- name: With health benefits counted, a $1,000 premium tax credit against a $5,000 premium. + period: 2026 + input: + gov.simulation.include_health_benefits_in_net_income: true + household_market_income: 30_000 + household_benefits: 0 + income_tax_refundable_credits_in_force: 0 + household_refundable_state_tax_credits: 0 + household_refundable_local_tax_credits: 0 + household_tax_before_refundable_credits: 2_000 + chip_premium: 0 + selected_marketplace_plan_premium_proxy: 5_000 + aca_ptc: 1_000 + output: + premium_tax_credit_in_force: 1_000 + household_health_costs: 5_000 + household_net_income: 30_000 + 1_000 - 2_000 - 5_000 + +- name: With health benefits counted, a $2,000 premium tax credit against the same premium. + period: 2026 + input: + gov.simulation.include_health_benefits_in_net_income: true + household_market_income: 30_000 + household_benefits: 0 + income_tax_refundable_credits_in_force: 0 + household_refundable_state_tax_credits: 0 + household_refundable_local_tax_credits: 0 + household_tax_before_refundable_credits: 2_000 + chip_premium: 0 + selected_marketplace_plan_premium_proxy: 5_000 + aca_ptc: 2_000 + output: + premium_tax_credit_in_force: 2_000 + household_health_costs: 5_000 + household_net_income: 30_000 + 2_000 - 2_000 - 5_000 + +# A single Texas adult aged 40 in 2025 (the 2017 cliffs analysis household, +# PolicyEngine/us-cliffs-2017 scripts/ptc_household.py), with health benefits +# counted. A $1,000 raise cuts the premium tax credit by $94, and net income +# rises by $709.50 with or without health benefits counted (31,840.09 to +# 32,549.59 without): the cut counts once. +- name: With health benefits counted, a Texas adult earning $30,000 in 2025. + period: 2025 + absolute_error_margin: 0.01 + input: + gov.simulation.include_health_benefits_in_net_income: true + people: + person1: + age: 40 + employment_income: 30_000 + tax_units: + tax_unit: + members: [person1] + households: + household: + members: [person1] + state_code: TX + output: + premium_tax_credit_in_force: 5_606.58 + household_health_costs: 6_194.58 + household_net_income: 25_645.50 + +- name: With health benefits counted, the same adult earning $31,000. + period: 2025 + absolute_error_margin: 0.01 + input: + gov.simulation.include_health_benefits_in_net_income: true + people: + person1: + age: 40 + employment_income: 31_000 + tax_units: + tax_unit: + members: [person1] + households: + household: + members: [person1] + state_code: TX + output: + premium_tax_credit_in_force: 5_512.58 + household_health_costs: 6_194.58 + household_net_income: 25_645.50 + 709.50 diff --git a/policyengine_us/tests/policy/baseline/household/income/household/household_refundable_tax_credits.yaml b/policyengine_us/tests/policy/baseline/household/income/household/household_refundable_tax_credits.yaml new file mode 100644 index 00000000000..434b26458d4 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/household/income/household/household_refundable_tax_credits.yaml @@ -0,0 +1,65 @@ +- name: Case 1, the premium tax credit counts as a refundable tax credit. + period: 2026 + absolute_error_margin: 0.01 + input: + used_aca_ptc: 1_000 + income_tax_refundable_credits_in_force: 0 + household_refundable_state_tax_credits: 0 + household_refundable_local_tax_credits: 0 + output: + household_refundable_tax_credits: 1_000 + +- name: Case 2, it counts once whether or not health benefits count in net income. + period: 2026 + absolute_error_margin: 0.01 + input: + gov.simulation.include_health_benefits_in_net_income: true + used_aca_ptc: 1_000 + medicaid_cost: 500 + snap: 0 + income_tax_refundable_credits_in_force: 0 + household_refundable_state_tax_credits: 0 + household_refundable_local_tax_credits: 0 + output: + household_refundable_tax_credits: 1_000 + # The health benefits are Medicaid alone. + household_health_benefits: 500 + +- name: Case 3, the credit counted is limited to the premium of the plan the household enrolls in. + # 26 U.S.C. 36B(b)(2): a $1,000 credit against an $800 plan counts $800. + period: 2026 + absolute_error_margin: 0.01 + input: + aca_ptc: 1_000 + selected_marketplace_plan_premium_proxy: 800 + income_tax_refundable_credits_in_force: 0 + household_refundable_state_tax_credits: 0 + household_refundable_local_tax_credits: 0 + output: + used_aca_ptc: 800 + premium_tax_credit_in_force: 800 + household_refundable_tax_credits: 800 + +- name: Case 4, a marketplace enrollee's credit reaches the household's refundable credits. + # A single 45-year-old in Lebanon County, PA, earning $40,000 (256% of the + # 2025 poverty guideline, which 2026 credits use). The credit is the + # benchmark premium minus the required contribution; at this income there + # is no EITC, refundable CTC or Pennsylvania refundable credit. + period: 2026 + absolute_error_margin: 1 + input: + people: + person1: + age: 45 + employment_income: 40_000 + tax_units: + tax_unit: + members: [person1] + households: + household: + members: [person1] + state_code: PA + county: LEBANON_COUNTY_PA + output: + assigned_aca_ptc: 10_086 + household_refundable_tax_credits: 10_086 diff --git a/policyengine_us/tests/policy/baseline/household/marginal_tax_rate.yaml b/policyengine_us/tests/policy/baseline/household/marginal_tax_rate.yaml index a3bbd71491e..bd23acb269c 100644 --- a/policyengine_us/tests/policy/baseline/household/marginal_tax_rate.yaml +++ b/policyengine_us/tests/policy/baseline/household/marginal_tax_rate.yaml @@ -103,15 +103,20 @@ period: 2024 input: people: + # Each has employer coverage, so no premium tax credit phases out + # with a raise. person1: age: 40 employment_income: 90_000 + has_esi: true person2: age: 35 employment_income: 60_000 + has_esi: true person3: age: 30 employment_income: 30_000 + has_esi: true tax_units: tax_unit1: members: [person1] @@ -136,6 +141,28 @@ federal_marginal_tax_rate: [0.22, 0.12, 0] fica_marginal_tax_rate: [0.0765, 0.0765, 0] +- name: A marketplace enrollee's rate includes the premium tax credit's phase-out + # A single 35-year-old in Texas earning $60,000 in 2024: 411% of the 2023 + # poverty guideline, where the enhanced schedule caps the benchmark plan at + # 8.5% of income, so the credit falls by 8.5 cents per added dollar. + # 12% bracket + 7.65% payroll tax + 8.5% = 28.15%. + absolute_error_margin: 0.0001 + period: 2024 + input: + people: + person1: + age: 35 + employment_income: 60_000 + tax_units: + tax_unit1: + members: [person1] + households: + household: + members: [person1] + state_code: TX + output: + marginal_tax_rate: 0.2815 + # A single Ohio parent of children aged 4 and 8. The marginal tax rate must # equal the change in household net income between the two fresh simulations # above it: 1 - (alt - base) / 1_000. Ohio Works First counts diff --git a/policyengine_us/tests/policy/contrib/federal/abolish_payroll_tax.yaml b/policyengine_us/tests/policy/contrib/federal/abolish_payroll_tax.yaml index bee6ad3084c..cc9a8d2a06a 100644 --- a/policyengine_us/tests/policy/contrib/federal/abolish_payroll_tax.yaml +++ b/policyengine_us/tests/policy/contrib/federal/abolish_payroll_tax.yaml @@ -30,6 +30,7 @@ - name: Abolishing payroll tax keeps the Idaho permanent building fund tax period: 2025 + absolute_error_margin: 0.01 input: gov.contrib.ubi_center.flat_tax.abolish_payroll_tax: true state_code: ID @@ -45,8 +46,10 @@ id_pbf: 10 household_state_tax_before_refundable_credits: 2_010 household_tax_before_refundable_credits: 7_010 # 5,000 + 2,000 + 10 - household_refundable_tax_credits: 900 - household_net_income: 53_890 # 60,000 + 900 - 7,010 + # The federal income tax stays, and so does the premium tax credit. + premium_tax_credit_in_force: 122.23 + household_refundable_tax_credits: 800 + 100 + 122.23 + household_net_income: 53_890 + 122.23 # 60,000 + 900 + 122.23 - 7,010 - name: Abolishing payroll tax keeps North Carolina use tax period: 2025 diff --git a/policyengine_us/tests/test_aggregate_list_integrity.py b/policyengine_us/tests/test_aggregate_list_integrity.py index 2599fa64000..747de736ff7 100644 --- a/policyengine_us/tests/test_aggregate_list_integrity.py +++ b/policyengine_us/tests/test_aggregate_list_integrity.py @@ -326,6 +326,7 @@ def test_reform_aggregates_keep_shared_cash_programs( "local_occupational_tax", "household_refundable_tax_credits", "income_tax_refundable_credits_in_force", + "premium_tax_credit_in_force", "federal_income_tax_in_force", "household_refundable_state_tax_credits", "household_refundable_local_tax_credits", diff --git a/policyengine_us/tests/test_household_spm_resource_parity.py b/policyengine_us/tests/test_household_spm_resource_parity.py index b3d554e469a..84eb058ad20 100644 --- a/policyengine_us/tests/test_household_spm_resource_parity.py +++ b/policyengine_us/tests/test_household_spm_resource_parity.py @@ -449,8 +449,10 @@ def test_net_incomes_reconcile_for_one_person(state, earnings): spm_unit_net_income subtracts work, medical and child support costs, and it leaves out health benefits. household_net_income subtracts health costs and - counts the benefits and bill discounts listed in HOUSEHOLD_ONLY. Take those - away and the two totals agree. + counts the benefits and bill discounts listed in HOUSEHOLD_ONLY, and the + premium tax credit as a refundable tax credit; the SPM counts the credit + through lower medical out-of-pocket spending, which spm_side adds back. + Take those away and the two totals agree. """ year = 2026 # Rent makes New Jersey's relief positive, so that payment is under test too. @@ -470,6 +472,7 @@ def value(name): - value("commodity_supplemental_food_program") - value("household_head_start_benefits") - value("housing_assistance") + - value("premium_tax_credit_in_force") ) spm_side = ( value("spm_unit_net_income") diff --git a/policyengine_us/tests/unit/test_ss_cola_means_test.py b/policyengine_us/tests/unit/test_ss_cola_means_test.py index e43f3dafbbf..e07b326ae37 100644 --- a/policyengine_us/tests/unit/test_ss_cola_means_test.py +++ b/policyengine_us/tests/unit/test_ss_cola_means_test.py @@ -1151,9 +1151,12 @@ def test_means_test_off_preserves_downstream_dependency_closure( for component in COMPONENTS: record[f"social_security_{component}"] = {2027: 6_000} record.pop(f"social_security_{component}_reported") + # Employer coverage keeps the worker off the premium tax credit, which + # household net income counts since main supplied these literals. record.update( employment_income={2027: 10_000}, taxable_interest_income={2027: 20_000}, + has_esi={2027: True}, ) situation = _situation({"worker": record}) situation["households"]["worker"]["state_code"] = {2027: "NY"} diff --git a/policyengine_us/variables/gov/aca/eligibility/aca_magi_fraction.py b/policyengine_us/variables/gov/aca/eligibility/aca_magi_fraction.py index 9464de7bdd2..f361e2e80c5 100644 --- a/policyengine_us/variables/gov/aca/eligibility/aca_magi_fraction.py +++ b/policyengine_us/variables/gov/aca/eligibility/aca_magi_fraction.py @@ -1,4 +1,5 @@ from policyengine_us.model_api import * +from policyengine_us.tools.parameters import FIRST_MODELED_YEAR class aca_magi_fraction(Variable): @@ -24,5 +25,12 @@ class aca_magi_fraction(Variable): def formula(tax_unit, period, parameters): magi = max_(0, tax_unit("aca_magi", period)) - fpg = tax_unit("tax_unit_fpg", period.last_year) + # The credit measures income against the guideline for the year before + # the coverage year. For coverage in the first modeled year, that year + # precedes the model, so the coverage year's guideline stands in and + # nothing is computed for an unmodeled year. + guideline_period = period.last_year + if guideline_period.start.year < FIRST_MODELED_YEAR: + guideline_period = period + fpg = tax_unit("tax_unit_fpg", guideline_period) return np.floor(100 * magi / fpg) / 100 diff --git a/policyengine_us/variables/household/household_net_income_including_health_benefits.py b/policyengine_us/variables/household/household_net_income_including_health_benefits.py index 4f4ab385316..2c6a3044e46 100644 --- a/policyengine_us/variables/household/household_net_income_including_health_benefits.py +++ b/policyengine_us/variables/household/household_net_income_including_health_benefits.py @@ -8,3 +8,6 @@ class household_net_income_including_health_benefits(Variable): definition_period = YEAR unit = USD adds = ["household_net_income", "healthcare_benefit_value"] + # household_net_income already counts the premium tax credit, as a + # refundable tax credit, and healthcare_benefit_value counts it too. + subtracts = ["premium_tax_credit_in_force"] diff --git a/policyengine_us/variables/household/income/household/household_refundable_tax_credits.py b/policyengine_us/variables/household/income/household/household_refundable_tax_credits.py index 0a0911e4f8e..b7587568790 100644 --- a/policyengine_us/variables/household/income/household/household_refundable_tax_credits.py +++ b/policyengine_us/variables/household/income/household/household_refundable_tax_credits.py @@ -9,6 +9,11 @@ class household_refundable_tax_credits(Variable): unit = USD adds = [ "income_tax_refundable_credits_in_force", + # The premium tax credit is a refundable credit (26 U.S.C. 36B), but + # mostly paid in advance to the insurer, with the return reconciling + # the difference. It counts here rather than in income_tax, which + # stays what the return shows and what SPM resources net. + "premium_tax_credit_in_force", "household_refundable_state_tax_credits", "household_refundable_local_tax_credits", ] diff --git a/policyengine_us/variables/household/income/tax_unit/premium_tax_credit_in_force.py b/policyengine_us/variables/household/income/tax_unit/premium_tax_credit_in_force.py new file mode 100644 index 00000000000..d6860c572ff --- /dev/null +++ b/policyengine_us/variables/household/income/tax_unit/premium_tax_credit_in_force.py @@ -0,0 +1,20 @@ +from policyengine_us.model_api import * + + +class premium_tax_credit_in_force(Variable): + value_type = float + entity = TaxUnit + label = "premium tax credit counted in household net income" + documentation = ( + "The premium tax credit, a refundable federal income tax credit " + "(26 U.S.C. 36B), as household net income counts it: the credit the " + "tax unit uses, which 36B(b)(2) limits to the premiums of the plan " + "it enrolls in and which is zero for a tax unit that doesn't enroll. " + "Like the other federal refundable credits, it is zero in a year the " + "federal income tax is abolished." + ) + definition_period = YEAR + unit = USD + reference = "https://www.law.cornell.edu/uscode/text/26/36B#b_2" + defined_for = "federal_income_tax_in_force" + adds = ["used_aca_ptc"]