Skip to content

Count the premium tax credit in household net income as a refundable credit - #10773

Open
DTrim99 wants to merge 4 commits into
mainfrom
ptc-refundable-credit-net-income
Open

DTrim99 wants to merge 4 commits into
mainfrom
ptc-refundable-credit-net-income

Conversation

@DTrim99

@DTrim99 DTrim99 commented Nov 9, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

This PR counts the federal premium tax credit in household net income as a refundable tax credit. Until now it counted only when gov.simulation.include_health_benefits_in_net_income was on, and that flag is off by default. So with default settings a reform to the credit moved neither household net income nor the budget. policyengine.py's budgetary impact is built from household taxes and benefits, plus the cost of Medicaid, CHIP and the Medicare Savings Programs, and the credit appeared in none of them. In the app such a reform scored $0 and showed no gains by decile, no winners and no change in inequality.

What changes

  • household_refundable_tax_credits adds premium_tax_credit_in_force, beside the federal, state and local refundable credits. The new variable is the credit the tax unit uses (used_aca_ptc), a refundable credit under 26 U.S.C. 36B: 36B(b)(2) limits it to the premiums of the plan the tax unit enrolls in, and it is zero for a tax unit that doesn't enroll. Like the other federal refundable credits, it is zero in a year gov.contrib.ubi_center.flat_tax.abolish_federal_income_tax abolishes the federal income tax. The credit now reaches household_net_income and household_tax the way the EITC and refundable CTC do, whether or not health benefits count.
  • It doesn't go in income_tax. Most of the credit is paid monthly to the insurer in advance, and the return only reconciles the difference (net_premium_tax_credit, Form 8962 line 26). income_tax stays what the return shows. This keeps the household calculator and API partners from reporting the credit as a refund at filing, and it keeps federal income tax totals comparable to IRS and CBO figures, which book advance payments as outlays.
  • SPM resources don't change. They already count the credit through medical out-of-pocket spending, which includes the marketplace premium net of the credit (marketplace_net_premium, since Decompose SPM MOOP premiums #8178). They subtract income_tax, which this PR leaves alone.
  • No double counting:
    • assigned_aca_ptc leaves the gov.household.household_health_benefits list, so it counts once when the flag is on.
    • household_net_income_including_health_benefits subtracts premium_tax_credit_in_force, since household_net_income and healthcare_benefit_value both count it now. Its value, and the marginal rate built on it, are unchanged.
    • healthcare_benefit_value still includes the credit as the value of coverage.
    • With the flag on, household_health_costs subtracts the whole Marketplace premium (selected_marketplace_plan_premium_proxy) instead of the net premium (marketplace_net_premium, the premium less the used credit). Net income counts the credit as a refundable credit, so the net premium counted it a second time. Main has the same double count under the flag: a single Texas adult aged 40 in 2025 whose raise from $30,000 to $31,000 cuts the credit by $94 gains $709.50 with the flag off but $615.50 with it on. Here both are $709.50.
  • 2015 credits read the 2015 guideline. The credit's FPL fraction uses the guideline for the year before coverage. Household net income now computes the credit for every household, so a 2015 household computed 2014 values, before the first modeled year (test_irmaa_first_modeled_year). For coverage in the first modeled year, the coverage year's guideline now stands in, as Colorado CCAP reads its parameters at the start of that year. No test pins a 2014 or 2015 credit.

Effects

In microsimulation, the change moves household_net_income and household_tax for every tax unit with a credit. On the default dataset, assigned credits come to $47.2 billion in 2026 (5.7 million tax units, 11.1 million people) and $54.2 billion in 2027 (5.7 million tax units, 11.0 million people), computed with policyengine-us 2.115.0 for the ACA calculator's 2027 update. This PR adds the used credit, which is at most those amounts (less for tax units on plans cheaper than their credit), to household net income and subtracts it from household tax. Medicaid, CHIP and the other health benefits stay behind the flag.

  • Household net income rises by the assigned credit for marketplace enrollees, and marginal_tax_rate now includes the credit's phase-out and the 400% FPL cliff.
  • Household tax falls by the credit, so policyengine.py's total budgetary impact includes changes in it. They land in the unattributed share for now, like SNAP and SSI. A policyengine.py follow-up can attribute them to the federal government and add an ACA line to its program statistics.
  • The app shows this once policyengine.py pins a policyengine-us release that includes it (it pins 2.2.1).
  • Keep a filer who can be claimed as a dependent out of the premium tax credit family, and apply each state premium program's rule #10041 also edits aca_magi_fraction.py: it reads a new aca_fpg in place of tax_unit_fpg(period.last_year). Whichever lands second carries the first-modeled-year guard into the other's lookup.
  • No partner contract file asserts any variable this changes: household_net_income, household_tax, household_refundable_tax_credits, the health aggregates or marginal_tax_rate.

Tests

  • New household_refundable_tax_credits.yaml:
    • the credit counts as a refundable credit;
    • it counts once with the health-benefits flag on;
    • a derived credit for a single 45-year-old in Lebanon County, PA, earning $40,000 in 2026 reaches the household's refundable credits ($10,086).
  • Counting the credit once with health benefits on, in household_net_income.yaml:
    • a flag-on pair: with a $5,000 premium, a $2,000 credit gives $1,000 more net income than a $1,000 credit;
    • the 2017 cliffs analysis household above (PolicyEngine/us-cliffs-2017 scripts/ptc_household.py): its raise adds $709.50 with the flag on, as with it off.
  • The premium cap, in household_refundable_tax_credits.yaml: a $1,000 credit against an $800 plan counts $800. household_health_costs.yaml checks that health costs take the whole premium and that the credit doesn't lower them.
  • New cases:
    • in household_net_income.yaml: net income and household tax;
    • in household_net_income_including_health_benefits.yaml: the credit counts once;
    • in marginal_tax_rate.yaml: a single $60,000 earner in Texas in 2024 faces 12% + 7.65% + 8.5% = 28.15%, the enhanced schedule's phase-out above 400% FPL.
  • Households that have a credit now count it. Each such case names premium_tax_credit_in_force as an output, so the difference shows:
    • two military retirement pay cases (Texas, 2024);
    • the Indiana county tax case (2021);
    • the two Montana 2021 rebate cases;
    • the payroll tax abolition case (Idaho, 2025).
  • Abolition. The federal income tax abolition case passes unchanged: abolishing the federal income tax takes the credit with the other federal refundable credits.
  • Employer coverage. Several tests give their households employer coverage, so the credit's phase-out doesn't blur the arithmetic they check:
    • the roommate marginal tax rate tests (YAML and test_marginal_tax_rate_coverage.py);
    • the zero-rate capital gains response test;
    • the estate income net income invariant;
    • the Social Security COLA differential, whose literals come from main.
  • household_health_benefits.yaml: with the flag on, the health benefits no longer include the credit.
  • test_household_spm_resource_parity.py: the one-person reconciliation takes the credit out of the household side, as one of the items the two measures define differently. test_aggregate_list_integrity.py adds the new variable to the household tax totals that reforms mustn't redefine.

Checks run before pushing, on Modal (the laptop hasn't the memory):

  • the 46 YAML files that assert a variable this changes or set the health-benefits flag;
  • the Python tests that touch net income, marginal rates or SPM resources: the parity, aggregate-list, SPM contract and system, marginal-rate coverage and branches, behavioral-response and capital-gains MTR, override, multi-year, cached-array and child-care tests.

All pass after the updates above. A run of 30 other Python test files failed the same tests on this branch as on main, from files the Modal run didn't mount (notebooks and CI config). CI runs the full suites.

axiom: n/a: accounting aggregate (household net income), not a statutory provision

🤖 Generated with Claude Code

DTrim99 and others added 3 commits November 9, 2026 16:49
…credit

The federal premium tax credit counted in household net income only when
gov.simulation.include_health_benefits_in_net_income was on, which it is
not by default. A reform to the credit then moved neither net income nor
policyengine.py's budgetary impact.

household_refundable_tax_credits now adds premium_tax_credit_in_force: the
assigned credit, a refundable credit under 26 U.S.C. 36B, zeroed like the
other federal refundable credits in a year the federal income tax is
abolished. It doesn't go in income_tax, which stays what the return shows
(most of the credit is paid in advance to the insurer) and what SPM
resources net; SPM resources already count the credit through the
marketplace premium net of it. The credit leaves the health-benefits list,
and household_net_income_including_health_benefits subtracts it, so it is
never counted twice.

Tests whose households have a credit now count it: each such case names
the credit as an output.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…l rate

The roommate marginal tax rate tests and the zero-rate capital gains
response test check mechanics the premium tax credit's phase-out would
blur, so their households now have employer coverage. A separate case
pins the phase-out: a single $60,000 earner in 2024 faces 12% + 7.65% +
8.5% = 28.15%.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Household net income now computes the premium tax credit for every
household, and the credit's FPL fraction read the prior year's poverty
guideline, so a 2015 household computed 2014 values, before the first
modeled year (test_irmaa_first_modeled_year). For coverage in the first
modeled year, the coverage year's guideline now stands in, as Colorado
CCAP reads its parameters at the start of that year.

The estate income invariant and the Social Security COLA differential
check net income against tax-only arithmetic and main's literals, so
their filers have employer coverage.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@MaxGhenis

Copy link
Copy Markdown
Contributor

An independent review at 460f68252e found two things to fix before this merges. I checked both against the code at that head.

1. With include_health_benefits_in_net_income on, the credit still counts twice. household_net_income now adds the credit through household_refundable_tax_credits. Under the flag it also subtracts household_health_costs, whose list still holds marketplace_net_premium, and that variable equals the premium less used_aca_ptc. So net income moves by $2 for each $1 of credit, as it does on main: with a $5,000 premium, raising aca_ptc from $1,000 to $2,000 raises net income by $2,000. A flag-on pair like that would pin the fix. One fix: have the costs list subtract the gross premium once the credit counts as cash.

2. premium_tax_credit_in_force adds assigned_aca_ptc, which has no premium cap. 26 U.S.C. 36B(b)(2) limits the credit to the premiums of the plan the household enrolled in. assigned_aca_ptc equals aca_ptc times take-up, so a household whose plan costs less than its credit counts more than the statute allows. used_aca_ptc already takes the lesser of the credit and the plan premium. Reading it, with a case where the premium falls short of the credit, would follow the statute.

Two smaller points:

@MaxGhenis

Copy link
Copy Markdown
Contributor

A household from the 2017 cliffs analysis shows finding 1 on PolicyEngine US 2.116.0: a single Texas adult aged 40 in 2025, with wages of $30,000 and then $31,000.

  • The credit falls by $94, from $5,606.58 to $5,512.58, and marketplace_net_premium rises by $94.
  • With the flag off, household_net_income rises by $803.50.
  • With the flag on, it rises by $615.50: household_health_benefits falls by $94 and household_health_costs rises by $94, so the $94 cut costs $188.

Counting the credit once gives $709.50. At this PR's head the first $94 arrives through household_refundable_tax_credits in place of the benefits list, and the second still comes through the costs list.

Review on #10773 (Max):

- premium_tax_credit_in_force reads used_aca_ptc, which 26 U.S.C.
  36B(b)(2) limits to the premiums of the plan the tax unit enrolls in
  (and which is zero for a tax unit that doesn't enroll), instead of
  assigned_aca_ptc.
- With include_health_benefits_in_net_income on, household_health_costs
  subtracts the whole Marketplace premium instead of the net premium.
  Net income counts the credit as a refundable credit, so the net premium
  counted it a second time: a $94 smaller credit cost $188.

Tests: a flag-on pair ($1,000 more credit against a $5,000 premium raises
net income $1,000), a credit capped at an $800 plan, and the 2017 cliffs
analysis household (a $30,000 to $31,000 raise in Texas in 2025 raises
net income $709.50 with or without health benefits counted).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@DTrim99

DTrim99 commented Nov 10, 2026

Copy link
Copy Markdown
Collaborator Author

Thanks, both fixed in e0a7f08.

1. Double count with the flag on. household_health_costs now subtracts the whole Marketplace premium (selected_marketplace_plan_premium_proxy) instead of marketplace_net_premium, so the credit counts only through household_refundable_tax_credits. Your 2017 cliffs household now matches with the flag on:

$30,000 $31,000 Change
Credit $5,606.58 $5,512.58 −$94.00
Net income, flag off $31,840.09 $32,549.59 +$709.50
Net income, flag on $25,645.50 $26,355.00 +$709.50

The flag-on levels differ by the $6,194.58 gross premium, which doesn't change with income. Two flag-on cases in household_net_income.yaml pin this household. A flag-on pair there pins the general case: with a $5,000 premium, a $2,000 credit gives $1,000 more net income than a $1,000 credit.

2. Premium cap. premium_tax_credit_in_force now adds used_aca_ptc. It takes the lesser of the credit and the plan premium, and it's zero without enrollment, because the premium proxy is zero then. A new case counts $800 for a $1,000 credit against an $800 plan.

Smaller points:

I reran the changed YAML files (13) and the parity, aggregate-list and premium-input tests on Modal, and all pass.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants