Skip to content

Use the Alaska and Hawaii poverty guidelines in the premium tax credit's income fraction - #10860

Open
DTrim99 wants to merge 9 commits into
mainfrom
aca-fpl-coverage-year-state
Open

DTrim99 wants to merge 9 commits into
mainfrom
aca-fpl-coverage-year-state

Conversation

@DTrim99

@DTrim99 DTrim99 commented Nov 13, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Fixes #9482. The premium tax credit's income fraction divides ACA MAGI by the prior year's poverty guideline (aca_fpg, since #10041). The lookup also read the household's state for the prior year, so a household whose state is supplied only for the coverage year fell back there to the default state, which is in the contiguous group. Every API request is such a household, and so are most YAML tests. As a result, Alaska and Hawaii households got the contiguous states' guideline. In the issue's example, an Alaska single filer's credit ended at 4 × $15,650 = $62,600 of MAGI instead of 4 × $19,550 = $78,200.

What changes

Effects

  • Alaska and Hawaii households whose state is supplied only for the coverage year now get a lower income fraction. Two examples:
  • For most of them that means a lower required contribution, a larger credit, and eligibility up to 400% of their own guideline. But an Alaska or Hawaii household between 100% of the contiguous guideline and 100% of its own now falls below the floor and, from 2026, when the below-poverty exception ends, loses eligibility: an Alaska single adult with aca_magi: 18_000 and the Medicaid, CHIP, Medicare and employer-coverage inputs false goes from 1.15 to 0.92. Alaska and Hawaii expanded Medicaid, so a household computed from earnings at that income is Medicaid-eligible and ineligible for the credit on both trees; the households that lose the credit under the floor are those barred from Medicaid for another reason.
  • Cost-sharing-reduction tiers (marketplace_csr_eligible, marketplace_csr_category) move with the fraction.
  • A household whose state changes between the two years now uses the coverage year's state: Alaska in 2025 and Texas in 2026 at $30,000 goes from 1.53 to 1.91. A state supplied only for the year before the coverage year no longer reaches the fraction, so the coverage year takes the default state: Alaska supplied for 2025 only goes from 1.53 to 1.91.
  • Territory households pay no Marketplace premium and are not eligible for the credit, in every year checked from 2015 to 2026 (and 2027 for Guam and the Virgin Islands; a Puerto Rico household in 2027 raises an unrelated ValueError from snap_region on main and here). On main, one whose state was supplied only for the coverage year got the contiguous guideline and could be eligible (2024 coverage at $30,000: 30,000 / 14,580, floored to 2.05); one whose state was supplied for both years already had an infinite fraction and was not eligible.
  • Microsimulation: input carry-over runs forward only, so a dataset household differs from main only when the coverage year is the dataset's own year and its state isn't visible a year earlier. The review ran enhanced_cps_2024 after 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 merged: in 2024, aca_fpg changes for 434 tax units, all in Alaska and Hawaii; aca_ptc changes for 18 of them, always upward, and rises by $2.93m ($62,542.37m to $62,545.30m); 2025 and 2026 don't change. The dataset has no territory rows.
  • 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 merged first, so this PR carries its change to where 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 put the code: the coverage-year state into aca_fpg, the zero-guideline guard and multiply-first truncation into aca_magi_fraction, and the no-Exchange test into is_aca_marketplace_enrollee. California's premium subsidy reads aca_fpg too, so it also takes the coverage-year state; California is contiguous, so no result changes.
  • Partner contract tests: two have Alaska households, but they assert only WIC and school-meal results (partners/analytics_coverage/edge_cases/federal/nutrition/{wic,school_meals}/federal.yaml), which don't read aca_magi_fraction. None has a Hawaii or territory household. The PR edits no partner file.

Related PRs

Left for follow-ups

  • Income exactly at 400% from 2026: 36B(c)(1)(A) says "does not exceed 400 percent", and Worksheet 2 gives 400 for MAGI equal to 4 × the guideline, but the 4.00 bracket of ptc_income_eligibility is false from 2026, so MAGI of exactly $78,200 for an Alaska single filer is denied. That needs the unrounded comparison and is outside this PR; Treat income exactly at 400% FPL as premium tax credit eligible #10312 makes it.
  • The Northern Mariana Islands: state_group puts MP in the contiguous group, so a 2026 MP single adult at $30,000 has a fraction of 1.91 and is_aca_ptc_eligible true, on main and here. 42 U.S.C. 18024(d) excludes it as it does the other territories.
  • Puerto Rico in 2027: snap_region raises ValueError: Unsupported array dtype: float64 when the ACA variables are computed, on main too.
  • The first-modeled-year guard: once Add premium tax credit rules for 2014–2017 #10846 adds the 2014 guideline, it can go, and 2015 coverage can use the guideline 36B(d)(3)(B) calls for.
  • pell_grant_eligibility_type reads tax_unit_fpg two years back with the same lagged-state pattern; I haven't checked which state and year the Pell rules call for.

Tests

  • aca_magi_fraction.yaml:
    • 2026 coverage: an Alaska single filer (1.53); a Hawaii single filer (1.66); an Alaska family of three (1.50, Alaska's per-person increment).
    • A mover, Alaska in 2025 and Texas in 2026 (1.91).
    • aca_tax_family_size of 3 supplied for 2026 only (1.50; the 2025 size would give 2.55).
    • 2025 coverage in Alaska, from the 2024 guideline (1.59).
    • Alaska and Texas households in one simulation ([1.53, 1.91]).
    • Texas at $35,369 in 2026, exactly 226% of $15,650 (2.26; dividing first gave 2.25).
    • Negative MAGI in Alaska (0), from Use current ACA household with prior-year poverty guidelines #10413.
  • test_claimable_filer_ptc_invariants.py: the identity for returns with no claimable filer now compares aca_fpg with the prior year's guideline for the coverage year's tax family size and state group, in place of the prior-year tax_unit_fpg, which reads the prior year's state. Its drawn states are all contiguous, so it passed either way; AK or HI could now join them.
  • is_aca_ptc_eligible.yaml, all with the Medicaid, CHIP, Medicare and employer-coverage inputs false:
    • a 2026 Alaska single filer at $70,000 is eligible (3.58; the contiguous guideline gave 4.47);
    • at $19,549 (0.99) is not, below the floor;
    • at $78,199 (3.99) is;
    • a 2024 Puerto Rico single filer pays no Marketplace premium and is not eligible. On main the state supplied for one year gave the contiguous guideline (2.05, eligible); without the no-Exchange test pays_aca_premium would be true;
    • a 2026 Puerto Rico single filer with no income has a required contribution of 0. Plain division would give NaN, and a fraction of 0 would give the 2026 initial rate, 0.021;
    • 2026 Guam and Virgin Islands single filers at $30,000 pay no Marketplace premium and are not eligible; each case fails without its state-group comparison.
  • I didn't run these locally (the laptop is short on memory), so CI is the check.

axiom: TheAxiomFoundation/rulespec-us#2210 queued (the applicable poverty guideline by state of residence under 26 CFR 1.36B-1(h) and Form 8962 Line 4, and the territory exclusion under 36B(c)(2)(A) and 42 U.S.C. 18024(d); the coverage-year family size is TheAxiomFoundation/rulespec-us#1627)

🤖 Generated with Claude Code

…t's income fraction

aca_magi_fraction read the household's state for the guideline year, so a
state supplied only for the coverage year fell back to the default state
there. It now reads the state for the coverage year and lags only the
guideline (26 CFR 1.36B-1(h)). Fixes #9482.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@codecov

codecov Bot commented Nov 13, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (aae7f98) to head (d5f3f7a).
⚠️ Report is 233 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff            @@
##              main    #10860   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files            1         3    +2     
  Lines           37        59   +22     
  Branches         0         2    +2     
=========================================
+ Hits            37        59   +22     
Flag Coverage Δ
unittests 100.00% <100.00%> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@DTrim99

DTrim99 commented Nov 13, 2026

Copy link
Copy Markdown
Collaborator Author

PR Review: Use the Alaska and Hawaii poverty guidelines in the premium tax credit's income fraction

PolicyEngine/policyengine-us #10860, head ccc11f9be0e76b28120412aada575cea942e1e25, base main at a93cc58d7e. I wrote this PR, so this is a self-review against the law, the IRS instructions, the HHS guidelines and the repository's rules. I ran no tests or simulations; every number below is a hand trace through the code at the head, and every quotation was re-read from the source today.

Summary

The fix is right. aca_magi_fraction now reads the state group for the coverage year and lags only the guideline date, through the existing fpg() helper. That matches 26 CFR 1.36B-1(h) and the Form 8962 Line 4 instructions. All four new expectations (1.53, 1.66, 1.50, and 3.58 with eligibility) are correct against the 2025 HHS guidelines, and each would fail on main. The quick-feedback job ran the whole gov/aca folder and passed it (349 tests).

Three things should change before merge:

  1. Puerto Rico, Guam and Virgin Islands households whose state is supplied only for the coverage year now reach the territory placeholder guideline. For coverage years 2015 to 2024 that raises an error, and household_net_income reaches this lookup. From 2025 the fraction is infinite.
  2. The Form 8962 references point at the wrong pages of the edition the link now serves, and the tuple breaks the repository's reference rules. The new documentation string counts on the order of those references.
  3. The PR description says households whose state is supplied for both years are unaffected, which isn't true for households that moved between years. It also leaves out Alaska and Hawaii households that fall below 100% of their own guideline and lose eligibility from 2026.

Counts: 0 critical, 3 should address, 7 suggestions.

Sources read

Source What it settles Location
26 U.S.C. 36B(d)(3)(B), https://www.law.cornell.edu/uscode/text/26/36B#d_3 "the poverty line used shall be the most recently published poverty line as of the 1st day of the regular enrollment period for coverage during such calendar year." (d)(3)(B)
26 U.S.C. 36B(c)(1)(A) and (E), https://www.law.cornell.edu/uscode/text/26/36B#c_1 "household income for the taxable year equals or exceeds 100 percent but does not exceed 400 percent of an amount equal to the poverty line for a family of the size involved". (E) suspended the 400% cap for 2021 to 2025. (B), the below-poverty exception for lawfully present immigrants, was repealed by P.L. 119-21 §71302. (c)(1)
26 U.S.C. 36B(c)(2)(A) and (d)(1), same page The plan must be "enrolled in through an Exchange established by the State under section 1311". Family size counts the individuals claimed under section 151 "for the taxable year". (c)(2)(A), (d)(1)
26 CFR 1.36B-1(h), https://www.law.cornell.edu/cfr/text/26/1.36B-1#h "the Federal poverty line for computing the premium tax credit for a taxable year is the Federal poverty line in effect on the first day of the initial or annual open enrollment period preceding that taxable year. ... If a taxpayer's primary residence changes during a taxable year from one state to a state with different Federal poverty guidelines or married taxpayers reside in separate states with different Federal poverty guidelines (for example, Alaska or Hawaii and another state), the Federal poverty line that applies ... is the higher Federal poverty guideline". paragraph (h)
26 CFR 1.36B-1(d)(1), same page "A taxpayer's family means the individuals for whom a taxpayer properly claims a deduction for a personal exemption under section 151 for the taxable year." paragraph (d)(1)
45 CFR 155.300(a), https://www.law.cornell.edu/cfr/text/45/155.300 Defines FPL for cost-sharing reductions with the same open-enrollment lag. definition of "Federal poverty level or FPL"
Instructions for Form 8962 (2025), https://www.irs.gov/pub/irs-prior/i8962--2025.pdf File page 7: married-filing-separately exceptions and "Specific Instructions", Line 1. File page 8: Line 4 ("Check the box to indicate your state of residence in 2025. ... (For 2025, the 2024 federal poverty lines are used for this purpose and are shown below.) If you moved during 2025 and you lived in Alaska and/or Hawaii, or you are filing jointly and you and your spouse lived in different states, use the table with the higher dollar amounts for your family size.") and Table 1-1. File page 9: Tables 1-2 and 1-3, and Worksheet 2 ("Is the amount on line 1 more than the amount on line 3? Yes. ... Enter 401" / "Do not round; instead, multiply this number by 100 ... and then drop any numbers after the decimal point. For example, for 0.9984, enter the result as 99"). The unversioned https://www.irs.gov/pub/irs-pdf/i8962.pdf is byte-identical to this file (same SHA-256). file pages 7, 8, 9
Instructions for Form 8962 (2022), https://www.irs.gov/pub/irs-prior/i8962--2022.pdf Line 4 starts at the foot of file page 6 and continues on page 7, with the same state-of-residence text. Worksheet 2 is on file page 8. file pages 6 to 8
HHS poverty guidelines, 2025: 90 FR 5917 (2025-01-17), https://www.federalregister.gov/documents/2025/01/17/2025-01377 Contiguous $15,650 + $5,500; Alaska $19,550 + $6,880 (three people: $33,310); Hawaii $17,990 + $6,330. Page 5918: "the poverty guidelines are not defined for Puerto Rico or other outlying jurisdictions. In cases in which a Federal program using the poverty guidelines serves any of those jurisdictions, the Federal office that administers the program is generally responsible for deciding whether to use the contiguous-states-and-DC guidelines for those jurisdictions or to follow some other procedure." pp. 5917 to 5918
HHS poverty guidelines, 2026: 91 FR 1797 (2026-01-15), https://www.federalregister.gov/documents/2026/01/15/2026-00755 Contiguous $15,960 + $5,680; Alaska $19,950 + $7,100; Hawaii $18,360 + $6,530. p. 1797 onward
HHS poverty guidelines, 2024 Form 8962 (2025) Tables 1-1 to 1-3 reprint them: $15,060 + $5,380; Alaska $18,810 + $6,730; Hawaii $17,310 + $6,190. i8962 (2025), file pages 8 to 9
Repository rules CLAUDE.md, "Parameter and variable references"; CONTRIBUTING.md, "Source conflicts". at the head

The regulation and the instructions agree on the lag, the state and the higher-guideline rule, so there is no source conflict to record under CONTRIBUTING.md#source-conflicts.

Critical

None. The PR reuses the existing fpg() helper from variables/gov/hhs/tax_unit_fpg.py, adds no variable, and hard-codes no policy value. Every CI check that has finished passed.

Should address

S1. Territory households now reach the placeholder guideline: an error for coverage in 2015 to 2024, and an infinite fraction from 2025

Where: policyengine_us/variables/gov/aca/eligibility/aca_magi_fraction.py:51-54, which reads policyengine_us/parameters/gov/hhs/fpg.yaml:52-57 and :107-112. The GU, PR and VI placeholders there are 2024-01-01: 0, with no earlier value.

Governing text:

Why it happens:

  • state_group maps PR, GU and VI to their own groups, and the head now reads that group for the coverage year.
  • fpg() then looks the group up at the guideline date. Before 2024 the territory parameters have no value: Parameter._get_at_instant returns None, so ParameterNodeAtInstant leaves the child out.
  • In policyengine-core 3.34.2, the version uv.lock pins, the vectorial lookup sends the missing key to its sentinel. It then raises ParameterNotFoundError under "Check for unexpected keys" (parameters/vectorial_parameter_node_at_instant.py). The installed 3.33.2 behaves the same way.
  • household_net_income reaches this lookup. It adds household_refundable_tax_credits, which adds premium_tax_credit_in_force, which adds used_aca_ptc, which reads aca_ptc. aca_ptc is defined_for = "is_aca_ptc_eligible", and core computes that mask before any formula runs (simulations/simulation.py, the defined_for block). is_aca_ptc_eligible reads aca_magi_fraction.

Household (hand trace): a single adult, state_code: PR supplied for 2024 only, aca_magi: 30_000, period 2024, requesting household_net_income or is_aca_ptc_eligible.

  • Main: tax_unit_fpg(2023) reads the state for 2023. Nothing was supplied for 2023, so state_code falls through state_name to state_fips, whose default is 6 (California). The fraction is 30,000 / 14,580 = 2.0576, floored to 2.05. It computes without an error.
  • Expected: a computed result with no premium tax credit.
  • Head: the state group is "PR" for 2024, and the parameters at 2023-01-01 have no PR entry, so the calculation raises ParameterNotFoundError: gov.hhs.fpg.first_person.PR. The same applies to every coverage year from 2015 to 2024.

From 2025: the PR guideline is 0. The same household in 2026 gets 30,000 / 0 = inf, or NaN at $0 of MAGI. is_aca_ptc_eligible is false, because numpy.digitize puts inf and NaN in the top sentinel bracket, which is false. That outcome is better than main, where the household got the contiguous 1.91 and could qualify for a credit it can't receive. But the inf still flows into aca_required_contribution_percentage, marketplace_csr_eligible, marketplace_csr_category and the API output.

Coverage: CI doesn't exercise this path. The 2024 Puerto Rico tests compute Puerto Rico tax variables and never reach the ACA chain, and the territory shard passed. On main the same error already occurs when a territory's state is supplied for both years. The PR extends it to the input shape it targets: an API request, where the state is supplied for one year.

Fix:

  • Backdate the GU, PR and VI placeholders in fpg.yaml to 1992-01-01: 0, the start date of the other groups, so that every guideline year resolves.
  • Add an is_aca_ptc_eligible test for a 2024 Puerto Rico household that expects false, and assert a household_net_income value in the same case so the error can't come back. Don't pin the infinite fraction itself.
  • Alternatively, gate the ACA chain off for territory state codes, with a citation to 36B(c)(2)(A).
  • Either way, say in the PR body that territory households are affected.

S2. The Form 8962 references point at the wrong pages, break the reference rules, and the new documentation depends on their order

Where: policyengine_us/variables/gov/aca/eligibility/aca_magi_fraction.py:10-31. The documentation string is at 10-17, the two existing Form 8962 entries at 19-26, and the new entry at 27-30.

Governing text:

  • CLAUDE.md, "Parameter and variable references": "One reference entry per source document; URLs that differ only by #page= are one source."
  • The same section: "PDF page numbers are file pages (1-indexed), not printed pages."
  • The same section: "Variable reference tuples have no title: put a # PDF pages 61-62, 67 comment above a multi-page href."
  • Instructions for Form 8962 (2025), https://www.irs.gov/pub/irs-prior/i8962--2025.pdf: Line 4 is on file page 8 and Worksheet 2 is on file page 9.

What is wrong:

  • Both existing entries link to the unversioned https://www.irs.gov/pub/irs-pdf/i8962.pdf. That file is now the 2025 edition (byte-identical to irs-prior/i8962--2025.pdf), but the titles still say "2022".
  • #page=7 opens the married-filing-separately exceptions and "Specific Instructions / Line 1". The Line 4 text the entry cites ("For 2025, the 2024 federal poverty lines are used") is on page 8.
  • #page=8 opens Line 2a and Line 4. Worksheet 2, the truncation the entry cites, is on page 9. The anchors were right only for the 2022 file, which the link no longer serves.
  • The two hrefs differ only by #page=, so under the rules they are one source.
  • All three entries are dict(title=..., href=...), and the PR adds the third one in that style. A variable's reference tuple takes bare URLs.
  • The new documentation sentence relies on "the first reference documents the use of the prior-year FPL; the second documents the truncation ...; the third sets the state". A reader who follows the first link lands on Line 1.

Concrete case: no household result changes. Opening aca_magi_fraction.py:21 (i8962.pdf#page=7) shows page 7 of the 2025 instructions, which has no poverty-line text. The cited sentence is one page later.

Fix: collapse the tuple to bare URLs pinned to a dated edition, and stop counting entries in the documentation string.

documentation = (
    "ACA-related MAGI as a fraction of the federal poverty line for the "
    "year before the coverage year, for the state of residence in the "
    "coverage year, truncated to whole percentage points."
)
reference = (
    # PDF pages 8-9: Line 4 (state of residence, prior-year guideline,
    # Alaska and Hawaii tables) and Worksheet 2 (truncation).
    "https://www.irs.gov/pub/irs-prior/i8962--2025.pdf#page=8",
    "https://www.law.cornell.edu/cfr/text/26/1.36B-1#h",
)

The 1.36B-1#h link the PR adds is correct: the anchor exists, and the paragraph supports both the lag and the Alaska and Hawaii rule.

S3. The PR description overstates which households are unaffected and leaves out households that lose eligibility

Where: the PR body. The last bullet of "What changes" says "Households in the contiguous states are unaffected, and so are households whose state is supplied for both years." The first bullet of "Effects" lists only gains: "a lower required contribution, a larger credit, and eligibility up to 400% of their own guideline."

Governing text:

  • 26 CFR 1.36B-1(h) and Form 8962 Line 4 (2025 instructions, file page 8) both point to the state of residence in the coverage year.
  • 26 U.S.C. 36B(c)(1)(A) (https://www.law.cornell.edu/uscode/text/26/36B#c_1) sets the floor: income that "equals or exceeds 100 percent". The below-poverty exception in (c)(1)(B) was repealed from 2026 by P.L. 119-21 §71302, and the model's below_fpl_immigration_exception_in_effect is false from 2026-01-01.

Household (a), a mover: state_code of AK for 2025 and TX for 2026, a single adult, aca_magi: 30_000, period 2026.

  • Main: reads the state for 2025, which is Alaska, and gets 30,000 / 19,550 = 1.5345, floored to 1.53.
  • Head: reads Texas and gets 30,000 / 15,650 = 1.9169, floored to 1.91.
  • The head is right, since the state of residence in 2026 is Texas. But this household lives in a contiguous state and has its state supplied for both years, and its result changes.

Household (b), pushed under the floor: 2026, Alaska, a single adult aged 30 with no Medicaid pathway. The inputs are is_medicaid_eligible, is_chip_eligible, is_medicare_eligible and is_aca_eshi_eligible all false, and aca_magi: 18_000.

  • Main: 18,000 / 15,650 = 1.1502, floored to 1.15. That is inside [1.00, 4.00), so is_aca_ptc_eligible is true.
  • Head: 18,000 / 19,550 = 0.9207, floored to 0.92. That is under the 1.00 floor, and the exception is off, so pays_aca_premium treats the household as in the coverage gap and is_aca_ptc_eligible is false.
  • The head is right under the Alaska guideline, but the description mentions only gains.

Also: the cost-sharing-reduction tiers (marketplace_csr_eligible, marketplace_csr_category) move with the fraction too, and the description doesn't mention them.

Fix: rewrite the last bullet of "What changes" as "Households whose state is the same in both years, and households outside Alaska and Hawaii in both years, are unaffected." Add three items to "Effects": Alaska and Hawaii households between 100% of the contiguous guideline and 100% of their own lose eligibility from 2026; households whose state changed between the two years now use the coverage year's state; CSR tiers follow the fraction. Optionally, replace "I didn't measure the microsimulation effect" with the mechanism: input carry-over runs forward only, so a dataset household differs between main and the head only when the coverage year is the dataset's own year.

Suggestions

G1. Record the mover and split-spouse simplification in the code, and cite Line 4 for the state rule

  • Where: aca_magi_fraction.py:45-51.
  • Governing text: 1.36B-1(h) says the state rule only by implication; it speaks of a "primary residence [that] changes during a taxable year" and spouses who "reside in separate states", then requires "the higher Federal poverty guideline". Form 8962 Line 4 (2025, file page 8) states it outright: "your state of residence in 2025".
  • Household: 2026 joint filers, family size 2, MAGI $70,000. One spouse lives in Alaska and one in Washington, and the household state is WA.
    • Law: the higher Alaska guideline applies: 19,550 + 6,880 = 26,430, so 70,000 / 26,430 = 2.6485, floored to 2.64.
    • Model: 21,150, so 3.3097, floored to 3.30.
    • The model has one state per household, and tax_unit.household(...) takes the first member's household, so this can't be modeled. It only needs a sentence.
  • Fix: extend the comment: "1.36B-1(h) and Form 8962 Line 4 apply the higher guideline when the residence moves into or out of Alaska or Hawaii during the year, or spouses live in different states; the model has one household state per year and uses the first member's household." Cite Line 4 alongside (h).

G2. Read family size for the coverage year

  • Where: aca_magi_fraction.py:50, size = tax_unit("tax_unit_size", guideline_period). The comment at 46 says "only the guideline lags".
  • Governing text: 26 U.S.C. 36B(d)(1) and 26 CFR 1.36B-1(d)(1) set family size "for the taxable year". The fpg() docstring says "Callers supply the current household size."
  • Household: 2026, Alaska, one listed member, tax_unit_size: 3 supplied for 2026 only, MAGI $50,000.
    • Coverage-year size: 50,000 / 33,310 = 1.50.
    • Head: reads the 2025 size, which falls back to the formula count of 1: 50,000 / 19,550 = 2.5575, floored to 2.55.
    • Ordinary households match, because tax_unit_size is nb_persons().
  • Fix: size = tax_unit("tax_unit_size", period), and drop the PR-body bullet "Family size is still read for the guideline year, as before."

G3. year_lag adds nothing

  • Where: aca_magi_fraction.py:52-53.
  • Governing text: none; this is code shape. instant(period).offset(-year_lag, "year") lands on the same January 1 as instant(guideline_period), including the 2015 guard case.
  • Household: none differs. The two forms are equivalent, so no test can tell them apart.
  • Fix: guideline = fpg(size, state_group, guideline_period, parameters), and drop line 52.

G4. Tests that would pin the new behavior more tightly

All expectations come from the 2024 and 2025 guidelines above.

  • Mover: AK for 2025 and TX for 2026, $30,000 → 1.91. This pins the coverage-year state directly rather than through the California fallback. It also fails if a later change takes the larger of the two years' guidelines, which no current test catches: with the state supplied for 2026 only, the larger guideline is Alaska's either way.
  • 100% edge in Alaska (2026, Medicaid inputs false): $19,549 → 0.99995, floored to 0.99, ineligible. The contiguous guideline gives 1.24, eligible.
  • 400% edge in Alaska: $78,199 → 3.99995, floored to 3.99, eligible. The contiguous guideline gives 4.99, ineligible. Don't assert exactly $78,200 until G5 is settled.
  • Mixed households: one simulation with two households, Alaska and Texas, both at $30,000 → [1.53, 1.91].
  • Before 2026: 2025 coverage in Alaska uses 18,810: 30,000 / 18,810 = 1.5949, floored to 1.59.

G5. Income exactly at 400% is denied from 2026 (pre-existing; separate PR)

  • Where: policyengine_us/parameters/gov/aca/ptc_income_eligibility.yaml, the 4.00 bracket, which is false from 2026-01-01, read by is_aca_ptc_eligible.py:17.
  • Governing text: 36B(c)(1)(A): "does not exceed 400 percent". Worksheet 2, line 4 (2025, file page 9): "Is the amount on line 1 more than the amount on line 3? Yes. ... Enter 401". MAGI exactly equal to 4 × FPL is answered "No" and gives 400, which is eligible.
  • Household: 2026, Alaska, single, MAGI $78,200 = 4 × 19,550.
    • Law: eligible.
    • Model: 4.00 falls in the false bracket under digitize(..., right=False), so the household is ineligible.
    • After truncation, 4.00 covers both MAGI = 4 × FPL and MAGI just above it, so the fix needs the unrounded comparison.
  • Fix: in a follow-up, test magi <= 4 × guideline on unrounded values, as Worksheet 2 does, and add a boundary test at $78,200. The PR body's "eligibility up to 400%" holds only below exactly 400%.

G6. California's premium subsidy still reads tax_unit_fpg

  • Where: policyengine_us/variables/gov/states/ca/hbex/premium_subsidy/ca_premium_subsidy_eligible.py:50 and ca_premium_subsidy_applicable_percentage.py:28-30 both read tax_unit_fpg for period.last_year.
  • Governing text: none; this is about keeping one source for the ACA guideline.
  • Household: none changes today. California is contiguous, and every California test that pins tax_unit_fpg also supplies the fraction or the eligibility. But an input or reform on tax_unit_fpg now reaches California's band test and not aca_magi_fraction.
  • Fix: note the decoupling in the PR body. When 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's aca_fpg lands, route California through it.

G7. The same lagged-state pattern in the Pell Grant (follow-up only)

  • Where: policyengine_us/variables/gov/ed/pell_grant/sai/eligibility_type/pell_grant_eligibility_type.py:21 reads tax_unit_fpg at period.offset(-2, "year").
  • Governing text: I haven't researched which state and year the Pell rules call for.
  • Household: an Alaska student whose state is supplied only for the award year gets the contiguous guideline, by the same mechanism as ACA applicable percentage uses the contiguous-states poverty guideline for Alaska and Hawaii #9482.
  • Fix: open a follow-up issue. This doesn't block this PR.

Checked and dropped

  • NaN claim corrected. An earlier pass of this review said a territory household with coverage in 2024 or before gets a NaN fraction. That doesn't hold. In policyengine-core 3.34.2 (the pinned version) and in 3.33.2, a key missing at the instant raises ParameterNotFoundError; 3.34.2 also raises when the sentinel index appears at all. I kept the finding as S1 with the corrected, more serious consequence.
  • Duplicates merged. Two passes raised the territory issue and two raised the reference issue. Each is now one entry (S1 and S2), at SHOULD in both passes.
  • Equivalent mutants. Two changes survive every test: reading tax_unit_size for period, and calling fpg() with guideline_period and no lag. Neither changes any formula result, so I didn't treat them as unpinned terms. They appear as G2 and G3.
  • Nothing else dropped. I re-checked every SHOULD claim against the head and the sources listed above.

Verified correct

  1. Lag date. For 2026 coverage, guideline_period is 2025 and year_lag is 1. fpg() reads parameters(2025-01-01): Alaska 19,550 + 6,880, Hawaii 17,990 + 6,330, contiguous 15,650 + 5,500. That matches 36B(d)(3)(B) and 1.36B-1(h): open enrollment for 2026 began on 2025-11-01, when the January 2025 guidelines were the latest. 2027 coverage reads the 2026 values, which match 91 FR 1797.
  2. State year. tax_unit.household("state_group_str", period) at :51 reads the coverage year, which matches Form 8962 Line 4 ("state of residence in 2025") and 1.36B-1(h).
  3. Root cause. state_code has a formula through state_name and state_fips. state_fips has no formula and defaults to 6, California. auto_carry_over_input_variables = True (system.py:109) carries an input only forward. So on main, a state supplied only for the coverage year fell back to California in the guideline year.
  4. First modeled year. The guard is unchanged: 2015 coverage uses year_lag 0 and the 2015 guideline.
  5. Truncation. np.floor(100 * magi / guideline) / 100 matches Worksheet 2 ("drop any numbers after the decimal point"), and max_(0, aca_magi) matches Line 3's "-0-".
  6. Parameters. The 2024, 2025 and 2026 values for CONTIGUOUS_US, AK and HI in gov/hhs/fpg.yaml match the Federal Register notices and the Form 8962 (2025) tables.
  7. Readers of the fraction.
    • The federal readers change for Alaska and Hawaii only: is_aca_ptc_eligible, aca_required_contribution_percentage, pays_aca_premium, aca_ptc_below_fpl_immigration_exception, marketplace_csr_eligible, marketplace_csr_category, basic_health_program, and the three contributed ACA reforms.
    • Cost-sharing reductions use the same lagged FPL (45 CFR 155.300(a)), so sharing the fraction is right.
    • The state subsidies in CA, CO, CT, MA, MD, NJ, NM, VT and WA are all in contiguous states, so they don't change.
    • Medicaid and CHIP use their own income levels and don't read this fraction.
  8. No source conflict. The regulation and the instructions agree. None of the new comment lines matches the source-conflict guard's patterns, and the PR-validation check passed.
  9. Code shape.
  10. Changelog. changelog.d/aca-fpl-coverage-year-state.fixed.md is a top-level fragment of type fixed, and its wording is accurate.
  11. PR-body arithmetic. 4 × 15,650 = 62,600 and 4 × 19,550 = 78,200. For an Alaska single filer at $30,000, the fraction moves from 1.91 to 1.53. I did not check the ACA calculator's credit figures ($9,947 vs $9,177); they need a simulation.
  12. Partner contract tests. The two partner files with Alaska households assert only WIC and school-meal results, which don't read the ACA chain. No partner file has a Hawaii household.

Hand-checked expectations (head: size from nb_persons, state group for 2026, guideline at 2025-01-01, then floor(100 × MAGI / guideline) / 100; "main" is the same input with the state supplied for 2026 only)

Test (file:line at head) MAGI Guideline used Arithmetic Expected in PR OK Main gives
aca_magi_fraction.yaml:44 Alaska, single 30,000 AK size 1: 19,550 1.53453 → 153 → 1.53 1.53 ✓ 30,000 / 15,650 = 1.91693 → 1.91
aca_magi_fraction.yaml:63 Hawaii, single 30,000 HI size 1: 17,990 1.66759 → 166 → 1.66 1.66 ✓ 1.91
aca_magi_fraction.yaml:82 Alaska, family of 3 50,000 19,550 + 2 × 6,880 = 33,310 (FR row 3: $33,310) 1.50105 → 150 → 1.50 1.5 ✓ 26,650 → 1.87617 → 1.87
is_aca_ptc_eligible.yaml:516, aca_magi_fraction 70,000 AK size 1: 19,550 3.58056 → 358 → 3.58 3.58 ✓ 4.47284 → 4.47
is_aca_ptc_eligible.yaml:516, is_aca_ptc_eligible — — 3.58 is in [1.00, 4.00), which is true in 2026; coverage flags false, age 30, not separate true ✓ 4.47 is in the 2026 false bracket → false
aca_magi_fraction.yaml:5 (existing) Texas, 2016 30,000 contiguous 2015: 11,770 2.54885 → 2.54 2.54 ✓ unchanged 2.54
aca_magi_fraction.yaml:24 (existing) Texas, 2015 guard 30,000 contiguous 2015: 11,770, lag 0 2.54885 → 2.54 2.54 ✓ unchanged 2.54

Every value of 100 × MAGI / guideline above is at least 0.05 away from the integer below it, so floating-point error can't flip a floor. The four new expectations fail on main and pass at the head.

Test coverage

Each row names a term at the head, a change to it, and the case that catches the change. All outcomes are hand traces. AMF is aca_magi_fraction.yaml and IAPE is is_aca_ptc_eligible.yaml.

Term (aca_magi_fraction.py) Mutation Pinning case Mutant gives
:51 state group for period read for guideline_period AMF:44, AMF:63, AMF:82, IAPE:516 1.91, 1.91, 1.87, 4.47 (ineligible)
:51 hard-code CONTIGUOUS_US the same four cases the same values
:52-53 year_lag force 0 AMF:44, AMF:63, AMF:82, AMF:5, IAPE:516 1.50, 1.63, 1.46, 2.52, 3.50
:52-53 force 1 (ignore the guard) AMF:24 2014's 10,890 → 2.75
:53 pass guideline_period and also lag AMF:5, AMF:44 2.75, 1.59
:53 pass guideline_period with no lag none: equivalent (G3) —
:50 size read for period none: equivalent for formula-computed size (G2) —
:50 hard-code 1 AMF:82 2.55
helper additional_person[state_group] use the contiguous increment AMF:82 30,550 → 1.63
:54 floor round instead AMF:63, AMF:5 1.67, 2.55
:43-44 guard (unchanged) remove it AMF:24 2.75

Every new term that changes a result is pinned. The cap in effect from 2026 is pinned through IAPE:516, and the 2025-to-2026 change in the Alaska and Hawaii guidelines pins the lag. Gaps that G4 would close: a mover, the 100% and 400% edges, a mixed-state simulation, and a year before 2026. The territory path is S1.

CI status

gh pr checks 10860, re-run at the end of this review: 22 pass, 6 pending, 0 fail (28 checks).

  • Pass (22):
    • Baseline: contrib-hhs, household, irs, reform, rest-a, rest-b, ssa-usda, states-shard-3
    • Household API Partners
    • PR validation (lock, guard tests, lint, changelog)
    • Package and compatibility
    • Python tests: Plan Rest Python shards; Full Suite Rest shards 2, 4, 5, 6, 7, 8 and 9
    • Quick Feedback (selective tests + coverage)
    • codecov/patch, codecov/project
  • Pending (6):
    • Baseline: states-shard-1, states-shard-2, states-shard-4
    • Python tests: Full Suite - Microsimulation; Full Suite Rest shards 1 and 3
  • Quick Feedback log:
    • gov/aca: 349 passed
    • aca_magi_fraction.yaml: 5 passed, including the 3 new cases
    • is_aca_ptc_eligible.yaml: 24 passed, including the new case
    • aca_magi_fraction.py: 100% coverage (20 statements, 2 branches)
  • What the pending shards run: the states shards run the contiguous-state subsidy tests that read the fraction, and those shouldn't change. The microsimulation shard can move only for Alaska and Hawaii households in the dataset's own year.

Branch status

The head is 1 commit ahead of main (a93cc58d7e) and 0 behind. GitHub reports it as mergeable; the merge state is BLOCKED because required checks are pending and the review is outstanding.

Verdict: REQUEST_CHANGES

DTrim99 and others added 2 commits November 13, 2026 09:16
…rences

- Read tax_unit_size for the coverage year (26 CFR 1.36B-1(d)(1)); only the
  guideline lags.
- Backdate the Guam, Puerto Rico and Virgin Islands guideline placeholders to
  1992, so coverage before 2025 no longer raises ParameterNotFoundError for a
  territory household. Their fraction is infinite, and is_aca_ptc_eligible now
  requires a finite fraction, since the 2021-2025 eligibility rows have no
  upper limit and no Exchange operates in the territories (36B(c)(2)(A)).
- Replace the titled Form 8962 references with a bare URL pinned to the 2025
  instructions (Line 4 and Worksheet 2, file pages 8-9) and drop the
  documentation that counted reference entries.
- Tests: a mover from Alaska to Texas, coverage-year family size, 2025
  coverage in Alaska, Alaska and Texas in one simulation, Alaska's 100% and
  400% edges, and a 2024 Puerto Rico household.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Moving the placeholders' only value to 1992 made uprating start in 1992,
where gov.hhs.fpg_uprating is undefined, so the parameter tree failed to
load. Keep 2024-01-01: 0 as the last value and add 1992-01-01: 0 before it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@DTrim99

DTrim99 commented Nov 13, 2026

Copy link
Copy Markdown
Collaborator Author

PR Review (round 2): Use the Alaska and Hawaii poverty guidelines in the premium tax credit's income fraction

PolicyEngine/policyengine-us #10860, head 391384776f1e2340047620e90acfe09ea1f6ecc1, base main at a93cc58d7e. Round 1 reviewed ccc11f9be0. Since then there are two commits: fa21a8006c (the fixes) and 391384776f, which keeps the 2024 territory placeholder as the last value after the first fix stopped the parameter tree from loading. I wrote this PR, so this is a self-review.

I ran no Python and no tests. Each claim rests on one of these, and I say which:

  • Reading: the code at the head, and the policyengine-core source. uv.lock pins 3.34.2; CI upgrades to 3.36.0 before it tests.
  • Hand traces of the arithmetic.
  • Primary sources: the 2025 Form 8962 instructions (I downloaded them and read file pages 8 and 9), the eCFR text of 26 CFR 1.36B-1, and 42 U.S.C. 18024 on Cornell LII.
  • CI logs at this head.

Where I say a changed version of the code would pass or fail a test, that is by my reading, not from a run.

Summary

Every round-1 item is closed:

  • S1: the territory placeholders now resolve for every guideline year. The 2024 Puerto Rico case pins that.
  • S2: the references are bare URLs to the right pages.
  • S3: the PR body describes the losers, the movers and the territories.
  • G1 to G4: done. The four new tests check out by hand, and each pins the change it names.
  • G5 to G7: recorded in the body as follow-ups.

The territory handling added this round brings four new should-address items:

  1. N1. Territory households are now never in pays_aca_premium's coverage gap. So one below the floor (including at $0 of MAGI) is now treated as paying a Marketplace premium. By my reading, it then reaches an SLCSP lookup that has no territory key, through household_net_income. The round-1 request to assert a net-income value on the territory case, which would show this, wasn't carried out.
  2. N2. The new has_guideline guard isn't pinned. Plain division gives the same result at the test's $30,000, and differs only at $0, where it gives NaN.
  3. N3. The np.isfinite check rests on a false premise. policyengine-core's single-amount scale already maps an infinite fraction to its false out-of-range cell in every year. The code comment, the test comment and the PR body all say otherwise. Round 1's statement on this was right.
  4. N4. The axiom: n/a line doesn't fit a PR that changes eligibility, and this round's territory clause makes that plainer. Round 1 didn't assess the line at all.

Counts open: 0 critical, 4 should address, 3 suggestions.

Prior findings

Finding Status Evidence
S1. Territory households reach the placeholder guideline: an error for 2015-2024 coverage Fixed fpg.yaml:54-62 and :112-120 add 1992-01-01: 0 for GU, PR and VI and keep 2024-01-01: 0 as the last value. Core starts uprating at the latest value (uprate_parameters.py: last_instant = instant(parameter.values_list[0].instant_str), and it raises if the index is undefined there), and gov.hhs.fpg_uprating starts in 2022, so the comment's reason for keeping 2024 is right (reading). aca_magi_fraction.py:49-51 returns inf where the guideline is 0, so territory households are ineligible in every year (traced below). Pinned by is_aca_ptc_eligible.yaml:590: without the 1992 entries, the 2023-01-01 lookup raises ParameterNotFoundError: gov.hhs.fpg.first_person.PR (reading). The body now covers territories. Not done: round 1 also asked for a household_net_income assertion on that case "so the error can't come back". See N1.
S2. Form 8962 references: wrong pages, rule breaks, documentation counted entries Fixed aca_magi_fraction.py:15-20: two bare URLs, i8962--2025.pdf#page=8 under # PDF pages 8-9: ..., and 1.36B-1#h. In the downloaded PDF, file page 8 has "Line 4 / Check the box to indicate your state of residence in 2025. ... (For 2025, the 2024 federal poverty lines are used ...) If you moved during 2025 and you lived in Alaska and/or Hawaii, ... use the table with the higher dollar amounts" and Table 1-1. File page 9 has Tables 1-2 (Alaska, 1 person $18,810) and 1-3, and Worksheet 2 ("Enter 401 ..."; "drop any numbers after the decimal point. For example, for 0.9984, enter the result as 99"). The documentation (:10-14) no longer counts entries.
S3. PR body overstated who is unaffected and left out losers Fixed The body now lists the 100% losers ($18,000: 1.15 → 0.92), movers (1.53 → 1.91), CSR tiers and territories, and gives the microsimulation mechanism. All figures check (see Verified). Three smaller wording points remain (G9).
G1. Record the mover / split-spouse limit; cite Line 4 Fixed aca_magi_fraction.py:34-41.
G2. Family size for the coverage year Fixed :42 reads tax_unit_size for period. 1.36B-1(d) sets family "for the taxable year" (eCFR text read). tax_unit_size is tax_unit.nb_persons(), so ordinary households don't change. Pinned by aca_magi_fraction.yaml:126: the guideline-year size gives 2.55. The paragraph cited is G8.
G3. year_lag adds nothing Fixed :44 calls fpg(size, state_group, guideline_period, parameters). Core's instant() returns period.start for a Period, so the date is 2025-01-01 for 2026 coverage and 2015-01-01 under the first-year guard, the same as before (reading periods/helpers.py).
G4. Tighter tests Fixed Mover, coverage-year size, 2025 Alaska, mixed simulation, 0.99 and 3.99 edges, and 2024 Puerto Rico. All are hand-checked below.
G5. Income exactly at 400% from 2026 Deferred, recorded Body, "Left for follow-ups".
G6. California subsidy still reads tax_unit_fpg Recorded Body, last-but-one Effects bullet.
G7. Pell Grant lagged-state pattern Deferred, recorded Body, "Left for follow-ups".

New findings

Critical

None. The new code hard-codes no policy value, reinvents no variable and adds no untested formula. Every CI check that has finished passed (see CI).

Should address

N1. Territory households are never in the coverage gap now, so one below the floor is treated as paying a Marketplace premium and, by my reading, reaches a lookup with no territory key

Where:

  • policyengine_us/variables/gov/aca/eligibility/pays_aca_premium.py:61-71, which is unchanged, now receives an infinite fraction.
  • aca_magi_fraction.py:51 produces that fraction.

What changes:

  • The coverage-gap test is in_coverage_gap = (magi_frac < floor) & ~income_eligibility.calc(magi_frac) & ~below_fpl_exception.
  • With magi_frac = inf, inf < 1.00 is false. So every territory household is outside the gap, and pays_aca_premium is true whenever its status, coverage and age conditions hold, even at $0 of MAGI.
  • On main, the PR targets households whose state is supplied only for the coverage year. For them, the guideline year fell back to the contiguous guideline. A territory household below 100% of it was in the gap, so pays_aca_premium was false.
  • pays_aca_premium's documentation calls it "Whether a person is enrolled in a Marketplace plan and charged an age-rated premium for it". Without an Exchange that can't happen, for the same reason the PR gives for denying the credit.

Where it leads (reading only):

  • household_net_income → household_refundable_tax_credits → premium_tax_credit_in_force → used_aca_ptc (used_aca_ptc.py:12-14: min_(aca_ptc, selected_marketplace_plan_premium_proxy)).
  • The proxy reads slcsp for every tax unit (selected_marketplace_plan_premium_proxy.py:16).
  • slcsp adds slcsp_age_curve_amount_person, which is defined_for = "pays_aca_premium". Core skips the formula only when the mask is false for everyone (simulation.py in core 3.36.0, if np.all(~mask): ... return). Otherwise it runs the formula over the whole population.
  • The formula reads slcsp_age_0, which does p.state_rating_area_cost[state_code][safe_rating_area] (slcsp_age_0.py:22).
  • gov/aca/state_rating_area_cost.yaml has 51 state keys (the 50 states and DC) and none for GU, PR or VI.
  • Core's vectorial lookup raises ParameterNotFoundError for a key that names no child (vectorial_parameter_node_at_instant.py, "Check for unexpected keys", in 3.36.0).
  • lcbp_age_0 makes the same lookup.

Household (hand trace, reading only): 2026, a Puerto Rico citizen adult, state_code: PR supplied for 2026, aca_magi: 0, no other coverage, requesting household_net_income.

Fraction In the gap pays_aca_premium SLCSP formula
main 0 / 15,650 = 0 yes false skipped
Head inf no true runs, and the PR key lookup raises

Above the floor, main already sends territory households down this path, so the head doesn't create the error. It extends it to the below-floor households that used to compute.

Caveat: I haven't traced every program that household_net_income reaches for a territory household. If another reader already raises for them on main, this changes nothing for that output. Medicaid's income-level path is graceful for PR: an existing case, medicaid_income_level.yaml:378, is "gracefully ineligible". The test below settles the question in CI.

Why it matters for the PR's claims:

  • The changelog says territory households "no longer raise an error for coverage before 2025", and the body says the same.
  • By my reading, that holds for aca_magi_fraction and is_aca_ptc_eligible, which is all the new case asserts. It doesn't hold for household_net_income.

Fix:

  • Take territory households out of pays_aca_premium, for example by & np.isfinite(magi_frac) in its return, or by an explicit territory test. Cite 36B(c)(2)(A) and 42 U.S.C. 18024(d) (G10). This states the rule where it belongs, and it makes is_aca_ptc_eligible's own check unnecessary (N3).
  • Add pays_aca_premium: false and used_aca_ptc: 0 to the 2024 Puerto Rico case. used_aca_ptc reaches the SLCSP lookup through the proxy, so it is the cheapest form of round 1's net-income assertion.
  • Multi-household simulations in which another household pays still run slcsp_age_0 over a territory row. That predates this PR and can be a follow-up.
  • If the new assertion passes at the head as it stands, my reading of the lookup is wrong. Then the remaining fix is only the pays_aca_premium semantics.
N2. The has_guideline guard is not pinned

Where: aca_magi_fraction.py:49-51:

has_guideline = guideline > 0
fraction = magi / where(has_guideline, guideline, 1)
return where(has_guideline, np.floor(100 * fraction) / 100, np.inf)

What is wrong (reading):

  • Plain division survives: replacing these lines with round 1's np.floor(100 * magi / guideline) / 100 fails no test. The only territory case (is_aca_ptc_eligible.yaml:590) has $30,000 of MAGI, where both forms give inf. model_api.py:13 sets warnings.filterwarnings("ignore"), so numpy's divide-by-zero warning doesn't fail it.
  • The two forms differ at $0 of MAGI, the most common territory request. Plain division gives 0 / 0 = NaN.
    • aca_required_contribution_percentage then returns NaN, where the guard gives 0. Traced: searchsorted puts NaN in the last bracket, position is NaN, and above_fpl_limit = NaN > 4.00 is false, so capped_out is false and the NaN applicable_figure comes back.
    • With the guard, inf lands in the last bracket, above_fpl_limit is true and calc(inf) is false (N3), so it returns 0.
  • The infinite value survives too: replacing np.inf with 0 at :51 also survives. A 0 fraction puts the 2024 case in the coverage gap, so it is ineligible either way.

Fix: add a 2026 case for a Puerto Rico single adult with aca_magi: 0 (Medicaid, CHIP, Medicare and employer-coverage inputs false). Assert aca_required_contribution_percentage: 0. Each version gives a different value there:

Version Fraction Required contribution
Head inf 0
Plain division NaN NaN
0 in place of inf 0 the 2026 initial rate, 0.021 (required_contribution_percentage/initial.yaml, 2026, first bracket)
N3. The np.isfinite check rests on a false premise, and three texts repeat it

Where:

  • is_aca_ptc_eligible.py:17-23: "Without this check the 2021-2025 rows of ptc_income_eligibility, which have no upper limit, would pass it."
  • The test comment at is_aca_ptc_eligible.yaml:590: "2024 has no 400% limit, so the finite-fraction check is what makes the result false."
  • The PR body's Territories bullet: "because the 2021-2025 rows of ptc_income_eligibility have no upper limit and would otherwise pass an infinite one."

The parameter: parameters/gov/aca/ptc_income_eligibility.yaml has three brackets:

Threshold Amount
0 false
1.00 true from 2014
4.00 true for 2021-2025, false otherwise

Its type is single_amount, so core builds a SingleAmountTaxScale (parameter_scale.py:133-140).

What core does with it: SingleAmountTaxScale.calc, the same in 3.34.2 (pinned) and 3.36.0 (installed by CI), reads:

guarded_thresholds = numpy.array([-numpy.inf] + self.thresholds + [numpy.inf])
bracket_indices = numpy.digitize(tax_base, guarded_thresholds, right=right)
...
guarded_amounts = numpy.concatenate([sentinel, amounts, sentinel])
return guarded_amounts[bracket_indices - 1]
  • For tax_base = inf, the guarded thresholds are [-inf, 0, 1.00, 4.00, inf]. With right=False, digitize returns 5, since inf ≥ the last bin.
  • guarded_amounts[4] is the trailing sentinel, a false of the amounts' dtype.
  • So calc(inf) is false in every year, 2021-2025 included. Only finite fractions see "no upper limit". This is what round 1 said ("numpy.digitize puts inf and NaN in the top sentinel bracket, which is false"). The premise of the added check contradicts it, and the core source bears round 1 out.

Consequences (reading):

  • Deleting & np.isfinite(magi_frac) changes no result. The fraction is never NaN at the head, and inf already maps to false, so it is an equivalent mutant.
  • The 2024 Puerto Rico case doesn't pin the check, although its comment says it does.
  • The three contributed reforms (aca_ptc_700_fpl_cliff, aca_ptc_additional_bracket, aca_ptc_simplified_bracket) override is_aca_ptc_eligible without the check. They still deny territory households, because each of their scales ends in a false bracket. Under the PR's stated premise they would need it too.

Fix:

  • If N1's fix lands, drop the check, its comment, the test comment's second sentence, and the clause in the body.
  • Otherwise, keep the check but reword all three. For example: "core's single-amount scale already maps an infinite fraction to its out-of-range cell, which is false; the check states the territory rule here rather than relying on that."
N4. The axiom: n/a line doesn't fit an eligibility change

Where: the PR body's last line, which says rulespec-us's pipeline takes the guideline as a caller-supplied fact "and treats the territories, which have no guideline, as ineligible".

Governing text:

  • CONTRIBUTING.md, "Axiom parity": "Every policy change here must also be correct in rulespec-us. That covers a new program, a parameter or threshold update, an eligibility rule and a bug fix."
  • The shared guide lists n/a for "infrastructure, data, UI, microsimulation-only or emulator-mapping changes".

Why it doesn't fit:

  • This PR is a bug fix that changes eligibility for Alaska and Hawaii households and for movers, and from this round for every territory household. None of the n/a categories applies.
  • The stated reason is accurate as far as it goes. rulespec-us's us/policies/aca/ptc_pipeline.yaml "accepts that exact annual amount as a trusted upstream fact for the household's family size and jurisdiction", and it defers "Exchange/QHP enrollment" under 36B(c)(2).
  • But that is a parity gap, not an exemption. The jurisdiction rule (1.36B-1(h) and Line 4) and the territory exclusion are encoded nowhere in rulespec-us.
  • The pipeline also requires a positive guideline, so a territory household can't be represented there at all.
  • The family-size change overlaps rulespec-us#1627 (OPEN, pe-parity, updated 2026-10-10). It queues the 36B(d)(1) family size "for the taxable year" and keeps the caller-supplied poverty line.

Fix:

  • Open a dispatch-ready pe-parity issue covering:
    • the applicable poverty guideline: 1.36B-1(h) and Form 8962 Line 4, meaning residence in the taxable year, the HHS tables for the 48 states and DC, Alaska and Hawaii, and the higher table for movers and split-state spouses;
    • territory residents: 36B(c)(2)(A) read with 42 U.S.C. 18024(d).
  • Give it companion tests from the Form 8962 (2025) tables. For example, an Alaska single filer with $30,000 against $18,810 gives 159 on Worksheet 2.
  • Record the blocker the shared guide requires.
  • Then write axiom: rulespec-us#<n> queued, and mention Add 2023 poverty line #1627 for family size.
  • If maintainers decide that a caller-supplied-input pipeline makes such changes n/a, record that decision in the PR instead.

Suggestions

G8. Cite 1.36B-1(d), not (d)(1). The comment at aca_magi_fraction.py:34-35 cites "26 CFR 1.36B-1(d)(1)". Paragraph (d)(2) ("For taxable years to which section 151(d)(5) applies, a taxpayer's family means the taxpayer, ... for the taxable year") governs taxable years ending on or after 2020-12-31 (paragraph (o)(2)). Both say "for the taxable year", so no result changes. My round-1 G2 introduced the "(d)(1)".

G9. Three wording points in the PR body:

  1. "Households whose state is the same in both years, and households outside Alaska, Hawaii and the territories in both years, are unaffected" has two exceptions:

    • territory households whose state is supplied for both years, with coverage before 2025, which go from an error to a result;
    • households whose tax_unit_size input differs between the two years.

    Adjacent bullets describe both, so prefixing it with "Otherwise," would fix it.

  2. The Tests bullet says the 2024 Puerto Rico case "raised an error before". That is relative to this PR's first commit. On main the case got the contiguous 2023 guideline: 30,000 / 14,580 = 2.0576, floored to 2.05, so it was eligible.

  3. "That differs only when tax_unit_size is supplied for the coverage year alone" also misses inputs that differ between the two years.

The Territories bullet's "Other readers of the placeholder get 0" could add that the readers that divide by it give inf, or NaN at $0, as they already do from 2024 (listed under Verified).

G10. Cite 42 U.S.C. 18024(d) with 36B(c)(2)(A). The comments and body rest "no Exchange" on 36B(c)(2)(A), which requires a plan "enrolled in through an Exchange established by the State under section 1311". ACA §1304(d), 42 U.S.C. 18024(d), closes the loop: "the term 'State' means each of the 50 States and the District of Columbia" (Cornell LII, read today). A reference to it makes the territory rule self-contained.

Verified

Hand-checked new expectations. Each row gives:

  • the head's computation: the coverage-year state and size, the guideline at January 1 of the prior year, then floor(100 × MAGI / guideline) / 100;
  • what the case pins;
  • what main gives for the same inputs. The state is supplied as in the test, so on main the guideline year falls back to the contiguous group.
Case (file:line at head) MAGI Guideline used Arithmetic Expected OK Pins main gives
aca_magi_fraction.yaml:104 mover, AK 2025 → TX 2026 30,000 TX (contiguous), size 1: 15,650 1.91693 → 191 → 1.91 1.91 ✓ coverage-year state; the 2025 state gives AK 19,550 → 1.53 1.53
aca_magi_fraction.yaml:126 size 3 for 2026 only, AK 50,000 19,550 + 2 × 6,880 = 33,310 1.50105 → 1.50 1.5 ✓ coverage-year size; the 2025 size of 1 gives 2.55 contiguous, size 1: 3.19489 → 3.19
aca_magi_fraction.yaml:149 2025, AK 30,000 AK at 2024-01-01: 18,810 (Form 8962 Table 1-2) 1.59490 → 1.59 1.59 ✓ the lag before 2026 (no lag gives 19,550 → 1.53) and the coverage-year state contiguous 15,060: 1.99203 → 1.99
aca_magi_fraction.yaml:167 AK and TX in one simulation 30,000 each 19,550 and 15,650 1.53453 / 1.91693 [1.53, 1.91] ✓ per-household state indexing; one state for all gives [1.53, 1.53] [1.91, 1.91]
is_aca_ptc_eligible.yaml:541 AK, 2026 19,549 19,550 0.999949 → 99 → 0.99; [0, 1.00) is false; the exception is off from 2026 0.99, false ✓ the Alaska floor, and truncation (rounding gives 1.00, which is eligible) 1.24914 → 1.24, true
is_aca_ptc_eligible.yaml:566 AK, 2026 78,199 19,550 3.999949 → 399 → 3.99; [1.00, 4.00) is true 3.99, true ✓ the Alaska 400% edge, and truncation (rounding gives 4.00, false in 2026) 4.99674 → 4.99, false
is_aca_ptc_eligible.yaml:590 PR, 2024 30,000 PR at 2023-01-01: 0 (the new 1992 entry) the guideline is 0 → inf; calc(inf) is false (N3); the exception: inf < 1 is false false ✓ the 1992 backdate (without it: ParameterNotFoundError); not the guard (N2), not isfinite (N3) contiguous 14,580: 2.05, true

In every row, 100 × MAGI / guideline is at least 0.10 above the integer below it, so floating-point error can't move a floor. The 99.9949 and 399.9949 cases sit 0.005 under the next integer, far beyond float error. Quick Feedback ran all of them green (CI).

Mutation check at the head (reading).

Term Change Caught by Changed code gives
aca_magi_fraction.py:42 size for period read for guideline_period AMF:126 2.55
:43 state for period read for guideline_period AMF:104, AMF:44/63/82, IAPE:516/541/566 1.53; 1.91 ...
:44 guideline_period period (no lag) AMF:149, AMF:44 1.53; 30,000 / 19,950 = 1.50
:49-51 guard plain division none (N2) the same at $30,000; NaN at $0
:51 np.inf 0 none (N2) the 2024 PR case is in the gap → false anyway
:51 np.inf a large finite value, such as 99 IAPE:590 true (2024's 4.00 bracket is true)
is_aca_ptc_eligible.py:21-23 & np.isfinite delete none: equivalent (N3) the same
fpg.yaml 1992 entries delete IAPE:590 ParameterNotFoundError
fpg.yaml 2024 entries delete every test (the tree fails to load) —

Readers of aca_magi_fraction with an infinite fraction (grep at the head, each read).

Reader What inf does
is_aca_ptc_eligible false
aca_ptc_below_fpl_immigration_exception inf < 1 is false, so no exception
pays_aca_premium outside the gap (N1)
aca_required_contribution_percentage last bracket, capped_out, returns 0; no NaN, since the 2015, 2021 and 2026 thresholds are finite, ending at 4.00
marketplace_csr_eligible inf <= maximum is false
marketplace_csr_category NONE
basic_health_program calc(inf) is false → 0, and it is defined_for enrollment
The three contributed reforms their scales end in false, and np.interp gives the last amount
  • None of these pays a territory household. None produces NaN at the head, because the guard prevents 0 / 0.
  • The state readers (CA, CO, CT, MA, MD, NJ, NM, VT, WA, 17 variables) are all defined_for their state, so a territory household never computes them.
  • The infinite value itself is an output of aca_magi_fraction. medicaid_income_level, the SNAP ratios and school_meal_fpg_ratio already return the same kind of value for territory households from 2024 on main.

Readers of the gov.hhs.fpg placeholder before 2024:

  • 33 files read gov.hhs.fpg directly, and 87 import the helper. Before this PR, for a territory household before 2024, every one raised ParameterNotFoundError. Now they get 0.
  • These divide by it, and so give inf, or NaN with no income: medicaid_income_level, tax_unit_medicaid_income_level, snap_gross_income_fpg_ratio, snap_gross_test_income_fpg_ratio, snap_net_income_fpg_ratio, school_meal_fpg_ratio, fcc_fpg_ratio and pell_grant_eligibility_type.
  • Their comparisons against limits (<=) are false for both, so the household is income-ineligible. medicaid_income_level.yaml:378 already pins that for PR in 2026.
  • This is exactly what those readers already return for territory households from 2024, where the placeholder was already 0. So the PR turns a pre-2024 error into the established 2024+ behavior, not into anything new. Pre-2024 territory households couldn't be computed at all before, so this matters little.

Other checks:

  • Uprating: with the 2024 entry last, uprating multiplies 0 by the index ratio from 2024, so the placeholders stay 0 in every year (reading core uprate_parameter).

  • Code shape:

    • The guard keeps the denominator nonzero, so aca_magi_fraction raises no numpy warning.
    • where and np.isfinite are vectorized.
    • No policy value is hard-coded.
    • The documentation (:10-14) is accurate.
  • References: see S2. The 1.36B-1#h anchor resolves, and paragraph (h) supports the lag and the higher-guideline rule (eCFR text).

  • Changelog: changelog.d/aca-fpl-coverage-year-state.fixed.md is top-level, of type fixed. Its wording is accurate except for the "no longer raise an error" scope (N1).

  • PR-body figures, by hand:

    Claim Arithmetic OK
    The 400% cutoff 4 × 15,650 = 62,600 and 4 × 19,550 = 78,200 ✓
    Alaska at $30,000 30,000 / 19,550 = 1.53453 → 1.53, against 30,000 / 15,650 = 1.91693 → 1.91 ✓
    Alaska at $18,000 18,000 / 15,650 = 1.15016 → 1.15, against 18,000 / 19,550 = 0.92072 → 0.92; below the floor from 2026, since below_fpl_immigration_exception_in_effect is false from 2026-01-01 ✓
    Mover 1.53 → 1.91 ✓

    I carried the ACA-Calc figures ($9,947 against $9,177) over without re-deriving them; they need a simulation.

  • Partner tests: no partner file has a GU, PR or VI household (grep), and the PR edits none.

  • Guards:

Axiom

The line at the head is axiom: n/a: .... It is not valid under either CONTRIBUTING file for a PR that changes eligibility (N4). Round 1 didn't assess the axiom line, and it should have. This round's territory clause adds a 36B(c)(2) eligibility outcome, which makes n/a harder to defend, not easier. The relevant rulespec-us items are:

  • ptc_pipeline.yaml, which takes the guideline as a caller-supplied fact and defers Exchange enrollment;
  • rulespec-us#1627 (OPEN, pe-parity), for family size.

No queued issue covers the jurisdiction rule or the territory exclusion.

CI

Run 43444234312 at 391384776f, checked at the time of posting: 38 checks, 29 passed, 9 still running (the 8 Contrib shards and Microsimulation), none failed.

  • Quick Feedback ran the two changed test files: aca_magi_fraction.yaml 9 passed, is_aca_ptc_eligible.yaml 27 passed.
  • All 11 Baseline shards, the 11 Python shards, Household API Partners, and Package and compatibility passed.
  • The run at the first fix commit (fa21a8006c, run 43441186447) failed all 27 jobs when it loaded the parameter tree: "Failed to uprate using gov.hhs.fpg_uprating at 1992-01-01 for gov.hhs.fpg.additional_person.GU". 391384776f fixed that.

Branch

Verdict: REQUEST_CHANGES

…ine guard

- pays_aca_premium is false where aca_magi_fraction is infinite, which is
  where HHS publishes no guideline: Puerto Rico, Guam and the Virgin Islands
  have no Exchange (26 U.S.C. 36B(c)(2)(A); 42 U.S.C. 18024(d)). An infinite
  fraction was outside the coverage gap, so a territory household below the
  floor, $0 included, was treated as paying a Marketplace premium.
- Drop the finite-fraction check in is_aca_ptc_eligible: core's single-amount
  scale already maps an infinite fraction to its false out-of-range cell, so
  the check changed nothing, and its comment said otherwise.
- New 2026 Puerto Rico case at $0 of MAGI pins the zero-guideline guard: the
  required contribution is 0, where plain division gives NaN and a fraction
  of 0 gives 0.021. The 2024 case now asserts pays_aca_premium false.
- Cite 1.36B-1(d) rather than (d)(1), and 42 U.S.C. 18024(d).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@DTrim99

DTrim99 commented Nov 13, 2026

Copy link
Copy Markdown
Collaborator Author

Round-2 items, fixed at a9cc99bc13:

  • N1: pays_aca_premium is false where the fraction is infinite, which is where HHS publishes no guideline: Puerto Rico, Guam and the Virgin Islands have no Exchange (36B(c)(2)(A); 42 U.S.C. 18024(d)). The 2024 Puerto Rico case now asserts pays_aca_premium: false; without the new test it would be true. I didn't add the used_aca_ptc assertion. By my reading it raises for any territory household, on main too, because slcsp adds slcsp_family_tier_amount, whose defined_for looks up family_tier_states[state_code], and that parameter has no territory key. The body lists this as a separate fix.
  • N2: a new 2026 Puerto Rico case at $0 of MAGI asserts aca_required_contribution_percentage: 0. Plain division gives NaN there, and a fraction of 0 gives 0.021.
  • N3: dropped the finite-fraction check from is_aca_ptc_eligible (the file is back to main), and corrected the comments and the body.
  • N4: opened Encode the premium tax credit's poverty guideline by state of residence, with Alaska, Hawaii and movers (26 CFR 1.36B-1(h)), Form 8962 Worksheet 2 truncation, and no credit in the territories (PE-US PR 10860) TheAxiomFoundation/rulespec-us#2210 (pe-parity) for the applicable guideline by state of residence and the territory exclusion, with 13 companion tests from the Form 8962 (2025) tables. Family size stays with rulespec-us#1627. The axiom: line now says queued.
  • G8, G10: cite 1.36B-1(d), and 42 U.S.C. 18024(d) next to 36B(c)(2)(A).
  • G9: all three body wording points are fixed.

Quick Feedback passed at this head: aca_magi_fraction.yaml 9 of 9, is_aca_ptc_eligible.yaml 28 of 28. The full suite is still running.

@PavelMakarchuk PavelMakarchuk left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: Use the Alaska and Hawaii poverty guidelines in the premium tax credit's income fraction (#10860)

Verdict: REQUEST_CHANGES

The legal reading is right and the main change does what it says. I reproduced every number in the description. One line introduces a rounding regression that affects all states, the description of the fpg.yaml change is not what the code does, and two repository rules are open.

Which commit. I reviewed 391384776f. You pushed a9cc99bc13 at 11:10am EST on 2026-11-13, during the review. I applied that commit and re-ran the two changed test files, gov/aca, contrib/aca, the partner folder and my household set on it. Each item below says where it stands at both commits. Line numbers are for a9cc99bc13 unless I say otherwise.

Counts open at a9cc99bc13: 1 critical, 3 should address, 5 suggestions.


Critical

C1. The new truncation divides before it multiplies, so a household exactly on a whole percentage loses one point (all states)

policyengine_us/variables/gov/aca/eligibility/aca_magi_fraction.py:52-53 (:50-51 at 391384776f):

fraction = magi / where(has_guideline, guideline, 1)
return where(has_guideline, np.floor(100 * fraction) / 100, np.inf)

main computes np.floor(100 * magi / fpg) / 100: it multiplies first. 100 * 35_369 / 15_650 is exactly 226. 35_369 / 15_650 is not representable, 100 * that is 225.99999..., and the floor gives 225. Worksheet 2 gives 226.

Household Worksheet 2 main This PR
Texas, single, 2026, MAGI $35,369 (226% of $15,650) 226 2.26; contribution 7.557%; aca_ptc $5,165.69 2.25; 7.520%; $5,178.71
Texas, single, 2025, MAGI $30,873 (205% of $15,060) 205 2.05; aca_ptc $5,515.38 2.04; $5,527.73
Hawaii, family of eight, 2026, MAGI $125,223 (201% of $62,300), state supplied for 2025 and 2026 201 2.01; CSR 73% AV; aca_ptc $9,848.49 2.00; CSR 87% AV; $9,894.58
Alaska, single, 2026, MAGI $44,183 (226% of $19,550), state supplied for 2026 226 2.82 (the bug this PR fixes) 2.25

The first three households are ones the description calls unaffected.

Extent, from a grid I ran on both trees:

  • Texas single filer, 2026, every whole-dollar MAGI that is an exact whole percentage from 95% to 405% (155 cases): main is right in all 155. This PR is wrong in 7 (114, 116, 226, 228, 230, 232 and 402%).
  • Family sizes 1 to 8, half-dollar MAGI included (2,488 exact-percentage cases): main wrong in 0, this PR wrong in 128.
  • One dollar either side of each boundary: no difference.
  • Microdata: no difference. In enhanced_cps_2024, no tax unit outside Alaska and Hawaii with a fraction between 0 and 8 gets a different value in 2024, 2025 or 2026. The regression needs MAGI exactly on a boundary, which household inputs and tests have and imputed incomes do not.

No test pins the order: with the fix below, all 37 cases in the two changed files still pass.

Fix (I ran it; it restores all four rows above and the grid):

percent = 100 * magi / where(has_guideline, guideline, 1)
return where(has_guideline, np.floor(percent) / 100, np.inf)

Add a case to aca_magi_fraction.yaml: Texas, single, 2026, aca_magi: 35_369, expected 2.26.


Should address

S1. The fpg.yaml change does not do what the changelog, the description and a test comment say

Where:

  • policyengine_us/parameters/gov/hhs/fpg.yaml:51-62 and :112-120 (the 1992-01-01: 0 entries).
  • changelog.d/aca-fpl-coverage-year-state.fixed.md: "their income fraction no longer raises an error for coverage before 2025".
  • is_aca_ptc_eligible.yaml:595-596: "The 2023 guideline lookup raised ParameterNotFoundError before the placeholders were backdated to 1992."
  • The description: "reading it for an earlier guideline year raised ParameterNotFoundError. That was already true on main when a territory's state was supplied for both years", and the Tests bullet "which raised an error before".

What I ran:

  • main raises no error here. Puerto Rico, single adult, state_code: PR for 2023 and 2024, MAGI $30,000, 2024 coverage: main gives aca_magi_fraction = inf and is_aca_ptc_eligible = false. The same holds for 2015 coverage. system.py backdates each parameter's first value to 2015-01-01 (#9628), so the 2024 placeholder already answers every date from 2015.
  • The 2024 Puerto Rico case with the state supplied for 2024 only gives 2.05 and eligible on main (the 2023 contiguous guideline), not an error.
  • The entries are not needed by any test. With main's fpg.yaml, all 36 cases at 391384776f and all 37 at a9cc99bc13 pass.
  • Parameter values. I compared both parts for the six state groups for every year from 1992 to 2035. The only differences are GU, PR and VI for 1992 to 2014 (an error on main, 0 here). The contiguous, Alaska and Hawaii values are identical in every year, including the uprated 2027 to 2035 values. The territory values stay 0 after 2026.
  • What the change really does: 2015 only, outside the ACA. SNAP reads the guideline for the fiscal year, dated 2014-10-01, which is before the backdated start. I compared all 7,644 variables for territory households on both trees:
    • Guam, adult and child, $0, 2015: 80 variables raise on main, 14 here. The 66 that now compute include snap $6,312, household_net_income $6,312, is_snap_eligible, is_lifeline_eligible, is_acp_eligible, is_ebb_eligible (all true) and is_head_start_eligible for the child.
    • Virgin Islands, adult and child, $12,000, 2015: 80 raise on main, 14 here; snap $3,120, household_net_income $18,602.
    • Puerto Rico, 2015: 96 raise on main, 88 here (the rest fail on main for an unrelated reason).
    • 2016, 2023, 2025 and 2026 (five territory households): no variable outside the ACA variables changes, no error appears or goes away, and no new infinite or NaN value appears outside aca_magi_fraction. The infinite ratios that the 0 placeholder produces (fcc_fpg_ratio, school_meal_fpg_ratio, medicaid_income_level, tax_unit_medicaid_income_level, the SNAP gross-income ratios) are the same on main.

So the edit extends the placeholder's existing behaviour to 2015 for about 66 non-ACA variables. The PR does not describe or test that, and it is not needed for the premium tax credit.

Fix: take the fpg.yaml edit out of this PR, or keep it and describe and test the 2015 effect. In both cases correct the changelog sentence, the test comment and the two statements in the description.

S2. The axiom: n/a line is not valid for this change

CONTRIBUTING.md, "Axiom parity": "Every policy change here must also be correct in rulespec-us. That covers a new program, a parameter or threshold update, an eligibility rule and a bug fix." The shared guide allows n/a for "infrastructure, data, UI, microsimulation-only or emulator-mapping changes".

This PR is a bug fix that changes eligibility: the Alaska single filer at $70,000 gains the credit, the one at $18,000 loses it, and territory households lose it. That is none of the n/a categories.

Your description of rulespec-us is accurate. us/policies/aca/ptc_pipeline.yaml takes the poverty line as a caller-supplied fact, its 11 companion cases use only contiguous amounts (15,650, 21,150, 32,150), and nothing there encodes 26 CFR 1.36B-1(h), Form 8962 Line 4 or the territory rule. That is a gap to queue, not an exemption. rulespec-us#1627 (open, pe-parity) covers the 36B(d)(1) family size only.

Fix: open a dispatch-ready rulespec-us issue with the pe-parity label for the applicable poverty guideline (1.36B-1(h) and Line 4: residence in the taxable year, the three HHS tables, the higher table for movers and split-state spouses) and the territory exclusion. Take the companion values from the Form 8962 tables (Alaska, one person, $18,810 for 2025). Record the encoder blocker in it. Then write axiom: rulespec-us#<n> queued and mention #1627 for family size.

S3. An older open PR fixes the same issue with the same change

#10413 ("Use current ACA household with prior-year poverty guidelines", opened 2026-10-23, 3:02pm EDT) also says "Fixes #9482". It makes the same edit to aca_magi_fraction.py (coverage-year tax_unit_size and state_group_str, the fpg helper with a one-year lag) and adds eight cases to the same YAML file. The description here names #10041 but not #10413.

Two more open PRs touch the same code and are not named:

  • #10312 (draft) is the "income exactly at 400%" item that the description leaves for a follow-up.
  • #10846 (draft) adds the 2013 and 2014 guidelines to fpg.yaml.

Fix: say in the description how this PR relates to #10413 (which one lands, and what the other one keeps), and link #10312 from the follow-up item.


Closed by a9cc99bc13 (open at 391384776f)

I checked these against your two self-review comments by running the code.

Item At 391384776f At a9cc99bc13
pays_aca_premium for a territory household under the floor Real. Puerto Rico, single, $0, 2026: main false, head true. Four variables changed with it (pays_aca_premium, is_aca_family_tier_dependent_child, selected_marketplace_plan_actuarial_value and marketplace_effective_actuarial_value 0 to 0.7). Fixed: false. Removing has_exchange fails 2 cases.
The predicted ParameterNotFoundError from the SLCSP lookup Not real. slcsp, used_aca_ptc and selected_marketplace_plan_premium_proxy are 0 on both trees. household_net_income is the same on both trees for Guam and the Virgin Islands. For Puerto Rico it raises a ValueError on main too. Same.
has_guideline guard not pinned Real. Plain division, and 0 in place of inf, both passed all 36 cases. Fixed. Each now fails the new 2026 Puerto Rico case.
np.isfinite in is_aca_ptc_eligible Real: removing it passed all 36 cases, so the comment's premise was wrong. Removed.
Cite 1.36B-1(d) and 42 U.S.C. 18024(d) Open. Done.
Axiom line Open. Open (S2).
Wording points in the description Open. Open. The description is unchanged, so it still says is_aca_ptc_eligible requires a finite fraction.

Suggestions

  1. Territory test by state, not by an infinite fraction. pays_aca_premium.py:80 reads np.isfinite(magi_frac) as "has an Exchange". A direct test on the state group says the rule where it is, and it survives a refactor of the fraction (see the #10041 note below).
  2. The $18,000 example needs its inputs stated. With earnings in place of a MAGI input, an Alaska single adult at $18,000 is Medicaid-eligible, and is_aca_ptc_eligible is false on main and here. Alaska and Hawaii are both expansion states, so the households that lose the credit under the floor are those barred from Medicaid for another reason.
  3. The first-modeled-year guard after #10846. This PR no longer computes a variable for 2014; it reads only the parameter. When #10846 adds the 2014 guideline, the guard can go and 2015 coverage can use the guideline that 36B(d)(3)(B) calls for.
  4. The ACA calculator figures. For a single 40-year-old in Anchorage Municipality earning $40,000 in 2027 I get $11,969.37 on main and $12,739.84 here. The $770 change matches yours; the levels ($9,177 and $9,947) do not.
  5. Update the description for a9cc99bc13: the Territories bullet, the Tests list (37 cases, the new 2026 Puerto Rico case) and "I didn't run these locally".

Interaction with #10041

At its head (18385c186b), #10041's aca_fpg reads state_group_str for the guideline year and its aca_magi_fraction is np.floor(100 * magi / aca_fpg) / 100.

  • If this PR merges first, #10041 must: read the state group for period in aca_fpg; keep the zero-guideline handling (its plain division gives NaN for a territory household with $0, and pays_aca_premium here depends on an infinite value); keep these test cases; and resolve the conflicts in aca_magi_fraction.py, pays_aca_premium.py, is_aca_ptc_eligible.py and the two YAML files.
  • If #10041 merges first, this PR must move the coverage-year state into aca_fpg, keep only the zero-guideline handling and the truncation in aca_magi_fraction, and drop the direct fpg import. Its multiply-first order then stays, which also settles C1.

What I checked

Law.

  • 26 U.S.C. 36B(d)(3)(B): "the most recently published poverty line as of the 1st day of the regular enrollment period". Prior-year guideline: confirmed.
  • 26 CFR 1.36B-1(h): the guideline in effect at the open enrollment before the taxable year, and the higher guideline for a mid-year move or spouses in separate states. Confirmed. The #h anchor exists on the cited page.
  • 26 CFR 1.36B-1(d)(1) and (d)(2): family "for the taxable year". Confirmed. Paragraph (o)(2) applies (d)(2) from 2020.
  • Form 8962 instructions (2025), file page 8: "Check the box to indicate your state of residence in 2025 ... (For 2025, the 2024 federal poverty lines are used ...) If you moved during 2025 and you lived in Alaska and/or Hawaii ... use the table with the higher dollar amounts". Page 9 has Tables 1-2 and 1-3 and Worksheet 2. The cited URL and page show the rule.
  • 36B(c)(2)(A) and 42 U.S.C. 18024(d) ("each of the 50 States and the District of Columbia"): confirmed.
  • One model limit, which the code comment states: a move between years uses the coverage year's state, and a move within the year cannot be represented.

Households (main then 391384776f; a9cc99bc13 is the same except pays_aca_premium in the territories). Coverage inputs are false where eligibility is shown.

Case main PR
Alaska single, $30,000, 2026 1.91; aca_ptc $10,799.92 1.53; $11,349.40
Hawaii single, $30,000, 2026 1.91 1.66
Alaska family of three, $50,000, 2026 1.87 1.50
Alaska 2025, Texas 2026, $30,000 1.53 1.91
Alaska, 2025 coverage, $30,000 1.99 1.59
Alaska single, $18,000, 2026 1.15; eligible 0.92; not eligible
Alaska single, $70,000, 2026 4.47; not eligible 3.58; eligible; aca_ptc $5,677.78
Alaska single, $78,199, 2026 4.99; not eligible 3.99; eligible
Alaska single, $78,200, 2026 4.99; not eligible 4.00; not eligible
Texas single, $62,600, 2026 (exactly 400%) 4.00; not eligible 4.00; not eligible
Puerto Rico single, $30,000, 2024 2.05; eligible inf; not eligible
Alaska and Texas, 2015 coverage, $30,000 2.03 and 2.54 2.03 and 2.54
Alaska supplied for both years 1.53 1.53
Alaska supplied for 2025 only 1.53 1.91 (2026 is the default state)
Texas 2025, Alaska 2026 1.91 1.53
Alaska 2025, Hawaii 2026 1.53 1.66
Alaska, tax_unit_size: 3 for 2026 only, $50,000 3.19 1.50
Alaska, tax_unit_size: 3 for 2025 only, $50,000 1.87 2.55
Alaska, Hawaii, Texas, Puerto Rico and Guam in one simulation 1.91 each 1.53, 1.66, 1.91, inf, inf
The issue's axis (Alaska, earnings $61,000 to $81,000), premium_tax_credit $5,059, 0, 0, 0, 0 $5,059, $4,561, $4,063, $3,565, 0
Alaska single, earnings $40,000, 2026 2.61; no CSR; aca_ptc $9,052.28 2.09; CSR 73% AV; $9,807.99
Hawaii single, earnings $40,000, 2026 2.55; no CSR; $3,058.49 2.22; CSR 73% AV; $3,531.45

The 400% rows confirm that the cliff at exactly 400% is the same on main.

All 7,644 variables, main against the PR:

  • Texas and California households (adult and child, $30,000, 2026): no variable differs. The state premium programs that read the fraction (California, Colorado, Connecticut, Maryland, Massachusetts, New Jersey, New Mexico, Vermont, Washington) are all in contiguous states.
  • Alaska, adult and child, $30,000: 2 variables (the fraction and the required contribution).
  • Hawaii, adult and child, $45,000: 27 variables, all in the credit's chain; household_net_income $46,368.51 to $46,892.58; CSR 73% to 87% AV.

Microsimulation (enhanced_cps_2024, 55,264 tax units, at 391384776f):

Year Scope aca_ptc, main PR Tax units that differ
2026 All $77,177.09m $77,177.09m 0
2026 Alaska (156 rows) $59.57m $59.57m 0
2026 Hawaii (278 rows) $11.08m $11.08m 0
2025 All $80,228.63m $80,228.63m 0
2024 All $62,540.51m $62,543.44m 18
2024 Alaska $183.82m $186.53m 8
2024 Hawaii $9.86m $10.08m 10

Weighted is_aca_ptc_eligible people are the same on both trees in each year (2026: 17,758,072). This confirms your mechanism: the dataset's state carries forward, so only the dataset's own year (2024) changes, by $2.93m. The dataset has no territory rows.

Tests (Python 3.13.3, policyengine-core 3.34.10):

Suite 391384776f Merged into main (47c4e8720b) a9cc99bc13
The two changed YAML files 36 passed included below 37 passed
baseline/gov/aca 356 passed 356 passed 424 passed, run together with contrib/aca
contrib/aca 67 passed 67 passed (in the 424)
baseline/gov/hhs 2,707 passed 2,709 passed not run
baseline/partners 623 passed 623 passed 623 passed

No failures. The merge into main is clean. The PR edits no partner file.

Does a test pin each changed line? I changed one line at a time and ran the two files.

Change Result
Family size read for the guideline year 1 fails: pinned
State read for the guideline year 11 fail: pinned
Guideline not lagged 11 fail: pinned
First-modeled-year guard removed 1 fails: pinned
Contiguous guideline for everyone 10 fail: pinned
has_exchange removed (a9cc99bc13) 2 fail: pinned
Plain division; 0 in place of inf (a9cc99bc13) 1 fails each: pinned
Multiply before dividing all pass: not pinned (C1)
fpg.yaml entries removed all pass: not pinned (S1)

Repository rules. The changelog fragment is a top-level file, named for the branch, type fixed. The axiom line is S2.

CI. All 38 checks passed at 391384776f. At a9cc99bc13, at 11:37am EST: 17 passed, 9 running, none failed.

Not verified

  • CI runs on Python 3.14 and may install a different policyengine-core. I ran Python 3.13.3 with policyengine-core 3.34.10.
  • At a9cc99bc13 I did not run baseline/gov/hhs, the merge into main, the microsimulation or the all-variable comparison. Its code change touches only pays_aca_premium and is_aca_ptc_eligible.
  • The microsimulation is one dataset and three years. It has no territory households, so it says nothing about them.
  • The rest of the suite (states, IRS, household, the other contrib reforms). CI covers it.
  • The ACA calculator's dollar levels (suggestion 4).
  • I read 26 CFR 1.36B-1 on Cornell LII, not on eCFR.
  • I ran nothing in rulespec-us. I read its pipeline file, its test file and its open issues.
  • I checked the all-variable comparison on twelve households with one adult and one child. Other family shapes in the territories are not covered.

DTrim99 and others added 2 commits November 13, 2026 13:40
…t territories by state group

Per Pavel's review:
- C1: compute 100 * MAGI / guideline before flooring. Dividing first floored
  income exactly on a whole percentage one point low in every state (Texas,
  $35,369 in 2026: 2.25 instead of 2.26). New case pins it.
- S1: restore fpg.yaml. Core backdates each parameter's first value to 2015,
  so the 2024 territory placeholder already answers every guideline year the
  credit reads; the 1992 entries only changed 2015 results outside the ACA.
  Correct the changelog and the 2024 Puerto Rico case's comment.
- Suggestion 1: pays_aca_premium tests the state group for Guam, Puerto Rico
  and the Virgin Islands instead of reading an infinite fraction.
- Add PR 10413's negative-income case.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
main already multiplies before dividing, so the truncation order changes
nothing against main; only this PR's earlier commit had regressed it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@DTrim99

DTrim99 commented Nov 13, 2026

Copy link
Copy Markdown
Collaborator Author

@PavelMakarchuk, thanks for running all of this. f72af6389b and 1e306d09cc take every item. The description is rewritten for the new head.

C1, truncation order: fixed. aca_magi_fraction computes 100 * magi / guideline before flooring, as main does. A new case pins it: Texas, single, 2026, aca_magi: 35_369, expecting 2.26. The changelog doesn't mention it, since the order is unchanged against main.

S1, fpg.yaml: you're right, and so was the error claim I made in my own round 1: core's 2015 backdating already covers it. I took the edit out; fpg.yaml now matches main. I also corrected:

  • the changelog, which no longer says anything about an error;
  • the 2024 Puerto Rico case's comment, which now says that on main the state supplied for 2024 only gave the contiguous guideline (2.05, eligible);
  • the description's Territories bullet and its territory effects, where a state supplied for both years "already had an infinite fraction and was not eligible".

S2, axiom: line: done. TheAxiomFoundation/rulespec-us#2210 (pe-parity) covers the guideline by state of residence (1.36B-1(h) and Line 4: the three tables, and the higher one for movers and split-state spouses) and the territory exclusion.

  • Its companion values come from the Form 8962 (2025) tables, for example Alaska, one person, $18,810: $30,000 gives 159.
  • It records the blocker: no pinned corpus release has 26 CFR 1.36B-1, the HHS notices and the Form 8962 instructions together, and 42 U.S.C. 18024 needs ingest.
  • The line is now queued and names Add 2023 poverty line #1627 for family size.

S3, related PRs: a new "Related PRs" section names them.

Suggestions:

  1. pays_aca_premium now tests the state group for Guam, Puerto Rico and the Virgin Islands, not np.isfinite(magi_frac).
  2. The $18,000 example states its inputs, and says that from earnings that household is Medicaid-eligible in Alaska.
  3. The guard follow-up is listed, tied to Add premium tax credit rules for 2014–2017 #10846.
  4. The description gives your direct-run levels ($11,969.37 to $12,739.84) beside the calculator's, and says I haven't traced the difference in levels.
  5. The description covers the head, including your microsimulation result: 18 tax units and $2.93m in 2024, nothing in 2025 or 2026.

A correction to my earlier comment: it said used_aca_ptc raises for a territory household through the family-tier lookup. Your run shows it doesn't, so that follow-up is gone from the description.

As before, I didn't run these locally, so CI is the check.

@PavelMakarchuk PavelMakarchuk left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-review: Use the Alaska and Hawaii poverty guidelines in the premium tax credit's income fraction (#10860)

Verdict: REQUEST_CHANGES

I reviewed 1e306d09cc (the two commits pushed at 1:40pm and 1:41pm EST on 2026-11-13). The critical item and the three should-address items from my last review are all resolved, and I confirmed each one by running the code. One small item is new: the last commit changed the territory test from one mechanism to three separate state comparisons, and two of the three have no test. Two cases close it.

Counts open at 1e306d09cc: 0 critical, 1 should address, 4 suggestions.


Resolved since the last review

Item Status How I checked
C1 Truncation divided before it multiplied Resolved aca_magi_fraction.py:56-57 computes 100 * magi / guideline and then floors. All four households below match main or Worksheet 2. The grids match main in every case. The new Texas case at aca_magi_fraction.yaml:193 pins the order: with the line changed back to divide first, that case fails (1 failed, 38 passed).
S1 fpg.yaml edit and the "raised ParameterNotFoundError" statements Resolved fpg.yaml is identical to main. The PR now changes five files. The changelog, the test comment at is_aca_ptc_eligible.yaml:590-597 and the description no longer state an error, and what they state about main (2.05 and eligible with the state supplied for one year; an infinite fraction and not eligible with the state supplied for both) is what I get.
S2 axiom: n/a Resolved The line is axiom: TheAxiomFoundation/rulespec-us#2210 queued. The issue is open, has the pe-parity label, and covers the guideline by state of residence (1.36B-1(h), Form 8962 Line 4, the three tables, movers and split-state spouses), the Worksheet 2 truncation and the territory exclusion (36B(c)(2)(A), 42 U.S.C. 18024(d)), with 13 companion cases and the encoder blocker recorded. rulespec-us#1627 (open, pe-parity) is named for family size.
S3 #10413, #10312, #10846 not named Resolved The new "Related PRs" section names all three. It states that #10413 makes the same change, that only one of the two should land, and that this PR's cases cover #10413's. The negative-income case from #10413 is added (aca_magi_fraction.yaml:212). All three PRs are still open.
Suggestion 1: test the territory by state Done, with one gap pays_aca_premium.py:79-85. See S1 below.
Suggestion 2: state the inputs of the $18,000 example Done The description states the inputs and the Medicaid point. I get 1.15 to 0.92 with aca_magi: 18_000, and Medicaid eligibility on both trees from $18,000 of earnings.
Suggestion 3: first-modeled-year guard after #10846 Done Listed under follow-ups.
Suggestion 4: ACA calculator levels Done The description gives both sets of levels and says the difference is not traced.
Suggestion 5: update the description Done The description matches the head. Its test list (9 new cases and 5 new cases) matches the files.

C1 witnesses (main at 5cc0d58afa, then the head):

Household Worksheet 2 main 1e306d09cc
Texas, single, 2026, MAGI $35,369 226 2.26; contribution 7.557%; aca_ptc $5,165.69 2.26; 7.557%; $5,165.69
Texas, single, 2025, MAGI $30,873 205 2.05; $5,515.38 2.05; $5,515.38
Hawaii, family of eight, 2026, MAGI $125,223, state supplied for both years 201 2.01; aca_ptc $9,848.49 2.01; $9,848.49
Alaska, single, 2026, MAGI $44,183, state supplied for 2026 226 2.82 (the bug this PR fixes) 2.26; contribution 7.557%; aca_ptc $9,310.96

C1 grids:

  • Single filer, every whole-dollar MAGI that is an exact whole percentage from 95% to 405%: Texas 2026 (155 cases), Texas 2025 (63), California 2026 (155), Alaska 2026 (155), Hawaii 2026 (31). The head is right in all 559. The fraction, eligibility, required contribution, CSR category and aca_ptc equal main in all of them.
  • Family sizes 1 to 8 in Texas, Alaska and Hawaii, at each exact percentage and one dollar either side (22,392 cases, state supplied for both years): the fraction equals main in every case. All 4,488 exact-percentage cases with a MAGI that the model stores exactly are right.

Should address

S1. The Guam and Virgin Islands exclusions have no test

policyengine_us/variables/gov/aca/eligibility/pays_aca_premium.py:79-85:

state_group = person.household("state_group", period)
groups = state_group.possible_values
has_exchange = (
    (state_group != groups.GU)
    & (state_group != groups.PR)
    & (state_group != groups.VI)
)

At a9cc99bc13 the test was np.isfinite(magi_frac), so one Puerto Rico case covered all three territories through the same zero guideline. The test is now three separate comparisons. The changelog and the description claim all three ("Households in Puerto Rico, Guam and the Virgin Islands ... pay no Marketplace premium"), and both territory cases are Puerto Rico (is_aca_ptc_eligible.yaml:590 and :619).

What I ran:

  • With the Puerto Rico comparison removed, 2 cases fail. With the Guam comparison removed, all 39 pass. With the Virgin Islands comparison removed, all 39 pass.
  • Witness: Guam, single adult aged 30, 2026, aca_magi: 30_000, the Medicaid, CHIP, Medicare and employer-coverage inputs false. At the head: pays_aca_premium false, selected_marketplace_plan_actuarial_value 0. With the Guam and Virgin Islands comparisons removed: pays_aca_premium true and the actuarial value 0.7, for Guam and for the Virgin Islands, and no test fails. is_aca_ptc_eligible stays false through the infinite fraction, so the effect is on the premium variables, not on the credit.

Fix: add two cases to is_aca_ptc_eligible.yaml, the same as the 2024 Puerto Rico case with state_code: GU and state_code: VI, each expecting pays_aca_premium: false and is_aca_ptc_eligible: false. I ran both households at the head for 2026 and they give those values.


Suggestions

  1. Update rulespec-us#2210 for the head. Its "PolicyEngine implementation" section still says pays_aca_premium adds has_exchange = isfinite(aca_magi_fraction) and counts 7 and 5 new cases. The head tests the state group and has 9 and 5.
  2. The Northern Mariana Islands. StateCode has MP, and state_group puts it in the contiguous group. A 2026 MP single adult with $30,000 of MAGI has a fraction of 1.91, pays_aca_premium true and is_aca_ptc_eligible true, on main and here (slcsp and aca_ptc are 0). 42 U.S.C. 18024(d) excludes it in the same way as the three territories. This PR does not change it; it is a candidate for the follow-up list.
  3. Puerto Rico in 2027 raises an unrelated error on both trees. A Puerto Rico single adult in 2027 raises ValueError: Unsupported array dtype: float64 from snap_region when pays_aca_premium, is_aca_ptc_eligible, slcsp or aca_ptc is computed. main does the same, so this PR does not cause it. It is worth a separate issue, because the description's "in any year" cannot be shown for Puerto Rico in 2027.
  4. One wording point. The description says "core backdates each parameter's first value to 2015". The backdating is in this repository (backdate_parameters, called from system.py), not in policyengine-core.

Interaction with #10041

At its new head (d4ec8c5cf8), #10041's aca_fpg still reads state_group_str for the guideline year, its aca_magi_fraction is the plain np.floor(100 * magi / fpg) / 100, and it moves the enrollment conditions of pays_aca_premium into is_aca_marketplace_enrollee. The PR that merges second must read the state group for the coverage year in aca_fpg, keep the zero-guideline handling in aca_magi_fraction (plain division gives NaN for a territory household with $0), carry the has_exchange test to where the enrollment conditions then live, and keep these test cases.


What I checked

The diff since a9cc99bc13, line by line (122 lines, 5 files).

  • aca_magi_fraction.py:52-57: the order is main's. The where(has_guideline, guideline, 1) denominator stays, so the formula itself does not divide by zero. In the runs with aca_magi supplied, main emits divide-by-zero and invalid-value warnings from the fraction for territory households and the head emits none. No NaN appears at the head in any run below; main gives NaN for a territory household with $0 and the state supplied for both years.
  • pays_aca_premium.py:79-85: the state group is read for the coverage year, as an enum, and compared with the three enum members. It is vectorised: in one simulation with 42 households in Puerto Rico, Guam, the Virgin Islands, the Northern Mariana Islands, Texas, Alaska and Hawaii, each household gets its own result. is_aca_ptc_eligible requires pays_aca_premium, so the exclusion does not depend on how the eligibility scale treats an infinite fraction.
  • The comments and the two new cases say what the code does. The changelog fragment matches the head: it is a top-level file, type fixed, it states the coverage-year state and family size and the territory rule, and it says nothing about an error or about truncation (the order is unchanged against main).

Territories, 2015 to 2027. Puerto Rico, Guam and the Virgin Islands; $0, $10,000 and $30,000; a single adult and an adult with a child; income as earnings and as an aca_magi input with the coverage inputs false; state supplied for the coverage year only and for both years.

Check main 1e306d09cc
2015 to 2026: errors (864 households, 1,296 people) none none
is_aca_ptc_eligible false with the state for both years; true for 198 of 648 people with the state for one year false for all 1,296
pays_aca_premium true for all 648 people with the state for both years; true for 252 of 648 with the state for one year false for all 1,296
aca_ptc, premium_tax_credit, used_aca_ptc, slcsp 0 0
aca_magi_fraction infinite or NaN (both years); the contiguous fraction (one year) infinite for all 864
aca_required_contribution_percentage NaN at $0 (both years); the contiguous rate (one year) 0 for all 864
2027, Guam and the Virgin Islands, single, $30,000 fraction 1.87, eligible infinite, not eligible, no error
2027, Puerto Rico ValueError from snap_region the same ValueError
  • The changes in pays_aca_premium and is_aca_ptc_eligible are the ones the PR states. Three variables follow pays_aca_premium for those people: selected_marketplace_plan_actuarial_value (0.7 to 0), is_aca_family_tier_dependent_child (true to false for the child) and marketplace_csr_category (to 0).
  • Controls in the same simulations: Texas and Northern Mariana Islands households have the same value on both trees for all 11 variables I compared, in every year from 2015 to 2026. Alaska and Hawaii households differ only where the state is supplied for the coverage year alone.

Households. I re-ran the 74 households from the last review on main (5cc0d58afa) and the head. Every head value equals the value at a9cc99bc13, and every main value equals the value on the earlier main. The C1 households are separate and are in the table above. Examples: Alaska single $30,000 in 2026, 1.91 to 1.53 and aca_ptc $10,799.92 to $11,349.40; Alaska single $70,000, 4.47 and not eligible to 3.58 and eligible with $5,677.78; Alaska single $78,200, not eligible on both trees; Texas single $62,600, 4.00 and not eligible on both trees; negative MAGI in Alaska, 0 on both trees.

Does a test pin each changed line? One change at a time, on the two changed files (39 cases at the head).

Change Result
Divide before multiplying 1 fails: pinned
Plain division, no zero-guideline handling 1 fails: pinned
0 in place of infinity for the territories 1 fails: pinned
has_exchange removed 2 fail: pinned
Puerto Rico comparison removed 2 fail: pinned
Guam comparison removed all pass: not pinned (S1)
Virgin Islands comparison removed all pass: not pinned (S1)
State read for the guideline year 11 fail: pinned
Family size read for the guideline year 1 fails: pinned
First-modeled-year guard removed 1 fails: pinned
Guideline not lagged 12 fail: pinned
Negative MAGI not floored at 0 1 fails: pinned

Tests (Python 3.13, run locally):

Suite 1e306d09cc Merged into main (5cc0d58afa)
The two changed YAML files 39 passed 39 passed
baseline/gov/aca 359 passed 359 passed
contrib/aca 67 passed 67 passed
baseline/partners 623 passed 623 passed

No failures. The merge into main is clean (main is 14 commits ahead of the merge base; none touches the ACA variables, fpg.yaml or the partner folder). The PR edits no partner file.

CI. All 38 checks passed at 1e306d09cc, including Microsimulation and all the Contrib shards (checked at 3:12pm EST on 2026-11-13).

Repository rules. The changelog fragment and the axiom: line are valid (see the table above). #10413 is disclosed.

Not verified

  • I ran the tests on Python 3.13. CI uses Python 3.14 and may install a different policyengine-core.
  • I did not re-run the microsimulation at this head. The figures in the description (18 tax units and $2.93m in 2024, no change in 2025 or 2026) are from my run at 391384776f. The CI Microsimulation job passed at the head. The dataset has no territory households.
  • I did not re-run baseline/gov/hhs or the all-variable comparison. The diff since a9cc99bc13 touches no parameter, and fpg.yaml now equals main.
  • For 2027 I ran only single adults at $30,000 in Guam and the Virgin Islands. Puerto Rico in 2027 raises the unrelated error on both trees, so I could not check it.
  • The ACA calculator's dollar levels. My run for a single 40-year-old Alaska adult earning $40,000 in 2027 in the default county gives $12,169.84 on main and $12,940.31 here; the change of $770.47 matches.
  • I ran nothing in rulespec-us. I read issue #2210 and the state and labels of #1627.
  • The rest of the suite (states, IRS, household, the other contrib reforms). CI covers it.

Per Pavel's re-review: the territory test is three state-group comparisons,
and only Puerto Rico's had a case. Each new case fails without its
comparison.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@DTrim99

DTrim99 commented Nov 13, 2026

Copy link
Copy Markdown
Collaborator Author

@PavelMakarchuk, thanks. 62e9ac8026 closes S1 and takes the suggestions.

S1: is_aca_ptc_eligible.yaml adds your two 2026 households, Guam and the Virgin Islands. Each is a single adult at $30,000 with the Medicaid, CHIP, Medicare and employer-coverage inputs false, expecting pays_aca_premium: false and is_aca_ptc_eligible: false. By my reading, each fails without its comparison. CI will confirm.

Suggestions:

  1. Encode the premium tax credit's poverty guideline by state of residence, with Alaska, Hawaii and movers (26 CFR 1.36B-1(h)), Form 8962 Worksheet 2 truncation, and no credit in the territories (PE-US PR 10860) TheAxiomFoundation/rulespec-us#2210's "PolicyEngine implementation" section is updated for this head:
    • has_exchange by state group, with the Northern Mariana Islands noted as not yet excluded;
    • multiply-first truncation;
    • fpg.yaml unchanged;
    • 9 and 7 new cases.
  2. The Northern Mariana Islands are now on the follow-up list.
  3. Puerto Rico in 2027 is on the follow-up list. The "in any year" claim is now "every year checked from 2015 to 2026", plus 2027 for Guam and the Virgin Islands. I can open a separate issue for the snap_region error if you'd like.
  4. The backdating is attributed to this repository's backdate_parameters, called from system.py.

The #10041 bullet also says the PR that lands second carries has_exchange to wherever #10041 moves the enrollment conditions.

#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>
@DTrim99

DTrim99 commented Nov 13, 2026

Copy link
Copy Markdown
Collaborator Author

#10041 merged, so d5909647d8 merges main and moves this PR's changes to where #10041 put the code, as your re-review laid out:

  • aca_fpg reads the state group for the coverage year. It lags only the guideline, through fpg(size, state_group, guideline_period, parameters). California's two reads of aca_fpg take the same state; California is contiguous, so nothing changes there.
  • 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, so it follows from enrollment.
  • Family size: the coverage-year case now supplies aca_tax_family_size, which aca_fpg reads since 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. A supplied tax_unit_size no longer reaches the fraction for a return with a tax family.
  • Tests: both sides' cases are kept. The invariant test's docstring now notes that its states are all contiguous, so aca_fpg still equals the prior-year tax_unit_fpg there.

The description and the changelog are updated. As before, CI is the check; I haven't run anything locally.

@PavelMakarchuk PavelMakarchuk left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-review: Use the Alaska and Hawaii poverty guidelines in the premium tax credit's income fraction (#10860)

Verdict: REQUEST_CHANGES

I reviewed d5909647d8 (the commit at 3:53pm EST and the merge of main at 4:02pm EST on 2026-11-13). #10041 is now in main, so I reviewed the whole diff against main (a8ab4228b3) as new code, not only the delta.

The Guam and Virgin Islands item from my last review is resolved, and the move into aca_fpg, aca_magi_fraction and is_aca_marketplace_enrollee is correct in every household I ran. One small item is new, and the move caused it: the case that pinned the multiply-first truncation no longer pins it. One changed number in one test case closes it.

Counts open at d5909647d8: 0 critical, 1 should address, 5 suggestions.


Resolved since the last review

Item Status How I checked
S1 The Guam and Virgin Islands exclusions had no test Resolved is_aca_ptc_eligible.yaml:991 and :1016. With the Guam comparison removed from is_aca_marketplace_enrollee.py:70-74, 1 case fails. With the Virgin Islands comparison removed, 1 case fails. With the Puerto Rico comparison removed, 2 fail. With has_exchange removed, 4 fail.
Suggestion 1: update rulespec-us#2210 Done at 62e9ac8026, stale again after the move See suggestion 3.
Suggestion 2: the Northern Mariana Islands Done On the follow-up list.
Suggestion 3: Puerto Rico in 2027 Done On the follow-up list, and "in any year" is now "every year checked from 2015 to 2026". Both trees still raise the same ValueError from snap_region.
Suggestion 4: the backdating wording Done The description names backdate_parameters in this repository.
The move after #10041 merged Done The coverage-year state is in aca_fpg.py:51, the zero-guideline guard and the multiply-first line are in aca_magi_fraction.py:33-38, and the no-Exchange test is in is_aca_marketplace_enrollee.py:68-78. pays_aca_premium.py equals main.

Should address

S1. The whole-percentage case no longer pins the multiply-first order

policyengine_us/tests/policy/baseline/gov/aca/eligibility/aca_magi_fraction.yaml:193-210:

- name: Income exactly on a whole percentage of the guideline is not floored one point low.
  # 100 x 35,369 / 15,650 = 226 exactly, so Worksheet 2 gives 226. Dividing
  # first gives 35,369 / 15,650 = 2.2599999..., which floors to 2.25.
  ...
        aca_magi: 35_369
  ...
    aca_magi_fraction: 2.26

The description says "A case now pins the order", and the comment in aca_magi_fraction.py:34-36 uses the same $35,369 example. That was true at 1e306d09cc. It is not true at this head.

  • Why it changed. Before the merge, the fraction divided by a guideline that came straight from the parameter (64-bit). Since #10041 it divides by the aca_fpg variable, which is stored in 32 bits like aca_magi. In 32-bit arithmetic 35,369 / 15,650 times 100 rounds to exactly 226, so dividing first also gives 2.26 for this household.
  • What I ran. I changed aca_magi_fraction.py:37-38 to divide first (fraction = magi / guideline, then floor(100 * fraction) / 100) and ran the two changed YAML files: all 55 cases pass. At 1e306d09cc the same change failed 1 case.
  • The order still matters. Of 4,608 single-state households on an exact whole percentage (2024 and 2025 guidelines, family sizes 1 to 8, 95% to 405%), dividing first in 32-bit arithmetic is one point low in 184. Multiplying first is right in all of them.

Witness (Texas, single adult aged 40, 2026 coverage, 2025 guideline $15,650):

aca_magi Worksheet 2 main d5909647d8 Divide-first variant
$35,369 (the case in the file) 226 2.26 2.26 2.26 (not caught)
$36,621 234 2.34 2.34 2.33
$32,865 210 2.10 2.10 2.09
$16,589 106 1.06 1.06 1.05

Fix: in the case at aca_magi_fraction.yaml:193, use aca_magi: 36_621 and expect aca_magi_fraction: 2.34 (100 x 36,621 / 15,650 = 234 exactly), and update the two comments to that example. I ran that case: it passes on main and at the head, and it fails with the divide-first variant.


Suggestions

  1. The invariant test compares aca_fpg with the wrong quantity for Alaska and Hawaii. test_claimable_filer_ptc_invariants.py:347-350 asserts aca_fpg(year) == tax_unit_fpg(year - 1). The docstring edit at :34-36 is accurate: no drawn state is outside the contiguous group, so the test passes and hides no failure of the model. But the identity itself is the pre-fix one. I ran the test's own _identities on an Alaska couple with no claimable filer and the state supplied for the coverage year, as the test supplies it:

    2026, Alaska couple, nobody claimable aca_fpg tax_unit_fpg(2025) _identities
    main 21,150 21,150 passes (both are the contiguous amount: the bug)
    d5909647d8 26,430 21,150 fails, 4 of 4 units

    26,430 = 19,550 + 6,880 is the right value. The right invariant is: aca_fpg(year) equals the prior year's guideline for the coverage year's state group and the coverage year's tax family size, that is fpg(aca_tax_family_size, state_group_str(year), year - 1). That identity holds at the head for every unit I drew in Texas, California, Alaska and Hawaii (32, 16 and 16 units, 2025 and 2026), and fails for Alaska and Hawaii on main. With it, AK or HI can join STATES, and the test then covers the claimable-filer rule and this PR together. Line 36 of the docstring is also longer than the lines around it.

  2. Two test comments name the old location. is_aca_ptc_eligible.yaml:993 and :1018 say "Without the Guam comparison in pays_aca_premium". The comparison is in is_aca_marketplace_enrollee. Each of the four territory cases could also assert is_aca_marketplace_enrollee: false, which is the value the changelog names first.

  3. rulespec-us#2210 describes the old head again. Its "PolicyEngine implementation" section names 62e9ac8026, says aca_magi_fraction lags the guideline and reads tax_unit_size, and puts has_exchange in pays_aca_premium. At this head the lookup is in aca_fpg, the size is aca_tax_family_size, and has_exchange is in is_aca_marketplace_enrollee. Its note 4 ("#1627's membership rules arrive with #10041") and its "Related" line about #10041 can now say that #10041 merged.

  4. Four points in the description.

    • "A case now pins the order": see S1.
    • The direct-run levels for the Anchorage example ($11,969.37 on main, $12,739.84 here). I get $12,169.84 on main and $12,940.31 here for a single 40-year-old Alaska adult earning $40,000 in 2027 in the default county. The change of $770.47 is the same.
    • #10413: since #10041 merged, GitHub reports it as conflicting with main, and its change is to aca_magi_fraction, which no longer holds the lookup. "Only one of the two should land" can now say that #10413 needs a rebase onto aca_fpg to do anything, or can close.
    • The microsimulation levels moved with #10041: I now get $62,542.37m to $62,545.30m for 2024 (the same +$2.93m and the same 18 tax units). The description does not list the docstring edit in test_claimable_filer_ptc_invariants.py.
  5. A supplied enrollment input in a territory. is_aca_marketplace_enrollee.py:82-83 adds a supplied pays_aca_premium after the no-Exchange test, as #10041 designed it for every other condition. So a Guam adult with pays_aca_premium: true supplied is an enrollee with a selected-plan actuarial value of 0.7. The credit is still 0 in every case I ran (see the table below), because the infinite fraction fails the income test. A sentence in the variable's documentation would make that choice explicit. No change to the code is needed.


What I checked

The whole diff against main, line by line (7 files, 437 added lines).

  • aca_fpg.py. The state group is read for the coverage year (:51) and only the guideline lags (:41-43, :52). fpg(size, state_group, guideline_period, parameters) reads the parameters at 1 January of the guideline year, which is what main's year_lag call read. The first-modeled-year guard is unchanged: Alaska in 2015 gives the same value on both trees, and Alaska in 2016 uses Alaska's 2015 guideline ($14,720, where main gave $11,770). The family size is aca_tax_family_size for the coverage year. A unit with no tax family keeps the whole-unit convention, now with the coverage-year state (Alaska couple, both filers claimable: 21,150 to 26,430; no credit on either tree).
  • Readers of aca_fpg. Two formulas read it besides aca_magi_fraction: ca_premium_subsidy_eligible.py:58 and ca_premium_subsidy_applicable_percentage.py:34. California is in the contiguous group, so both get the same value as on main. A California household is identical on both trees for all 7,660 variables.
  • Readers of aca_magi_fraction. The federal credit and cost-sharing variables, the Basic Health Program, the three contributed ACA reforms, and the Vermont, Massachusetts, Colorado, Connecticut, New Mexico, New Jersey, Maryland and Washington premium programs. None is an Alaska, Hawaii or territory program, and each is limited to its own state, so none sees a changed value.
  • Supplied inputs. A supplied tax_unit_fpg (for 2025 or for 2026) does not reach the fraction on either tree, as since #10041. A supplied aca_fpg is used as given on both trees (Alaska, $30,000, aca_fpg: 10_000: 3.00 on both). A supplied tax_unit_size does not reach the fraction for a return with a tax family, as the description says.
  • aca_magi_fraction.py. The denominator is where(has_guideline, guideline, 1), so the formula does not divide by zero. Territories get an infinite fraction: no NaN at the head in any run below, where main gives NaN for a territory household with $0.
  • is_aca_marketplace_enrollee.py. The state group is read for the coverage year as an enum and compared with the three members. It is vectorised: in one simulation with 42 households across Puerto Rico, Guam, the Virgin Islands, the Northern Mariana Islands, Texas, Alaska and Hawaii, each household gets its own result. Reading the state group for the prior year instead fails 4 cases, so the year is pinned. Because the test is on the state group and not on the guideline, a Guam household with a supplied aca_fpg of 15,650 (fraction 1.91) is still not an enrollee.
  • The changelog fragment is a top-level file of type fixed. It names aca_fpg, the coverage-year state, and the territory rule in the order the code applies it (not enrollees, no premium, not eligible). It matches the head.

Territory households with supplied enrollment or premium inputs (single adult, 2026, aca_magi: 30_000, the coverage inputs false, state supplied for 2026; Guam shown, Puerto Rico and the Virgin Islands give the same):

Supplied main d5909647d8
nothing enrollee, eligible, aca_ptc 0 (slcsp 0) not an enrollee, not eligible, 0
is_aca_marketplace_enrollee: true or pays_aca_premium: true eligible, aca_ptc 0 enrollee as supplied, not eligible, 0
premium $6,000 coverage-family premium $6,000, used_aca_ptc 0 $6,000, used_aca_ptc 0
premium $6,000 and slcsp $7,000 aca_ptc $5,150.14, used_aca_ptc $5,150.14 aca_ptc 0, used_aca_ptc 0
premium, slcsp and enrollee aca_ptc $5,150.14 aca_ptc 0, used_aca_ptc 0
  • The supplied-premium rule from #10041 (aca_ptc_coverage_family_premium.py) still assigns a supplied premium to the tax family when the model finds no enrollee. That does not create a credit for a territory household: used_aca_ptc is capped by aca_ptc, which is 0. I got the same result for 2020, 2024 and 2025, with the state supplied for one year and for both.
  • Texas and Northern Mariana Islands households with the same supplied inputs are identical on both trees.

Households, main then the head.

Household main d5909647d8
Texas, single, 2026, $35,369 2.26; aca_ptc $5,165.69 the same
Texas, single, 2025, $30,873 2.05 the same
Hawaii, family of eight, 2026, $125,223, state for both years 2.01; $16,343.79 the same
The same, state for 2026 only 2.31; $14,961.32 2.01; $16,343.79
Alaska, single, 2026, $44,183 2.82; $8,490.92 2.26; $9,310.96
Alaska, single, 2026, $30,000 1.91; $10,799.92 1.53; $11,349.40
Alaska in 2025, Texas in 2026, $30,000 1.53; $6,538.08 1.91; $5,988.60
Texas in 2025, Alaska in 2026, $30,000 1.91; $10,799.92 1.53; $11,349.40
Alaska, single, 2026, $18,000 1.15; eligible 0.92; not an enrollee, not eligible
Alaska, single, 2026, $70,000 4.47; not eligible 3.58; $5,677.78
Alaska couple, one filer claimable (tax family of 1 in a tax unit of 2), 2026, $29,325 aca_fpg 15,650; 1.87; $10,898.08 aca_fpg 19,550; 1.50; $11,421.06
The same, state for both years 19,550; 1.50; $11,421.06 the same
Hawaii couple, one filer claimable, 2025, $25,965 15,060; 1.72; $5,685.43 17,310; 1.50; $5,913.93
Texas couple, one filer claimable, 2026, $23,475 15,650; 1.50; $6,854.85 the same
  • The Hawaii family-of-eight figure differs from my last review ($9,848.49) because this run uses two adults and six children, not one adult and seven.
  • In the claimable-filer cases the claimable spouse is not eligible and the other spouse is, on both trees; aca_ptc_slcsp is half of slcsp on both.

Exact-percentage grids. Single filers on every whole-dollar exact percentage from 95% to 405% (Texas 2026 and 2025, California 2026, Alaska 2026, Hawaii 2026: 559 cases): the head is right in all 559, and the fraction, eligibility, required contribution, CSR category and aca_ptc equal main in all of them. Family sizes 1 to 8 in Texas, Alaska and Hawaii at each exact percentage and one dollar either side (22,392 cases, state supplied for both years, aca_tax_family_size supplied): the fraction equals main in every case, and all 7,452 whole-dollar cases that the model stores exactly are right.

Territories, 2015 to 2027. Puerto Rico, Guam and the Virgin Islands; $0, $10,000 and $30,000; a single adult and an adult with a child; income as earnings and as an aca_magi input; state supplied for one year and for both.

Check main d5909647d8
2015 to 2026: errors (864 households, 1,296 people) none none
NaN fraction and required contribution at $0 (156 households) none
is_aca_marketplace_enrollee, pays_aca_premium true for all 648 people with the state for both years; true for 252 of 648 with one year false for all 1,296
is_aca_ptc_eligible true for 198 of 648 people with the state for one year false for all 1,296
aca_ptc, used_aca_ptc, premium_tax_credit, slcsp, aca_ptc_slcsp 0 0
aca_magi_fraction; aca_required_contribution_percentage infinite, NaN or the contiguous value infinite; 0
2027, Guam and the Virgin Islands (48 households) eligible at $30,000 with the state for one year no error, no NaN, not eligible, all amounts 0
2027, Puerto Rico ValueError from snap_region the same ValueError

Controls: no value differs from main outside Alaska, Hawaii and the territories.

  • In the territory simulations, the Texas and Northern Mariana Islands households have the same value on both trees for all 16 variables, in every year from 2015 to 2027. Alaska and Hawaii households differ only where the state is supplied for the coverage year alone.
  • All 7,660 variables, an adult with a child: Texas 2026, California 2026 and New York 2025 are identical on both trees. Alaska 2026 and Hawaii 2024 with the state supplied for both years are identical too.
  • Alaska 2026 with the state for one year (earnings of $30,000, Medicaid-eligible): 3 variables differ (aca_fpg, aca_magi_fraction, aca_required_contribution_percentage).
  • Guam 2026: 16 variables differ, all of them enrollment, eligibility, cost-sharing and actuarial-value variables that follow is_aca_marketplace_enrollee.

Does a test pin each changed line? One change at a time, on the two changed YAML files (55 cases at the head).

Change Result
Divide before multiplying all pass: not pinned (S1)
Plain division, no zero-guideline handling 1 fails: pinned
0 in place of infinity for the territories 1 fails: pinned
has_exchange removed 4 fail: pinned
Puerto Rico comparison removed 2 fail: pinned
Guam comparison removed 1 fails: pinned
Virgin Islands comparison removed 1 fails: pinned
has_exchange reads the prior year's state group 4 fail: pinned
aca_fpg reads the state for the guideline year 11 fail: pinned
aca_fpg reads the family size for the guideline year 33 fail: pinned
First-modeled-year guard removed 1 fails: pinned
Guideline not lagged 21 fail: pinned
Negative MAGI not floored at 0 1 fails: pinned

Tests at d5909647d8 (Python 3.13, policyengine-core 3.34.10, run locally):

Suite Result
The two changed YAML files 55 passed
baseline/gov/aca 518 passed
contrib/aca 76 passed
State premium programs that read aca_fpg or aca_magi_fraction (California, Vermont, Massachusetts, Colorado, Connecticut, New Mexico, New Jersey, Maryland, Washington) and the Basic Health Program 835 passed
test_claimable_filer_ptc_invariants.py, test_aca_ptc_filing_gate_invariants.py, test_aca_required_contribution_percentage_properties.py, test_premium_input_contract.py and the three California premium subsidy unit tests 38 passed
baseline/household/income 224 passed
baseline/partners 623 passed

No failures. The PR edits no partner file.

Microsimulation (enhanced_cps_2024, 55,264 tax units, main then the head):

Year aca_ptc, all aca_ptc, Alaska aca_ptc, Hawaii used_aca_ptc, all
2024 $62,542.37m to $62,545.30m (+$2.93m) $183.82m to $186.53m (+$2.71m) $9.86m to $10.08m (+$0.22m) $43,665.21m, unchanged
2025 $80,230.82m, unchanged $246.97m, unchanged $10.87m, unchanged $55,740.67m, unchanged
2026 $77,178.31m, unchanged $59.57m, unchanged $11.08m, unchanged $54,887.60m, unchanged
  • 2024: aca_fpg changes in 434 tax units, all in Alaska (156) and Hawaii (278). aca_ptc changes in 18 of them (8 in Alaska, 10 in Hawaii), always upward. used_aca_ptc changes in 9 rows, all with a weight of 0. No row outside Alaska and Hawaii changes in aca_fpg, the fraction, aca_ptc or used_aca_ptc. Eligibility changes for no person.
  • 2025 and 2026: no row changes in any of those variables. The dataset has no territory rows.

CI. At 4:58pm EST on 2026-11-13: 33 checks passed, 6 Contrib shards were still running, none had failed. Quick Feedback, PR validation (lock, guard tests, lint, changelog), Household API Partners, Microsimulation and every Baseline shard had passed.

Repository rules. The changelog fragment is valid and matches the code. The axiom: line is valid: rulespec-us#2210 is open with the pe-parity label and covers the guideline by state of residence, the Worksheet 2 truncation and the territory exclusion; rulespec-us#1627 (open, pe-parity) is named for family size. #10413 is disclosed (see suggestion 4 for its new state). #10312 and #10846 are still open drafts.

Not verified

  • I ran the tests on Python 3.13. CI uses Python 3.14 and may install a different policyengine-core.
  • The six Contrib CI shards had not finished when I checked.
  • Microsimulation: the default dataset only, 2024 to 2026, and only aca_fpg, aca_magi_fraction, is_aca_ptc_eligible, is_aca_marketplace_enrollee, aca_ptc and used_aca_ptc. I did not compare state premium programs, taxes or net income in the microsimulation. The dataset cannot show the territory change.
  • For 2027 I ran Guam and the Virgin Islands only. Puerto Rico in 2027 raises the unrelated error on both trees.
  • The all-variable comparison used one household shape (an adult with a child) in five states. I did not run it for Hawaii with the state supplied for one year or for Puerto Rico and the Virgin Islands.
  • The ACA calculator's dollar levels (PolicyEngine/ACA-Calc#37). Only the change of $770.47 is confirmed.
  • I ran nothing in rulespec-us. I read issue #2210 and the state and labels of #1627.
  • The rest of the suite (the other state, IRS and contrib tests). CI covers it.

…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>
@DTrim99

DTrim99 commented Nov 13, 2026

Copy link
Copy Markdown
Collaborator Author

@PavelMakarchuk, thanks. d5f3f7a6b8 closes S1 and takes the five suggestions.

S1: the whole-percentage case now uses aca_magi: 36_621 and expects 2.34, which is 100 × 36,621 / 15,650 = 234 exactly. Both comments use that example. In float32, dividing first gives 233 and multiplying first gives 234, so the case catches a divide-first formula again. I checked that with numpy; I didn't run the YAML. The $35,369 case gave 226 either way.

Suggestions:

  1. Invariant test: the identity now compares aca_fpg(year) with fpg(aca_tax_family_size, state_group_str(year), year - 1), through the fpg helper and the tax unit's household projection. The docstring says the same. I didn't add AK or HI to STATES, because that adds simulations to a property test, and CLAUDE.md asks for timing measurements first. The identity is ready for them.
  2. Territory cases: the comments now name is_aca_marketplace_enrollee, and all four cases assert is_aca_marketplace_enrollee: false.
  3. rulespec-us#2210: its "PolicyEngine implementation" section now describes this head:
  4. Description:
  5. Documentation: is_aca_marketplace_enrollee now says that a supplied pays_aca_premium of true still marks an enrollee in the territories, and that the credit stays 0 because the infinite fraction fails the income test.

CI is the check, as before.

@PavelMakarchuk PavelMakarchuk left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict: APPROVE (head d5f3f7a6b8)

The one open item from my last review is closed, and the five suggestions are taken. I found no new defect in the commit. Nothing is open.

Resolved since the last review

Item Status
S1: the whole-percentage case did not pin the multiply-first order Resolved. aca_magi_fraction.yaml now uses aca_magi: 36_621 and expects 2.34. I changed aca_magi_fraction.py to divide first and ran the file: 1 failed, 14 passed. At the head: 15 passed.
Suggestion 1: the invariant compared aca_fpg with the prior-year tax_unit_fpg Done. The identity now uses the prior year's guideline for the coverage year's tax family size and state group. For an Alaska couple in 2026 both sides give 26,430; the old right-hand side gives 21,150. The 14 tests in test_claimable_filer_ptc_invariants.py pass.
Suggestion 2: territory test comments named pays_aca_premium Done. The comments name is_aca_marketplace_enrollee, and the four territory cases now also assert is_aca_marketplace_enrollee: false.
Suggestion 3: TheAxiomFoundation/rulespec-us#2210 described an older commit Done. The issue is open with pe-parity and was updated on 2026-11-13 at 5:08pm EST. The axiom line names it as queued.
Suggestion 4: stale points in the description Done. The description has the $36,621 example, and it says that #10413 conflicts with main.
Suggestion 5: a supplied pays_aca_premium in a territory Done as documentation in is_aca_marketplace_enrollee.

What I checked

  • The diff since d5909647d8 (5 files, 31 lines added, 16 removed), line by line. The only change to a formula file is a comment in aca_magi_fraction.py and the documentation string in is_aca_marketplace_enrollee.py. No value changes.

  • Households at this head (2026, single adult aged 40 unless stated).

    Household aca_fpg aca_magi_fraction
    Texas, MAGI $36,621 15,650 2.34
    Texas, MAGI $35,369 15,650 2.26
    Alaska, MAGI $44,183 19,550 2.26
    Alaska, MAGI $30,000 19,550 1.53
    Hawaii, MAGI $30,000 17,990 1.66
    Alaska, MAGI $18,000 19,550 0.92
    Alaska, two adults, MAGI $40,000 26,430 1.51

    These equal the values from my last review.

  • Tests at this head. The two changed YAML files: 55 passed. baseline/gov/aca and contrib/aca: 594 passed. test_claimable_filer_ptc_invariants.py: 14 passed.

  • Partner contract tests. The PR edits no file under policyengine_us/tests/policy/baseline/partners/. The folder passes at this head (623 passed).

  • Merge. The head merges into current main (f926615321) without conflicts. The five commits that main added since the merge base do not touch ACA files.

  • CI. All 38 checks pass at d5f3f7a6b8, the microsimulation job included. The last check finished on 2026-11-13 at 6:15pm EST.

Not verified

  • I ran the tests on Python 3.13; CI uses 3.14.
  • I did not run the microsimulation again at this head. The commit changes no formula, and the run at d5909647d8 gave the same 2025 and 2026 totals as main.
  • I did not run the state premium program directories or the household income directory again; they passed at d5909647d8, and no formula changed since.

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.

ACA applicable percentage uses the contiguous-states poverty guideline for Alaska and Hawaii

2 participants