Repository navigation
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
Conversation
The federal standard deduction and 17 state formulas read only whether the tax-unit head is claimed elsewhere, so a joint return where one spouse can be claimed was taxed differently depending on which spouse was labelled head. Each formula now applies its jurisdiction's rule for that case (either spouse, single filers only, per spouse, or only when every filer is claimed), using two new label-independent helpers. Adds swap-pair YAML cases and a Hypothesis property test. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… barred The New Mexico, Maine and Hawaii renters gates counted filers who are not claimed elsewhere, so a tax unit with neither a head nor a spouse (252 in the default dataset) looked like one whose every filer was claimed. A new helper, every_filer_is_dependent_elsewhere, requires a claimed filer and no unclaimed one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ss EITC - Hawaii's dependent standard deduction uses earned income net of the deductible part of self-employment tax, as the N-11 worksheet does. - New Mexico's medical care credit needs a filer who is both 65 or older and not a dependent when a filer is a dependent elsewhere (NMSA 7-2-18.13). - The federal EITC without a qualifying child requires that no filer can be claimed as a dependent (IRC 32(c)(1)(A)(ii)(III), Publication 596), so Missouri's working family credit no longer reaches such a couple. - Hawaii's food/excise credit keeps minor children receiving public support (HRS 235-55.85(c)) on a joint return where one spouse can be claimed. - Comments label the readings the law does not settle, and the property test covers the EITC and the food/excise exemption amount. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… meaning - The federal and Oregon dependent worksheets use earned income net of the deductible part of self-employment tax, as Hawaii's now does. - Hawaii's food/excise minor-child count excludes children who can be claimed by another taxpayer and the filers themselves (Form N-311 line 3). - claimed_as_dependent_on_another_return documents what it stands for: a §151 deduction for the person is allowable to another taxpayer, so a person whose would-be claimer files only for a refund is not one (Publications 501 and 596); rules on actually being claimed read it too. - Hawaii's renters comment labels the mixed joint reading as an inference; the Hawaii dependent threshold cites HRS 235-2.4(a)(3) on its page. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A filer who can be claimed on another return is treated as having no dependents (IRC 152(b)(1); on a joint return either spouse counts) and has no personal exemption (IRC 151(d)(2)); one who is claimed gets no education credits (25A(g)(3)), student loan interest deduction (221(c)) or recovery rebate (6428(d)(2), 6428A(d)(2), 6428B(c)(2)). The model read none of these. Now the child tax credit and credit for other dependents, the under-13 child and dependent care qualifying person, exemptions_count and its RI, DE, OH, OR, WI, HI and NM readers, the recovery rebates, the education credits and the student loan interest deduction apply them symmetrically for joint returns. Georgia keeps its own counts; the HI, WI and Treasury exemption reforms follow the corrected count. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…it family IRC 36B(c)(1)(D) excludes anyone another taxpayer can claim from being an applicable taxpayer, and 26 CFR 1.36B-1(d)(2) leaves such a spouse out of a joint return's family. Add is_aca_tax_family_member and aca_tax_family_size, measure household income against the poverty line for that family (aca_fpg, aca_magi_fraction), gate is_aca_ptc_eligible and the below-FPL immigration exception on it, and price the credit on the coverage family's benchmark (aca_ptc_slcsp, which equals slcsp whenever every enrollee is in the family). The NY/VT family-tier logic becomes shared functions so the benchmark can apply it to the coverage family. The three ACA reforms that override is_aca_ptc_eligible keep the gate. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e tax family Massachusetts, Connecticut, Colorado, Maryland, California, New Mexico and Vermont require each enrollee to be eligible for the federal credit, so the gate flows through is_aca_ptc_eligible and their wraps use the coverage family's benchmark. New Mexico's Middle Income Household component and New Jersey Health Plan Savings require every federal criterion except income, so they now read the tax family too. Washington's Cascade Care Savings pays its non-federally-subsidized amount (Group 2) to a QHP-eligible enrollee without a federal subsidy, so a claimable enrollee moves from Group 1 to Group 2. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Child and dependent care: childcare expenses do not count on a return with a claimable filer unless a child under 13 still qualifies (IRC 21(b)(2)(A)(ii)). - Tuition and fees deduction: not allowed to a filer who can be claimed (IRC 222(c)(3)), so the education credit bar no longer makes such a filer eligible. - Recovery rebates: the Armed Forces exception waives the identification rule only, not the exclusion of a spouse who is another taxpayer's dependent. - Georgia: the contributed refundable credit and SB 520 reforms count the same children as ga_ctc_potential through a new ga_ctc_eligible_child. - Education credits and student loan interest read the claimed input directly, since IRC 25A(g)(3) and 221(c) turn on an actual claim. - One reference per source document; quote IRC 24(h)(4)(A) exactly. - Tests: mixed-recipient care, tuition deduction, military rebates, Georgia refund cap, and the swapped New Mexico rebate case; the property test now covers the state readers of the exemption count. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts: # policyengine_us/tests/policy/baseline/gov/states/oh/tax/income/exemptions/personal/oh_personal_exemptions_eligible.yaml
#10024 now separates three facts: is claimed, can be claimed, and the would-be claimant's filing exception. - IRC 152(b)(1) gates (child tax credit, credit for other dependents, under-13 care child and its expenses, dependents in exemptions_count, ARPA dependents, Ohio dependents, the Hawaii and Wisconsin reforms) read head_or_spouse_is_dependent_elsewhere_without_filing_exception, so a filer within the Publication 501 exception keeps their dependents. - Own exemptions, recovery rebates and the tuition deduction read claimability. - Education credits and student loan interest keep reading the claimed input. - Tests: the filing exception and a filer who can be claimed but is not. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#10024's food/excise credit, Act 115 rebate and renters credit took claimable filers off exemptions_count themselves. exemptions_count now leaves them out (and, outside the filing exception, the return's dependents), so those credits read it as it is; the filing-exception cases were one exemption short. The renters credit also leaves out a dependent who can be claimed by another taxpayer (Schedule X counts only people who cannot be), as the food/excise credit already did. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Education credits: a dependent student is not eligible on a return whose filer can be claimed outside the filing exception (IRC 25A(f)(1)(A)(iii), 152(b)(1)); the filer's own eligibility still turns on an actual claim. - Recovery rebates: no credit when every filer is a dependent of another taxpayer (IRC 6428B(a)); the CARES and CAA child prerequisite is a spouse with a valid SSN, whether or not that spouse is an eligible individual. - Child and dependent care: cdcc_qualifying_childcare_expenses holds the expense rule, and the single-parent work requirement reform and California's expenses read it. - References: Georgia HB 136 page 3; one entry per recovery rebate statute; quote IRC 24(h)(4)(A) exactly in the test comment. - Tests: dependent students, the two rebate cases, the reform and California expenses, and the Hawaii, Wisconsin and Treasury reforms with a claimable filer under both labellings. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Education credits: is_barred_from_education_credits_by_dependency holds the claimed-filer and dependent-student rules; both eligibility formulas and the reconciliation SSN reform's Lifetime Learning Credit read it, so the reform no longer turns a barred student's tuition into that credit. - Child and dependent care: childcare counts when any child under 18 is still a qualifying individual, so an incapacitated child aged 13 to 17 keeps it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Setting meets_eitc_identification_requirements on one person made it an input for every person, so the other spouse lost their SSN too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…claimable-filer-ptc
…role-less units - is_aca_tax_family_member reads claimable_as_dependent_on_another_return (#10024's split input: 36B(c)(1)(D) and 1.36B-1(d)(2) turn on being claimable, and Form 1040 line 12a, which Form 8962 line 1 keys on, has no refund-only carve-out). The return keeps its dependents when the would-be claimant meets the refund-only filing exception (Pub 501, Dependent Taxpayer Test), a return with no applicable taxpayer has no family, and a tax unit with neither a head nor a spouse uses each member's own claimability (a lone claimable minor no longer gets the credit). - aca_magi leaves out the tax-exempt interest and Social Security of the return's dependents who are outside the tax family (26 CFR 1.36B-1(e)(1)). - used_aca_ptc is capped at the coverage family's share of the selected plan premium (new aca_ptc_coverage_family_premium; 26 CFR 1.36B-3(d)(1)(i), (h)(1)), not the whole policy's premium. - marketplace_csr_eligible applies 45 CFR 155.305(g)(3): the model prices one policy per tax unit, and a policy that also covers an enrollee without credit eligibility gets no cost-sharing reductions. - aca_fpg documents that it sizes the family for the tax year; the NJHPS exclusion is labelled an interpretation; one reference per source in is_aca_coverage_family_member. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
YAML: refund-only exception (family keeps the child), single claimable filer with the exception (no family), claimable but not claimed, lone claimable and unclaimed 17-year-old filers, household income without an out-of-family child's tax-exempt interest, the coverage-family premium cap (silver and bronze), CSR under the family-policy rule, Colorado's binding residual ($420), and the tax year's family size against a supplied prior-year size. Test inputs now use claimable_as_dependent_on_another_return. Property test: Hypothesis runs every year 2021-2026 and draws claimable-but- unclaimed spouses and the filing exception; the credit's monotonicity exemption is limited to crossing the 100% floor or dependents' income leaving household income; the benchmark check compares enrolled people, not counts; new identities for household income, the premium cap and CSR. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…cation, MAGI floor - A tax unit with neither a head nor a spouse is filed by its oldest member. When that member can be claimed elsewhere, nobody is in the tax family. Before, a claimable 17-year-old filing with an infant got $12,000. - The coverage family is priced on its own. Its children are ranked for the three-oldest-children rule (45 CFR 147.102(c)(1)), and its family tier is built from its own members. - A shared premium is allocated by each family's benchmark (26 CFR 1.36B-3(h)(1)). The denominator is the coverage family's benchmark plus the benchmark of the enrollees outside it, not the whole policy's tier. An empty coverage family gets no share of the premium. - Out-of-family dependents' tax-exempt interest and Social Security are removed before the single zero floor. A negative benefit no longer cuts household income. - is_aca_marketplace_enrollee (enrolled) is split from pays_aca_premium (charged an age-rated premium). The family-tier child tests no longer depend on payment. - Tests: YAML for each case under both head/spouse labellings. The property test now gives dependents tax-exempt interest and Social Security. It checks aca_magi against an expectation built from the inputs, and it exempts only that removal from monotonicity. Monotonicity runs under Hypothesis in every year 2021-2026. The "unchanged" identities apply only to returns with a head or spouse. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The reform came from main (#10681) in the last merge and still read the whole tax unit's benchmark (slcsp) and selected premium. It now reads the coverage family's benchmark (aca_ptc_slcsp) and its share of the premium (aca_ptc_coverage_family_premium), as the baseline credit does, so an enrollee who can be claimed on another return stays out of the credit. The reform's tests state enrollment (is_aca_marketplace_enrollee), as this branch's used_aca_ptc tests do, and a new case covers a claimable spouse: $5,016.40, the baseline credit, where the reform gave $11,016.40. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Resolves the conflict in is_aca_ptc_eligible.yaml by keeping both sides' new cases: this branch's claimable-filer cases and main's permanent resident barred from Medicare at 70 (#10632). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Main now has #10773, which guarded aca_magi_fraction's prior-year guideline for the first modeled year. This branch reads aca_fpg there, so the guard moves into aca_fpg (guideline year and state group). The aca_magi_fraction and is_aca_ptc_eligible tests keep both sides' cases. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@PavelMakarchuk, thanks for the review. This push fixes the Critical item and does suggestion 6. The other items are still open. Critical: the minimum premium reform now uses the coverage family (0675e97)
Suggestion 6: the first-modeled-year guard is in
|
PavelMakarchuk
left a comment
There was a problem hiding this comment.
Verdict: REQUEST_CHANGES (head b7841c8935)
The push fixes the Critical item from my first review, and the first-modeled-year guard from #10773 is kept with the same effect. But the merge with main turns Should address 1 from that review into a CI failure: four tests that #10773 added fail at this head and pass on main. Should address 2 to 7 are unchanged.
Round-1 items
| Item | Status |
|---|---|
| Critical: the minimum premium reform bypassed the coverage-family benchmark and premium | Resolved (0675e97286). aca_ptc is 5,016.40 with the reform on, where it was 11,016.40. One test gap remains (Should address 1 below). |
Should 1: a supplied pays_aca_premium no longer implies enrollment |
Not resolved, now Critical. It causes the failing CI check (see Critical). |
| Should 2: stale TheAxiomFoundation/rulespec-us#1627 | Not resolved. The issue is open, last updated 2026-10-10. |
| Should 3: source-conflict register | Not resolved. No change to source_conflicts/candidates.yaml. |
| Should 4: California reference pages | Not resolved. The three files are unchanged. |
| Should 5: helpers and the private Core call | Not resolved. slcsp_age_curve_amount_person_uncapped.py:24-27 and is_aca_tax_family_member.py:44-45 are unchanged; the PR body still names the two unused helpers. |
| Should 6: three unpinned branches | Not resolved. No test for those branches changed. |
| Should 7: microsimulation impact "Pending" | Not resolved. The PR body still says "Pending". |
| Suggestions 1 to 5 | Not addressed. The reply says they are left for the PR author. |
Suggestion 6: move the #10773 guard into aca_fpg.py |
Resolved. Same results as main for 2015, 2016 and 2017. |
Critical
Four household net income tests from #10773 fail at this head. This PR causes the failure.
-
Check: "Baseline tests / Full Suite - Baseline (household)", completed 2026-11-12 6:48pm EST. I reproduced it:
policyengine_us/tests/policy/baseline/household/income/gives 4 failed, 220 passed at this head. The same four cases pass on main (ca7a470271).Test file Case Result at this head household/income/household/household_health_costs.yaml:26"The premium tax credit doesn't lower household health costs ..." marketplace_net_premium5,000, expected 4,000household/income/household/household_net_income.yaml:206"... a $1,000 premium tax credit against a $5,000 premium." premium_tax_credit_in_force0, expected 1,000household/income/household/household_net_income.yaml:224"... a $2,000 premium tax credit against the same premium." premium_tax_credit_in_force0, expected 2,000household/income/household/household_refundable_tax_credits.yaml:28"Case 3, the credit counted is limited to the premium of the plan ..." used_aca_ptc0, expected 800 -
Cause: each case supplies
aca_ptcandselected_marketplace_plan_premium_proxyfor a default household.aca_ptc_coverage_family_premium.py:32-34and:65-69return $0 unlessis_aca_marketplace_enrolleeis true for a tax-family member. For the default household that variable computes to false (the person is Medicaid-eligible). Soused_aca_ptcis $0, and #10773'spremium_tax_credit_in_force(which addsused_aca_ptc) drops the credit from household net income. -
With
is_aca_marketplace_enrollee: trueadded to the Case 3 inputs,used_aca_ptcis 800 again. That confirms the cause. -
This is the behavior in Should address 1 of my first review. The witness there still holds: 2026, New Mexico, one adult aged 30,
pays_aca_premium: true,slcsp_age_curve_multiplier: 1,aca_magi: 23_475,slcsp_age_0: 6_000givesused_aca_ptc5,016.40 on main and 0 at this head. -
The effect is larger since #10773: a caller who supplies a premium or
pays_aca_premiumwithoutis_aca_marketplace_enrolleenow loses the credit inhousehold_refundable_tax_creditsandhousehold_net_income, not only inmarketplace_net_premium. -
Fix, one of:
- Let a supplied premium or a supplied
pays_aca_premium: truecount as enrollment (for example withhas_input_for_periodinis_aca_marketplace_enrollee). The four tests then pass unchanged. I recommend this one: the PR has already addedis_aca_marketplace_enrolleeto the three New Mexico MIH fixtures and to seven minimum premium cases for the same reason. - Add
is_aca_marketplace_enrollee: trueto the four cases, and state the new input contract in the changelog and in the documentation ofused_aca_ptc.
- Let a supplied premium or a supplied
Should address
- The new reform case does not pin two of the three changed lines. In "A claimable spouse's benchmark and premium stay out of the credit" (
policyengine_us/tests/policy/contrib/aca/ptc_minimum_premium.yaml:202), the cap does not bind. With the old reads ataca_ptc_minimum_premium.py:58and:76(whole-policy premium 12,000), the case still gives 5,016.40 (min(5,016.40, 12,000 - 60)). Add the same household withaca_required_contribution_percentage: 0: I getaca_ptc5,940 andused_aca_ptc5,940 at this head, and the old reads give 6,000 for both. - Round-1 Should address 2 to 7 are open and unchanged. See my first review for the details: the stale axiom issue, the source-conflict register, the California reference pages, the helpers and the private Core call, the three unpinned branches, and the "Pending" microsimulation impact.
Suggestions
- Round-1 suggestions 1 to 5 are unchanged.
- Not caused by this PR, and the same on main:
aca_fpg.py:43readsstate_group_strfor the guideline year. When the state is supplied only for the tax year, the prior year falls back to the default state group. A 2026 Alaska filer with $30,000 of MAGI getsaca_magi_fraction1.91 (the contiguous guideline, 15,650) on main and at this head; the 2025 Alaska guideline (19,550) gives 1.53. Reading the state group forperiodwould fix it. A separate issue is enough.
What I verified
Minimum premium reform (policyengine_us/reforms/aca/aca_ptc_minimum_premium.py)
-
I read the file at this head. The three reads now match the baseline
aca_ptc_before_filing_gateandused_aca_ptcat this head, plus the minimum. -
2026 joint return with one claimable spouse, reform on:
Output Minimum does not bind Minimum binds (contribution 0) aca_tax_family_size1 1 aca_ptc_slcsp6,000 6,000 aca_ptc_coverage_family_premium6,000 6,000 aca_ptc5,016.40 5,940 used_aca_ptc5,016.40 5,940 marketplace_net_premium(whole 12,000 policy)6,983.60 6,060 -
Hand check of the new case: 6,000 - 0.0419 x 23,475 = 5,016.40; the coverage family's share is 12,000 x 6,000 / 12,000 = 6,000; 5,016.40 is below 6,000 - 60.
-
Ordinary households, reform on, this head against main: identical
aca_ptc,used_aca_ptcandmarketplace_net_premiumfor the same joint return with no claimable spouse (11,507.03; 11,940 when the minimum binds) and for five computed households (Texas, Florida and Pennsylvania; one adult, or one adult with one or two children).
aca_fpg.py and the merge with #10773
- The guard is the same logic as main's
aca_magi_fraction.py. For $30,000 of MAGI,aca_magi_fractionat this head equals main in every case I ran: 2015 (Texas 2.54, Alaska 2.03, Hawaii 2.21), 2016 (2.54) and 2017 (2.52). In 2015aca_fpgis 11,770, the 2015 guideline. - California's
ca_premium_subsidy_eligibleandca_premium_subsidy_applicable_percentagecompute for 2015, 2016, 2021 and 2026 without error, with the same values as main for a single adult earning $30,000. aca_magi_fraction.yaml,is_aca_ptc_eligible.yamlandptc_minimum_premium.yamllose no line of main's version. All other files of #10773 are identical to main, exceptaca_magi_fraction.py(readsaca_fpg, as intended) and the changelog fragment that main's version bump consumed.- The credit counts once.
premium_tax_credit_in_forceaddsused_aca_ptc, andhousehold_health_costssubtracts the whole premium. For the claimable-spouse return, a $1,000 rise in MAGI lowersused_aca_ptcby 112.68 andhousehold_net_incomeby 112.68, with health benefits counted or not.household_refundable_tax_creditsis 5,016.40, equal toused_aca_ptc.
Tests run at this head (Python 3.13, policyengine-core 3.34.10)
| Tests | Result |
|---|---|
policyengine_us/tests/policy/baseline/gov/aca/ and policyengine_us/tests/policy/contrib/aca/ |
567 passed |
| The nine state premium program directories (CA, CO, CT, MA, MD, NJ, NM, VT, WA) | 624 passed |
policyengine_us/tests/policy/baseline/household/income/ |
4 failed, 220 passed (on main, household/income/household/: 83 passed) |
tests/core/test_claimable_filer_ptc_invariants.py and the three tests/unit/test_ca_premium_subsidy_*.py files |
34 passed |
policyengine_us/tests/policy/baseline/partners/ |
623 passed |
- Partner contract tests: the PR edits none of them, and all pass.
- CI at this head: 26 checks passed, 1 failed (the household shard above), 1 skipped ("Contrib tests").
- Mergeability: a trial merge with main at
ca7a470271has no conflict. - The changelog fragment
changelog.d/claimable-filer-ptc.fixed.mdis unchanged and at the top level.
Not verified
- Population impact (no microsimulation run).
- The sources that I could not open in the first review.
- I did not run a mutation of
aca_ptc_minimum_premium.py:58and:76; Should address 1 rests on the arithmetic above.
A supplied pays_aca_premium of true marks an enrollee, since everyone who pays an age-rated premium is one. A supplied premium for a tax unit in which the model finds no enrollee, with enrollment not supplied, is the tax family's, as before enrollment was modeled. This restores #10773's four household tests unchanged. The minimum premium reform gets a case in which the cap binds, so the coverage family's premium is pinned. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@PavelMakarchuk, thanks. 18385c1 takes your option 1 for the Critical item and adds the case from Should address 1. Critical: supplied inputs count as enrollment again
Should address 1: the cap now binds in a reform case
The whole-policy reads would give 6,000. Not in this push
As with the last push, I haven't run tests locally, so CI is the check. |
|
@PavelMakarchuk, your suggestion 2 (the Alaska and Hawaii state group in |
PavelMakarchuk
left a comment
There was a problem hiding this comment.
Verdict: REQUEST_CHANGES (head 18385c186b)
The push fixes the Critical item from my last review: the four household tests pass again, CI is green, and a supplied pays_aca_premium or a supplied premium gives the same credit as main. No Critical item is open. The verdict stays REQUEST_CHANGES because of test gaps (one line of the reform, and the three conditions of the new premium rule), one gap in the new rule for monthly premiums, and round-1 Should address 2 to 7, which are unchanged.
Resolved since the last review
| Item | Status |
|---|---|
| Critical: four household tests from #10773 failed | Resolved (18385c186b). policyengine_us/tests/policy/baseline/household/income/ gives 224 passed, 0 failed. The four cases at household_health_costs.yaml:26, household_net_income.yaml:206 and :224, and household_refundable_tax_credits.yaml:28 pass unchanged. |
Round-1 Should 1: a supplied pays_aca_premium no longer implied enrollment |
Resolved. The New Mexico witness (2026, one adult aged 30, pays_aca_premium: true, slcsp_age_curve_multiplier: 1, aca_magi: 23_475, slcsp_age_0: 6_000) gives is_aca_marketplace_enrollee true, used_aca_ptc 5,016.40 and marketplace_net_premium 983.60, the same as main. The case is now in used_aca_ptc.yaml:164. |
Round-2 Should 1: the reform case did not pin aca_ptc_minimum_premium.py:58 and :76 |
Partly resolved. The new case pins line 58. Line 76 is still not pinned (Should address 1). |
| Round-1 Should 2 to 7 | Not resolved (Should address 4). |
Suggestion 2 of the last review (state group in aca_fpg) |
Tracked in #9482, as the reply says. No action needed here. |
Should address
-
The reform's
used_aca_ptcread atpolicyengine_us/reforms/aca/aca_ptc_minimum_premium.py:76is still not pinned.-
I changed one line at a time to the old whole-policy read and ran
policyengine_us/tests/policy/contrib/aca/ptc_minimum_premium.yaml:Line changed back Result :52(aca_ptc_slcsptoslcsp)2 failed, 9 passed :58(enrolled_premium)1 failed, 10 passed :76(used_aca_ptc)11 passed -
Cause: in the new case at
ptc_minimum_premium.yaml:255,aca_ptcis already capped at 5,940 by line 58. With the whole-policy read at line 76,used_aca_ptcis min(5,940, 12,000 - 60) = 5,940, the same value. My last review gave 6,000 for the old reads; that holds only when both lines are changed together. That was my error in the witness. -
A case that pins line 76: the same household (2026 Texas joint return, head claimable elsewhere,
aca_magi: 23_475,aca_required_contribution_percentage: 0,slcsp_age_0: 6000) withselected_marketplace_plan_premium_proxy: 8_000. At this head I getaca_ptc_coverage_family_premium4,000,aca_ptc5,940 andused_aca_ptc3,940 (4,000 - 60). With the old read at line 76,used_aca_ptcis 5,940 and the case fails.
-
-
No test pins the three conditions of the new supplied-premium rule (
policyengine_us/variables/gov/aca/ptc/aca_ptc_coverage_family_premium.py:43-48).The reply says the rule applies only when enrollment is not supplied and the tax unit has a tax-family member. I removed each condition in turn and ran the ACA, ACA contrib and household income directories. All 794 tests pass each time.
Condition removed Tests A household that the condition protects (2026, Texas, selected_marketplace_plan_premium_proxy: 5_000,aca_ptc: 1_000)This head Without the condition not has_input_for_period(sim, "is_aca_marketplace_enrollee", period)(:45)794 passed One adult aged 40 with is_aca_marketplace_enrollee: falseused_aca_ptc01,000 ~tax_unit.any(enrolled)(:47)794 passed Joint return. The head can be claimed elsewhere and is the only enrollee; the spouse has is_aca_eshi_eligible: trueused_aca_ptc0,marketplace_net_premium5,0001,000 and 4,000 tax_unit.any(member)(:47)794 passed One adult aged 30, claimable elsewhere, is_medicaid_eligible: trueused_aca_ptc01,000 The existing case "r3 no Marketplace enrollees receive none of a supplied annual premium" (
aca_ptc_coverage_family_premium.yaml:525) supplies enrollment and has no tax-family member, so two conditions protect it at the same time and it pins neither. Add the three households above toaca_ptc_coverage_family_premium.yaml. -
The new rule reads only the annual premium input, so monthly premiums get $0.
-
aca_ptc_coverage_family_premium.py:43-45testshas_input_for_period(sim, "selected_marketplace_plan_premium_proxy", period). This PR addsmonthly_selected_marketplace_plan_premium_proxyas the monthly input for the same premium. A caller who supplies the premium by month does not reach the rule. -
Witness: 2026, California, one adult aged 40 with no other inputs (the model computes Medicaid eligibility, so no enrollee),
aca_ptc: 1_000.Premium input selected_marketplace_plan_premium_proxyaca_ptc_coverage_family_premiumused_aca_ptcmarketplace_net_premiumAnnual, 5,000 5,000 5,000 1,000 4,000 Monthly, 500 in each month 6,000 0 0 6,000 -
Fix: also treat a supplied monthly premium as a supplied premium (for example, test
has_input_for_periodfor any month of the year), or state in the documentation ofaca_ptc_coverage_family_premiumthat the rule applies to the annual input only. Add the case. -
The monthly variable is new in this PR, so no existing caller changes.
-
-
Round-1 Should address 2 to 7 are open. The push changes none of the files they concern. I checked each again at this head:
- 2, axiom issue: TheAxiomFoundation/rulespec-us#1627 is open with the
pe-paritylabel and was last updated on 2026-10-10. It predates the refund-only exception and the later rules in this PR. - 3, source-conflict register:
source_conflicts/candidates.yaml(us-co:premium-assistance-ptc-recipients) still hascode: []at line 2085 andurl: nullat lines 2074 and 2077. - 4, California reference pages:
ca_premium_subsidy_eligible.py:14andca_premium_subsidy.py:23still point to#page=1;ca_premium_subsidy_applicable_percentage.py:15-16to#page=2. No reference reaches PDF page 3. - 5, helpers:
slcsp_age_curve_amount_person_uncapped.py:24-27still calls the privatesimulation._get_set_input_periodswith a fallback for older Core releases. This push useshas_input_for_periodin two more places, so the helper is the pattern of the PR.is_aca_tax_family_member.py:44-45still computes the exception inline. The PR body (line 17) still names two helpers that no ACA formula reads. - 6, three unpinned branches:
marketplace_csr_eligible.py:27-31, the even-split fallback ataca_ptc_coverage_family_premium.py:64, andis_aca_filer.py:32. I changed each branch in turn (thewhereto the before-premium test only; the fallback to zero; theTruefallback to~dependent) and ran the ACA, ACA contrib and nine state directories: 1,194 passed each time, so no test pins any of the three. - 7, microsimulation impact: the PR body still says "Pending". I ran the comparison below; the body can use it or its own run.
- 2, axiom issue: TheAxiomFoundation/rulespec-us#1627 is open with the
Suggestions
changelog.d/claimable-filer-ptc.fixed.mdsays "with zero allocation for empty enrollment". After this push a supplied premium with no enrollee found goes to the tax family. Add a sentence for the two supplied-input rules (pays_aca_premiumand the premium).- Round-1 suggestions 1 to 5 are unchanged. The push changes none of the files they concern.
What I checked
The new rules, by household (2026; this head against main at a93cc58d7e)
| Household | is_aca_marketplace_enrollee |
aca_ptc |
used_aca_ptc |
Same as main |
|---|---|---|---|---|
New Mexico witness, pays_aca_premium: true |
true | 5,016.40 | 5,016.40 | yes |
Adult aged 67, pays_aca_premium: true, is_medicare_eligible: true |
true | 5,016.40 | 5,016.40 | yes |
Adult aged 30, pays_aca_premium: true, is_aca_eshi_eligible: true |
true | 5,016.40 | 5,016.40 | yes |
Couple, pays_aca_premium: true for one spouse only, aca_magi: 31_725 |
true, true | 4,670.72 | 4,670.72 | yes |
Adult, pays_aca_premium: false, computed as enrolled |
true | 0 | 0 | yes |
Adult, pays_aca_premium: true, claimable elsewhere |
true | 0 | 0 | no (intended: this is the rule of the PR) |
Default adult, premium 5,000, aca_ptc: 1_000 |
false | 1,000 | 1,000 | yes |
Adult with Medicare, premium 5,000, aca_ptc: 1_000 |
false | 1,000 | 1,000 | yes |
| Adult with an employer offer, premium 5,000, computed credit | false | 0 | 0 | yes |
| Adult computed as enrolled, premium supplied as 0 | true | 5,016.40 | 0 | yes |
Adult claimable elsewhere, premium 5,000, aca_ptc: 1_000 |
false | 1,000 | 0 | no (intended) |
- A supplied
pays_aca_premium: truefor a person with Medicare or an employer offer gives a credit of 5,016.40. Main does the same, so the push does not cause it. The push only makesis_aca_marketplace_enrolleeagree with the supplied value. - A supplied
pays_aca_premium: falsedoes not remove a computed enrollee. That is also the same as the last head. - A supplied premium does not make anyone an enrollee, so it adds no member to
slcspand creates no computed credit. For the default adult with only a premium supplied,aca_ptcandused_aca_ptcare 0. has_input_for_periodanswers for the whole simulation. I found no household where this gives a wrong amount: a tax unit with no supplied premium has a premium of 0, and a person with no suppliedpays_aca_premiumhas false.
Microsimulation
-
The default dataset has no column for
pays_aca_premium,selected_marketplace_plan_premium_proxy,monthly_selected_marketplace_plan_premium_proxyoris_aca_marketplace_enrollee. Its ACA inputs aretakes_up_aca_if_eligibleandselected_marketplace_plan_benchmark_ratio. Its health premium columns (health_insurance_premiumsand others) are not read by either rule. -
In a
Microsimulationon the default dataset,has_input_for_periodis false for all four variables, before and after the calculation. So both new rules are inactive for population results: nobody becomes an enrollee or receives a credit because of an imputed premium. -
I confirmed it with a run. Weighted 2026 totals are identical at this head and at the last head (
b7841c8935):aca_ptc$144.7502bn,used_aca_ptc$47.0997bn,aca_ptc_coverage_family_premium$100.7288bn, 54.0744m enrollees. -
For round-1 Should 7, the whole PR against its merge base (
ac280b06ed), default dataset, one run each:Weighted total 2025 merge base 2025 this head 2026 merge base 2026 this head aca_ptc$170.0219bn $170.0219bn $144.7502bn $144.7502bn used_aca_ptc$60.2158bn $60.2158bn $47.0997bn $47.0997bn selected_marketplace_plan_premium_proxy$79.4978bn $79.4978bn $100.7288bn $100.7288bn marketplace_net_premium$19.2820bn $19.2820bn $53.6291bn $53.6291bn slcsp$352.3184bn $352.4290bn $444.5247bn $444.6575bn pays_aca_premium(people)52.7722m 52.7994m 53.4732m 53.5006m The credit totals do not move at this precision.
slcsprises by about $0.11bn (2025) and $0.13bn (2026) and about 27,000 more people pay an age-rated premium; this agrees with the covered-child ranking change. I did not break the difference down by cause.
Since-prior diff, line by line
is_aca_marketplace_enrollee.py:57-63: the read ofpays_aca_premiumhappens only for a supplied value, so there is no cycle with the formula ofpays_aca_premium. The result is the union of the computed and the supplied value.aca_ptc_coverage_family_premium.py:39-48and:82: the Python boolean combines correctly with the two arrays. The first branch of thewhereis unchanged, so households with a covered enrollee get the same result as at the last head.- The three new YAML cases match my hand checks: 6,000 - 0.0419 x 23,475 = 5,016.40; 6,000 - 60 = 5,940.
- Defects found: Should address 1 to 3 above.
Tests run at this head (Python 3.13, policyengine-core 3.34.10)
| Tests | Result |
|---|---|
policyengine_us/tests/policy/baseline/household/income/ |
224 passed |
policyengine_us/tests/policy/baseline/gov/aca/ and policyengine_us/tests/policy/contrib/aca/ |
570 passed |
| The nine state premium program directories (CA, CO, CT, MA, MD, NJ, NM, VT, WA) | 624 passed |
tests/core/test_claimable_filer_ptc_invariants.py and the three tests/unit/test_ca_premium_subsidy_*.py files |
34 passed |
policyengine_us/tests/policy/baseline/partners/ |
623 passed |
- Partner contract tests: the PR edits none of them, and all pass.
- CI at this head: 36 of 36 checks passed; the last one completed on 2026-11-12 at 9:41pm EST.
- GitHub reports the PR as mergeable (no conflict with main).
- The changelog fragment is at the top level of
changelog.d/.
Not verified
- I ran the tests on Python 3.13; CI uses 3.14. CI is green at this head.
- The microsimulation runs are one local run per commit on the default dataset. I did not run the microsimulation test suite locally (the CI check passed), and I did not run any other dataset.
- The sources that I could not open in the first review.
- For Should address 2, I did not run the nine state directories with each condition removed.
… review A supplied monthly premium now counts as a supplied premium. New cases pin each condition of the rule (enrollment supplied, an enrollee found, a tax family present), the monthly input, and the minimum premium reform's used_aca_ptc cap. The changelog names the two supplied-input rules. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@PavelMakarchuk, thanks. 6d336a2 covers Should address 1–3 and suggestion 1. Should address 1: the reform's
With the whole-policy read at line 76, the used credit would be 5,940. Should address 2: the three conditions.
Should address 3: monthly premiums. The rule now treats a supplied Suggestion 1: the changelog now names both supplied-input rules. Round-1 Should address 2–7 and suggestions 1–5 are still open and unchanged in this push. Thanks for running the microsimulation comparison for Should address 7. As before, I didn't run these locally, so CI is the check. |
Address the round-1 items that Pavel's 2026-11-13 review carried forward. - New cases pin the even-split fallback for a supplied annual premium, the CSR test of a premium payer's supplied PTC eligibility, and is_aca_filer's fallback when every member is a supplied dependent. - aca_ptc_coverage_family_premium returns early when no tax unit has an enrollee outside its tax family; the result is unchanged. - slcsp_age_curve_amount_person_uncapped uses has_input_for_period, and is_aca_tax_family_member reads is_dependent_elsewhere_without_filing_exception. - California references reach PDF page 3 for (d)(7)(B), (d)(10) and (d)(11). - The Colorado source-conflict candidate gets its URLs, quotes and code paths, and a new candidate records the Exchange "tax filer" reading. - Case names drop review-round prefixes, is_aca_tax_family_member.yaml drops its YAML anchors, and the NM and tax-family-size wording is fixed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@PavelMakarchuk, a follow-up to my 16:29 reply. d4ec8c5 takes round-1 Should address 2-7 and suggestions 2-5. Round-1 suggestion 1 isn't done (below). I haven't run tests locally; each new expected value is worked by hand in its case's comment, and CI will run them. Round-1 Should address
Round-1 suggestions
No formula result changes in this commit outside the new test cases. |
PavelMakarchuk
left a comment
There was a problem hiding this comment.
Verdict: APPROVE (head d4ec8c5cf8)
The two pushes (6d336a28dd, d4ec8c5cf8) close every open item from my last review and from my first review. I changed each pinned line back and a test now fails each time. I found no new defect in the diff since 18385c186b. No Critical or Should address item is open. Four optional suggestions remain.
Resolved since the last review
| Item | Status | How I checked |
|---|---|---|
Last review, Should 1: the reform's used_aca_ptc read at aca_ptc_minimum_premium.py:76 was not pinned |
Resolved | The new case at ptc_minimum_premium.yaml:303 gives aca_ptc_coverage_family_premium 4,000, aca_ptc 5,940, used_aca_ptc 3,940. With the whole-policy read at line 76: 1 failed, 11 passed (used_aca_ptc 5,940, expected 3,940). |
| Last review, Should 2: the three conditions of the supplied-premium rule were not pinned | Resolved | Three new cases in aca_ptc_coverage_family_premium.yaml (from line 569). I removed each condition in turn; each time 1 failed, 16 passed (table below). |
| Last review, Should 3: monthly premium inputs did not reach the rule | Resolved | aca_ptc_coverage_family_premium.py:45-52 now also tests each month of monthly_selected_marketplace_plan_premium_proxy. The California witness gives 6,000 / 1,000 / 5,000 (table below). Without the monthly test: 1 failed, 16 passed. |
| First review, Should 2: axiom issue was stale | Resolved | TheAxiomFoundation/rulespec-us#1627 is open with the pe-parity label. Its body was updated on 2026-11-13 at 11:46am EST to this head. It now has the refund-only exception (required output 2, case H) and household income (required output 3, cases I and J). It gives a reason for each rule it leaves to separate issues. |
| First review, Should 3: source-conflict register | Resolved | us-co:premium-assistance-ptc-recipients has both URLs, quoted text and three code paths. Reading 3 is the new candidate us:exchange-tax-filer-claimable-spouse. I opened both Colorado sources (below). |
| First review, Should 4: California reference pages | Resolved | The references open PDF page 2 and name pages 2-3. I read pages 1 to 3 of both Designs (below). |
| First review, Should 5: helpers and private Core API | Resolved | slcsp_age_curve_amount_person_uncapped.py:23 calls has_input_for_period; the private call and the fallback for older Core releases are gone. is_aca_tax_family_member.py:53-56 reads is_dependent_elsewhere_without_filing_exception. The PR body names the helpers that the PR reads. |
| First review, Should 6: three unpinned branches | Resolved | One new case for each branch. I changed each branch back; a test fails each time (table below). |
| First review, Should 7: microsimulation impact was "Pending" | Resolved | The PR body has the table and says which totals move. I ran this head again; the totals did not change (below). |
| Last review, suggestion 1: changelog | Resolved | changelog.d/claimable-filer-ptc.fixed.md names both supplied-input rules. |
| First review, suggestions 2 to 5 | Resolved | The New Mexico comment, the twelve case names, the YAML anchors, the wording and the early return are done. Suggestion 1 is open (below). |
Suggestions
- The two tier variables have no reader (first review, suggestion 1). No formula or reform reads
aca_ptc_slcsp_family_tier_categoryoraca_ptc_slcsp_family_tier_multiplier; onlyaca_ptc_slcsp.yamlasserts them. It is acceptable to keep them as outputs for the tests. If they stay, say in their documentation that they are reporting outputs and thataca_ptc_slcspdoes not read them. - Follow-up 1 of the PR body names only #10413. #10860 is also open for the same problem (issue #9482). Name both. The PR that merges second (this one or #10860) must put the coverage-year state lookup and the zero-guideline handling of #10860 into
aca_fpg, withaca_tax_family_sizeas the size, and keepaca_magi_fractionreadingaca_fpg, so the federal fraction and California's two direct reads use one poverty line. - The new register candidate is
unreviewed.us:exchange-tax-filer-claimable-spousesays the reading was not put to the project lead. Put it to them after the merge; the model result for a joint return with one claimable spouse depends on it. - The microsimulation paragraph of the PR body says that the commits after
18385c186bwere not rerun. They can now say that the 2026 totals atd4ec8c5cf8are the same (below).
What I checked
Mutation checks at this head. I changed one line at a time and ran the test file that holds the new case.
| Line changed | Change | Result | Failing value |
|---|---|---|---|
aca_ptc_minimum_premium.py:76 |
Read selected_marketplace_plan_premium_proxy |
1 failed, 11 passed | used_aca_ptc 5,940, expected 3,940 |
aca_ptc_coverage_family_premium.py:53-55 |
Remove "enrollment not supplied" | 1 failed, 16 passed | premium 5,000, expected 0 |
aca_ptc_coverage_family_premium.py:57 |
Remove ~tax_unit.any(enrolled) |
1 failed, 16 passed | premium 5,000, expected 0 |
aca_ptc_coverage_family_premium.py:57 |
Remove tax_unit.any(member) |
1 failed, 16 passed | premium 5,000, expected 0 |
aca_ptc_coverage_family_premium.py:47-52 |
Remove the monthly input test | 1 failed, 16 passed | premium 0, expected 6,000 |
marketplace_csr_eligible.py:27-31 |
Use the before-premium test only | 1 failed, 7 passed | marketplace_csr_eligible true, expected false |
aca_ptc_coverage_family_premium.py:78 |
Zero fallback in place of the even split | 1 failed, 16 passed | premium 0, expected 3,500 |
is_aca_filer.py:32 |
~dependent in place of the where |
1 failed, 21 passed | is_aca_filer [false, false], expected [true, false] |
aca_ptc_coverage_family_premium.py:60-64 |
Remove the early return | 224 passed (gov/aca/ptc and contrib/aca) |
none: the early return gives the same values |
Monthly premium witness (2026, California, one adult aged 40, aca_ptc: 1_000, no other inputs, so the model finds no enrollee):
| Premium input | selected_marketplace_plan_premium_proxy |
aca_ptc_coverage_family_premium |
used_aca_ptc |
marketplace_net_premium |
|---|---|---|---|---|
| Annual, 5,000 | 5,000 | 5,000 | 1,000 | 4,000 |
| Monthly, 500 in each month | 6,000 | 6,000 | 1,000 | 5,000 |
| Monthly, 500 for January to June only | 3,000 | 3,000 | 1,000 | 2,000 |
| Monthly, 500 for December only | 500 | 500 | 500 | 0 |
| Annual 5,000 and monthly 500 in each month | 5,000 | 5,000 | 1,000 | 4,000 |
| Annual 5,000 and monthly 500 for January to June | 5,000 | 5,000 | 1,000 | 4,000 |
Monthly 500 in each month, is_aca_marketplace_enrollee: false |
6,000 | 0 | 0 | 6,000 |
| Monthly 500 in each month, adult claimable elsewhere | 6,000 | 0 | 0 | 6,000 |
| Monthly 0 in each month | 0 | 0 | 0 | 0 |
| Monthly 500 for each month of 2025 only | 0 | 0 | 0 | 0 |
- The second row was 6,000 / 0 / 0 / 6,000 at the last head.
- When the annual and the monthly inputs are both supplied, the annual amount is the premium. This agrees with the documentation of
selected_marketplace_plan_premium_proxy. - A premium supplied for another year does not start the rule.
Since-prior diff, line by line
-
aca_ptc_coverage_family_premium.py:44-55: the input test is a Python boolean for the whole simulation, and it combines correctly with the two arrays. I ran one simulation with four tax units in Texas (2026), each with a supplied premium, once with annual and once with monthly inputs. The results are the same for both input types:Tax unit Premium aca_ptc_coverage_family_premiumDefault adult, no enrollee found 5,000 5,000 Joint; the claimable head is the only enrollee 4,000 0 Joint; both enrolled, the head claimable (equal benchmarks) 7,000 3,500 Single adult, no enrollee found 3,000 3,000 -
aca_ptc_coverage_family_premium.py:60-64: when no tax unit has an enrollee outside its tax family, the share is 1 for each unit, so the early return equals the full formula. The mutation check above confirms it. -
is_aca_tax_family_member.py:53-56:is_dependent_elsewhere_without_filing_exceptionisclaimable & ~exceptionat the person level (it is from #10024 on main). No ACA test supplies it. The projection throughperson.tax_unit.anyis unchanged. -
slcsp_age_curve_amount_person_uncapped.py:23:has_input_for_periodis the shared helper inpolicyengine_us/tools/general.py. This PR does not change that file. No variable or reform of this PR calls a private Core method. -
is_aca_tax_family_member.yaml:503-642: I parsed the file at both heads. The 21 old cases differ only in their names; one case is new. -
New expected values, by hand: 8,000 x 6,000 / 12,000 = 4,000 and 4,000 - 60 = 3,940; 12 x 500 = 6,000; 12 x (7,000 / 12) x 0.5 = 3,500.
-
No parameter value changed.
threshold.yamlchanges only two reference titles. -
Defects found: none.
Axiom line
-
The line
axiom: us:statutes/26/36B/c/1/D queued (TheAxiomFoundation/rulespec-us#1627)has the correct form. -
The issue is open, has the
pe-paritylabel, and names this head. -
I ran its companion cases B, F and H in the model (2026 joint return,
aca_magi: 23_475, benchmark 6,000 for each member). The model agrees with the issue:Case aca_tax_family_sizeaca_fpgaca_ptcB: one claimable spouse 1 15,650 5,016.40 F: as B with one child 1 15,650 5,016.40 H: as F, the claimant files only for a refund 2 21,150 11,507.03 -
The issue leaves premium allocation, covered-child rating, shared-policy CSR and the nine state programs to separate issues. Its reason holds: the rulespec-us pipeline takes the enrolled premium and the benchmark from the caller, and it has no CSR module and no module for these state programs.
Source-conflict register
us-co:premium-assistance-ptc-recipients: PDF page 1002 of the 2024 C.R.S. Title 10 file shows 10-16-1205(1)(b)(II) with the quoted text. PDF page 2 of Amended Regulation 4-2-78 shows Section 4C ("Eligible enrollee") with the quoted text, and page 1 shows the quoted Section 2 text.us:exchange-tax-filer-claimable-spouse: the entry has three sources, two readings, an example (5,016.40, the same as case B above) and two code paths. All code paths in both entries exist.policyengine_us/tests/code_health/test_source_conflicts.pyandtest_known_dead_reference_urls.py: 143 passed.
California reference pages (2026 Program Design and Final 2027 Program Design)
| Provision | PDF page, 2026 | PDF page, 2027 | Value on the page |
|---|---|---|---|
| (a), (b) | 1 | 1 | subsidy formula |
| (c)(2) | 2 | 2 | "Meets all eligibility requirements of the federal advance premium tax credit" |
| (d)(4) table | 2 | 2 | 2026: 0% to 150%, 3.19% to 3.91% above 150% up to 165%. 2027: 2.8% to 5.28% above 150% up to 200% |
| (d)(5) | 2 | 2 | benchmark has its section 36B meaning |
| (d)(7) | 2 | 2 | "at or below 165 percent" (2026), "at or below 200 percent" (2027) |
| (d)(7)(B), (d)(10), (d)(11) | 3 | 3 | dependent rule, family size, poverty level |
ca_premium_subsidy_eligible.py:11-20,ca_premium_subsidy_applicable_percentage.py:12-19,ca_premium_subsidy.py:20-25andthreshold.yaml:30-35agree with this table.- The thresholds 1.5, 1.65 (2026) and 2.0 (2027) are on page 2 of each Design.
Microsimulation (default dataset, weighted 2026 totals, one run for each commit)
| Total | 18385c186b |
d4ec8c5cf8 |
|---|---|---|
aca_ptc |
$144.7502bn | $144.7502bn |
used_aca_ptc |
$47.0997bn | $47.0997bn |
aca_ptc_coverage_family_premium |
$100.7288bn | $100.7288bn |
| Marketplace enrollees | 54.0744m | 54.0744m |
- The totals did not change. They are the same as in my last review.
has_input_for_periodis false forpays_aca_premium,selected_marketplace_plan_premium_proxy,monthly_selected_marketplace_plan_premium_proxyandis_aca_marketplace_enrolleeon the default dataset, so the supplied-input rules stay inactive for population results.
Tests run at this head (Python 3.13, policyengine-core 3.34.10)
| Tests | Result |
|---|---|
policyengine_us/tests/policy/baseline/household/income/ |
224 passed |
policyengine_us/tests/policy/baseline/gov/aca/ and policyengine_us/tests/policy/contrib/aca/ |
578 passed |
| The nine state premium program directories (CA, CO, CT, MA, MD, NJ, NM, VT, WA) | 624 passed |
tests/core/test_claimable_filer_ptc_invariants.py and the three tests/unit/test_ca_premium_subsidy_*.py files |
34 passed |
policyengine_us/tests/policy/baseline/partners/ |
623 passed |
The two register test files in tests/code_health/ |
143 passed |
- The ACA count is 570 at the last head plus the 8 new cases.
- Partner contract tests: the PR edits none of them.
Merge with main
- The PR merges into main at
5cc0d58afawith no conflict. On that merge,gov/aca,contrib/acaandhousehold/incomegive 802 passed. - Main moved by six commits after
5cc0d58afa. One of them changessource_conflicts/candidates.yaml, which this PR also changes. GitHub reports the PR as mergeable.
CI and repository rules
- CI at this head: 36 of 36 checks passed; the last one completed on 2026-11-13 at 1:00pm EST.
- The changelog fragment is at the top level of
changelog.d/. - The PR adds no program, so
policyengine_us/programs.yamlneeds no change.
Not verified
- I ran the tests on Python 3.13; CI uses 3.14. CI is green at this head.
- The microsimulation comparison is one local run for each commit on the default dataset, for 2026 only. I did not run the microsimulation test suite locally (the CI check passed), and I did not run another dataset or 2025 at this head.
- For each mutation I ran the test file (or the two directories) named in the table, not the full suite.
- I did not run the tests on a merge with the newest main, only with
5cc0d58afa. I did not run the register tests on the merge. - In TheAxiomFoundation/rulespec-us#1627, I ran cases B, F and H. I did not run cases A, C, D, E, G, I and J, and I did not check the corpus rows that the issue names.
- For the register entry
us:exchange-tax-filer-claimable-spouse, I did not open the three federal sources again; I read 26 CFR 1.36B-1(d) in my first review. - The sources that I could not open in the first review (956 CMR 12.00, Connecticut Public Act 21-2, the New Mexico MIH Addendum, Publication 501, the Form 1040 instructions).
#10041 moved the guideline lookup into aca_fpg and the enrollment conditions of pays_aca_premium into is_aca_marketplace_enrollee. Resolve by carrying this PR's changes to where they now live, as Pavel's review laid out: - aca_fpg reads the state group for the coverage year and lags only the guideline (1.36B-1(h); Form 8962 line 4). - aca_magi_fraction reads aca_fpg and keeps the zero-guideline guard and the multiply-first truncation. - is_aca_marketplace_enrollee carries the no-Exchange test for Guam, Puerto Rico and the Virgin Islands; pays_aca_premium is main's. - The coverage-year family-size case now supplies aca_tax_family_size, which aca_fpg reads since #10041. - Both sides' test cases are kept. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Main's 19 new commits include the premium tax credit rules for a filer who can be claimed as a dependent (#10041). They add four items to source_conflicts/candidates.yaml and edit three others, none of which this branch changes, and Git merged the file without a conflict. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
…age-year state Per Pavel's re-review at d590964: - S1: since #10041, aca_magi_fraction divides by the stored aca_fpg, and in 32-bit arithmetic 35,369 / 15,650 x 100 rounds to exactly 226, so that case no longer caught a divide-first formula. Use 36,621 (234% of 15,650), which divide-first floors to 233. - The claimable-filer invariant compares aca_fpg with the prior year's guideline for the coverage year's tax family size and state group, not with tax_unit_fpg for the prior year, which reads the prior year's state. - The territory cases assert is_aca_marketplace_enrollee, and their comments name where the comparisons now live. - is_aca_marketplace_enrollee documents that a supplied pays_aca_premium still marks an enrollee in the territories, with no credit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Main's #10041 rewrote the note on Colorado's premium-assistance item, directly above the District of Columbia childless EITC item this branch adds. Keep main's note and the branch's item. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Summary
Someone another taxpayer can claim as a dependent gets no premium tax credit, and a joint return where another taxpayer can claim one spouse computes the credit for the other spouse alone. The model applied neither rule:
is_aca_ptc_eligibleignored whether another taxpayer could claim a filer, andaca_ptc, the benchmark premium, the poverty line, household income and the premium cap all counted the whole tax unit.The PR reads
claimable_as_dependent_on_another_return(from #10024: a deduction for the person is allowable to another taxpayer, whether or not that taxpayer claims it). Form 1040 (2025) instructions, line 12a, p.33 has the box checked "If you or your spouse ... can be claimed as a dependent on someone else's return", with no refund-only carve-out, and Form 8962 line 1 keys on that box. The return still claims its dependents when the would-be claimant meets the refund-only filing exception (dependent_claimant_filing_exception; Form 1040 dependents worksheet, p.18; Publication 501's Dependent Taxpayer Test).From #10024,
is_aca_tax_family_memberreadsclaimable_as_dependent_on_another_returnandis_dependent_elsewhere_without_filing_exception(claimable, and the would-be claimant doesn't meet the refund-only exception). No formula in this PR reads #10024's tax-unit helpers.Federal change
is_aca_tax_family_member(new, person),is_aca_filer(new, person)is_aca_fileruses supplied or inferred head/spouse roles and completes an incomplete joint-filer set without an adult-age cutoff. It infers the oldest member, or the two oldest on a joint return, respecting supplied dependency hints when some member isn't a supplied dependent. The same inference identifies filers for household income. An age tie between a filer and dependent needs explicit head/spouse roles. A dependent another taxpayer could also claim stays in the family of the return that claims them.aca_tax_family_size(new)tax_unit_sizealone.aca_fpg(new)aca_tax_family_size. A return where another taxpayer can claim every filer has no tax family; it keeps the tax unit's guideline as a numerical convention. The credit reads $0 there, and other readers ofaca_fpgsee the old value. In 2015, the first modeled year, the tax year's guideline stands in (the guard from #10773, moved here so California's two direct reads get it too).aca_magi_fractionaca_fpg(wastax_unit_fpgfor the prior year, which also took the household size from the prior year; the size now comes from the tax year, as line 1 requires, which differs only when a user supplies a prior-year size).aca_magimedicaid_magidirectly, including a supplied value. When exclusion is necessary, it reconstructs AGI plus MAGI additions, removes the excluded amounts, then floors once at zero; this also handles signed Social Security. Medicaid calculations remain separate.is_aca_ptc_eligible,is_aca_ptc_eligible_before_premium(new)is_aca_ptc_eligibleadditionally requirespays_aca_premium. This lets CSR distinguish an insured fourth child from someone outside the tax family.aca_ptc_below_fpl_immigration_exceptionis_aca_coverage_family_member(new),is_aca_marketplace_enrollee(new)pays_aca_premiumof true marks an enrollee.aca_child_index,pays_aca_premiumaca_ptc_slcsp(new), withaca_ptc_slcsp_family_tier_categoryand_multiplierslcsp_age_curve_amount_personprices, including positive and zero quotes for enrolled nonpayers, flow through an uncapped-price variable. Without a supplied quote, the model reprices a child freed from the original family's cap. When every enrollee belongs to the tax family,aca_ptc_slcspreadsslcspdirectly, including supplied values.slcspstill prices the whole enrollment.aca_ptc,aca_ptc_before_filing_gateaca_ptcfiling-gate wrapper remains intact. Itsaca_ptc_before_filing_gatecalculation usesaca_ptc_slcsp; the filing decision and take-up behavior from main remain in the dependency path.aca_ptc_coverage_family_premium(new),used_aca_ptccoverage_family_benchmark / (coverage_family_benchmark + other_family_benchmark), pricing the other enrollees separately; then sum the allocated monthly amounts (1.36B-3(a), (h)(1)). This applies to silver and bronze. The share is one when every enrollee is in the family. A premium supplied for the year or for any month, for a tax unit in which the model finds no enrollee and withis_aca_marketplace_enrolleenot supplied, is the tax family's, as before enrollment was modeled; otherwise the allocation reads zero when no family member enrolls, and a tax unit with no tax family gets none.marketplace_csr_eligibleis_aca_ptc_eligiblevalues continue to apply to premium-paying enrollees.slcsp_family_tier_category,slcsp_family_tier_multiplieraca_ptc_700_fpl_cliff,aca_ptc_additional_bracket,aca_ptc_simplified_bracketreformsis_aca_ptc_eligibleand none changes 36B(c)(1)(D), so each keeps the gate. Without it the reforms would pay claimable filers.aca_ptc_minimum_premiumreformaca_ptc_slcsp. The enrolled premium in its cap is the larger of that benchmark andaca_ptc_coverage_family_premium, and itsused_aca_ptccaps the credit ataca_ptc_coverage_family_premiumless the minimum premium.selected_marketplace_plan_premium_proxykeeps its annual input API and sumsmonthly_selected_marketplace_plan_premium_proxy. The monthly allocation uses the month-specific selected plan prices. When a user supplies only an annual selected premium, the model distributes that total in proportion to the monthly selected-plan proxies; if those proxies are all zero, it distributes the total evenly. This timing assumption preserves the supplied annual amount. A user who knows the timing can supply monthly premiums through the new monthly variable. Either way, a supplied premium for a tax unit in which the model finds no enrollee, with enrollment not supplied, goes to the tax family.Cost-sharing eligibility per person requires APTC eligibility (45 CFR 155.305(g)(1)), and 155.305(f)(1)(ii) requires "a member of the tax filer's family within the meaning of 26 CFR 1.36B-1(d)".
Worked cases (
aca_ptc.yaml), $500 a month benchmark per enrollee, contiguous US, both spouses aged 30:The joint fixtures with a positive credit test the rule given the flag; see the note under the readings.
Before this PR, the joint one-claimable couple got the control's credit, and the single claimable filer got $6,000 (2025).
State premium programs, each checked against its own law
aca_ptc_slcspaca_ptc_slcspaca_ptc_slcsp. See reading 2aca_ptc_slcspaca_ptc_slcsp; eligibility comment updated. The band test and the rate lookup now divide byaca_fpg, the poverty line the federal credit uses. (d)(10) of the 2026 and Final 2027 Designs both give family size its section 36B meaning, and (d)(11) of both takes the guideline most recently published when open enrollment startsaca_ptc_slcspnm_premium_assistance_mih_eligiblenow needs a coverage family memberaca_ptc_slcspnj_njhps_member_eligiblenow needs a tax family member (it does not readis_aca_ptc_eligible, to keep the 400-600% band); cap usesaca_ptc_slcsp. #9515 also edits these two files (citations only); land it firstslcsp, since their premium is in the household's net premium. See reading 1The PR leaves Colorado OmniSalud alone: it serves undocumented enrollees and reads neither the credit nor its eligibility. The Basic Health Program payment proxy reads
aca_magi_fractionand so now uses the tax family's poverty line, as its documentation says it mirrorsaca_ptc; the PR leaves its enrollee premiums alone (follow-up 2).All modeled state programs start in 2026, so the state changes affect 2026 on.
The 2026 state cases use a joint couple aged 30 with one spouse claimable, MAGI of $23,475 and a $500 a month benchmark each, which gives a federal credit of $5,016.40. Each runs under both labellings, beside the nobody-claimable control and the both-claimable case. Before this PR the model treated this couple as the control in every program.
Readings
Max accepted readings 1 to 4 as built. Reading 5 sets a timing assumption for annual-only premiums.
Readings 2 and 3 are source conflicts, so
source_conflicts/candidates.yamlrecords them asus-co:premium-assistance-ptc-recipients(now with its statute and regulation texts and code paths) andus:exchange-tax-filer-claimable-spouse. Readings 1 and 4 fill gaps where no source gives the alternative, so they aren't source conflicts and have no candidate.Another taxpayer can claim a married person only if the couple files jointly "only to claim a refund of income tax withheld or estimated tax paid". Publication 501 Example 3 (p.11) holds that claiming a refundable credit defeats that. We infer that the example reaches the premium tax credit, so a joint return that claims a positive credit while one spouse is claimable may not arise in practice. The model takes the flag as given and applies the line 1 rule, as it does for recovery rebates.
is_aca_tax_family_member's documentation now says so.Invariants
policyengine_us/tests/core/test_claimable_filer_ptc_invariants.pychecks items 1 to 3 with Hypothesis on generated roleful returns in each year 2021-2026, with spouses claimable, claimed or neither, and with and without the refund-only exception. It also runs a seeded population in 2025 and 2026, in Texas, New York, Vermont and the nine wrap states, on #10024's household builders. Separate YAML cases cover role-less minor filers, supplied inputs, changing monthly prices and empty enrollment:aca_fpg,aca_magi,aca_magi_fraction,slcsp,aca_ptc_slcspandaca_ptcunchanged. It also leaves the coverage-family premium, the used credit, CSR eligibility, the number of eligible members and all nine state amounts unchanged.aca_tax_family_sizeequals the filers no other taxpayer can claim, plus the dependents if one filer counts as an applicable taxpayer and none is claimable outside the exception.aca_fpg.yamltests the cases where the current-year family size differs from the old prior-year size. Separate YAML cases test role-less filer corrections and explicit supplied inputs.aca_fpg.policyengine_us/tests/unit/test_ca_premium_subsidy_one_poverty_line_properties.pydraws incomes in 2026 and 2027 for a couple with one claimable spouse (tax family of one, tax unit of two) and lets the model computeaca_fpg. It checks eligibility against the Design band and the rate against the Design interpolation over the one-person guideline, in exact arithmetic. YAML witnesses inca_premium_subsidy.yamlcover $20,000, $25,000 (both labellings) and $27,000 in 2026 and $35,000 in 2027; each lands differently over the tax unit's two-person line.The seeded test also asserts it reaches the rule: returns with one claimable filer and a positive credit, and in 2026 a positive state wrap.
YAML cases also pin each condition of the supplied-premium rule (enrollment supplied, an enrollee found outside the family, no tax family), a premium supplied by month, the even split of an annual premium with no monthly price pattern, the CSR test of a premium payer's supplied
is_aca_ptc_eligible,is_aca_filer's fallback when every member is a supplied dependent, and the minimum premium reform'sused_aca_ptccap. The comments of the last three cases (even split, CSR, filer fallback) say what the changed line would return.Tests
At
e03b7ec97a(main merged at0315452583), 1,121 tests passed across 88 files, one file per process with numeric threads capped.tests/policy/baseline/gov/states/ca/hbex/premium_subsidy/) and its three unit files (test_ca_premium_subsidy_applicable_percentage.py,test_ca_premium_subsidy_eligible_properties.py,test_ca_premium_subsidy_one_poverty_line_properties.py)tests/policy/baseline/gov/aca/)tests/core/test_claimable_filer_ptc_invariants.pyAt
18385c186b, Pavel's runs (Python 3.13, policyengine-core 3.34.10) passed the federal ACA and ACA contrib directories (570), the nine state premium program directories (624),tests/policy/baseline/household/income/(224), the invariant file with the three California unit files (34) and the partner contract tests (623), and CI passed 36 of 36 checks.6d336a28ddadds five cases for Pavel's Should address 1-3.d4ec8c5cf8renames twelve cases that carried review-round names, writes out the six cases that used YAML anchors, and adds three cases. CI runs them.California now divides by
aca_fpgin both its band test and its rate lookup. Its tests supply that denominator asaca_fpgfor the test's own period (63 YAML pins and both unit files), and no expected output changes. Five YAML witnesses (2026 at $20,000, $25,000 under both labellings and $27,000; 2027 at $35,000) and the new property file fail if either California read returns to the tax unit's guideline oraca_fpgtakes the tax unit's size.Two fixtures needed new inputs; their assertions did not change. Three New Mexico MIH integration scenarios supply
aca_magiwithout income, so Medicaid treats them as having no income and they don't enroll. They have failed since this PR's MIH gate began requiring a coverage-family member; CI skipped the draft at09544440a7, so no run showed it. They now supply Marketplace enrollment. The invariant file now supplies its signed Social Security dependent as thesocial_securitytotal, because #10484 requires reported components to be nonnegative.This comment holds the review history through r4 and the earlier merge with main.
Microsimulation impact
Pavel Makarchuk ran this PR against its merge base (
ac280b06ed) on the default dataset, one run each, at18385c186b(review of 2026-11-13):aca_ptcused_aca_ptcselected_marketplace_plan_premium_proxymarketplace_net_premiumslcsppays_aca_premium(people)slcsprises by $0.11bn in 2025 and $0.13bn in 2026, and about 27,000 more people pay an age-rated premium (27,200 in 2025, 27,400 in 2026). Pavel notes that this agrees with the covered-child ranking, under which an older child who isn't enrolled no longer takes one of the three child-premium slots; he didn't break the difference down by cause. The credit totals (aca_ptc,used_aca_ptc) and the premium totals don't move at this precision.The default dataset sets the dependency inputs for nobody (follow-up 3) and has no column for
pays_aca_premium,selected_marketplace_plan_premium_proxy,monthly_selected_marketplace_plan_premium_proxyoris_aca_marketplace_enrollee, so neither the claimable-filer rules nor the two supplied-input rules move it. The commits after18385c186bweren't rerun; they add no dataset input and their formula changes compute the same values for it.Follow-ups (separate work)
tax_unit_fpg(period.last_year)does the same, and this PR keeps that read so it changes nothing else. Use current ACA household with prior-year poverty guidelines #10413 (issue ACA applicable percentage uses the contiguous-states poverty guideline for Alaska and Hawaii #9482) fixes this inaca_magi_fraction; whichever lands second should move the fix intoaca_fpg, so California's two direct reads get it too.medicaid_income_levelwhere 42 CFR 600.305 uses household income for "the applicable family size".aca_magi(throughmedicaid_magi) adds dependents' tax-exempt interest and all their Social Security, but 1.36B-1(e)(1)(ii) counts another family member's income only if they "are required to file a return". Main does the same, and this PR removes only out-of-family dependents' amounts.tax_unit_fpg(period.last_year). Whichever lands second should divide byaca_fpg, so each program uses the poverty line its credit uses (invariant 4).axiom: us:statutes/26/36B/c/1/D queued (TheAxiomFoundation/rulespec-us#1627)
🤖 Generated with Claude Code