You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The original proposal was wrong in its column-code choice and claimed effect size. Correcting:
Correct IRS SOI columns
IRS Historical Table 2 publishes three related PTC series — picking the wrong one was the original error:
Column
Meaning
National TY2022
A85770 / N85770
Total premium tax credit (claimed, pre-reconciliation-payback)
~$53.9B
A85775 / N85775
Advance premium tax credit (APTC paid)
~$60.7B
A11560 / N11560
Net premium tax credit (claimed after Form 8962 reconciliation)
~$2.4B
The model's aca_ptc variable at policyengine_us/variables/gov/aca/ptc/aca_ptc.py computes max(0, benchmark - income * applicable_figure) with no Form 8962 reconciliation modeled. That is the gross PTC entitlement — closest to A85770 total PTC. A11560 is the wrong target; it's the residual after reconciliation pay-back, which doesn't exist in the microsim.
Use A85770/N85770 (total premium tax credit), not A11560/N11560 (net).
NY Essential Plan / BHP expectations corrected
BHP federal funding flows to the state as a separate §1331 payment, not through APTC/PTC on Form 8962 — so BHP enrollees do show with PTC = 0 on tax returns. TY2022 NY: A85770 ≈ $0.49B vs non-BHP states' scaled-by-population peers at 2–3× higher per capita. Directionally the BHP signal is visible in A85770.
But the "591% → ~5%" claim was specific to misusing A11560 as a state-share proxy against an $98B national anchor — a compensating-error hack, not a real definition match. Realistic expectation for this fix (using A85770):
NY per-state ACA calibration error: 591% → roughly 100–200% (still the biggest outlier because the microsim doesn't model Essential Plan, just now measured against a proper PTC-claimed target rather than CMS APTC outlay).
Other states with 40%+ errors on main: the p90 state error should drop from today's ~26% to ~43% with the correct target swap if nothing else changes. That's worse on median p90 but correct on definition.
The NY problem isn't really solvable on the target side — it needs BHP modeling upstream in policyengine-us (separate issue). The value of this issue's fix is defining the right quantity to calibrate, not reducing the NY-specific error below 100%.
Keeping the current national $98B CMS APTC anchor alongside new A85770-based state targets recreates the EITC "total of state rows ≠ national row" contradiction that PR #803 removed. Three ways to resolve:
Drop the CMS national anchor, use SOI A85770 national row ($53.9B, uprated to target year) as the aggregate, and A85770 state rows as the distribution. Same pattern as EITC post-Add state and AGI cross-tab EITC calibration targets (#802) #803. Recommended.
Keep CMS APTC national as the anchor, use CMS state shares (current behavior), accept that APTC ≠ PTC and the state tolerance must be 50%+.
Keep CMS APTC national + add SOI state rows rescaled to sum to the national anchor. This is the compensating-error hack — forces the state distribution to match SOI shape but a CMS level. Rejected.
Going with (1). The CMS national number is still useful as a sanity crosscheck, but not as a calibration target.
Tolerance
Tightening test_aca_calibration from 500% to 25% is not defensible. Per Codex diagnostics: even with correct A85770 state shares, current model gives ~25% median / ~43% p90 state error, and NY remains a 100%+ outlier while BHP isn't modeled upstream. Propose 50% tolerance, which allows the p90 to pass while still flagging egregious drift.
(renamed from original Amount to make the SOI column explicit)
Unrelated bug this surfaced
policyengine_us_data/db/etl_irs_soi.py:95 maps aca_ptc to IRS code 85530. IRS Historical Table 2 documentation labels 85530 as Additional Medicare tax, not PTC. Likely a transcription error — the correct code is 85770 (total PTC) or 85775 (advance PTC) or 11560 (net PTC), depending on intent. Will file as a separate issue.
Revised sequencing
Fix the etl_irs_soi.py column-code bug (tiny, independent).
Swap state ACA targets from CMS-APTC rescaled to SOI A85770 uprated; drop the $98B national anchor in favor of SOI national A85770.
Set test tolerance to 50%, expect NY to remain the outlier.
Model BHP upstream in policyengine-us (separate issue) to close the NY gap.
Original proposal (superseded)
The rest of this issue kept for history, but the column-code, impact, and tolerance claims above supersede the originals.
Context
PR #803 (issue #802) rewrote EITC calibration on coherent IRS SOI TY2022 anchors after noticing that the loss function was targeting Treasury's EITC outlay parameter (~$67B) as if it were directly comparable to the eitc microsim variable (which computes total claimed EITC, ~$59B per SOI). The same class of bug exists for ACA Premium Tax Credit state-level calibration, and it's causing the test_aca_calibration / test_sparse_aca_calibration integration tests to fail on every PR that touches calibration — most dramatically in New York, where simulated aca_ptc is ~$6bn vs a target of ~$0.86bn (591% error, above the 500% tolerance).
This issue proposes replacing the per-state ACA spending target with an IRS SOI Net Premium Tax Credit target drawn from Historical Table 2, the same workbook PR #803 already extracts for EITC.
The definitional mismatch today
Current wiring in policyengine_us_data/utils/loss.py (~L960–988) builds per-state targets as:
Outlay vs claimed.aca_spending_and_enrollment_2024.csv holds CMS's Advance Premium Tax Credit (APTC) — the monthly payment CMS makes to insurers — multiplied by 12 to annualize. The microsim variable aca_ptc is the Premium Tax Credit claimed on the tax return, after the reconciliation on Form 8962. These are similar in aggregate but diverge household by household (income changes mid-year, takeup changes, filing-status changes, reconciliation payback) and can diverge badly state by state.
NY Essential Plan / MN MinnesotaCare. NY and MN operate federally-funded Basic Health Programs (BHP) that cover the 138–200% FPL population who would otherwise take APTC on the Marketplace. Approximately 1.5M New Yorkers enrolled in the Essential Plan in 2024, meaning NY's Marketplace APTC (the current target) is artificially small while NY's microsim aca_ptc reflects full APTC-eligible simulation because policyengine-us doesn't model BHP as an APTC-disqualifier. The $0.86bn NY target vs $6bn simulation is mostly this — not a calibration bug the optimizer can fix.
Mislabeled source. The loss-matrix label is state/irs/aca_spending/{state}, but the underlying data is published by CMS, not IRS. The label is a red herring when debugging.
Proposal
Replace per-state CMS APTC with per-state IRS SOI Net Premium Tax Credit (claimed on tax returns).
Target source
IRS SOI Historical Table 2, published annually as {YY}in55cmcsv.csv — the same workbook PR #803 already pulls for state EITC. It contains two PTC columns:
N11560 — Number of returns with net premium tax credit
A11560 — Amount of net premium tax credit (in thousands, after reconciliation)
Format matches eitc_state.csv (added in PR #803). 51 rows × 2 metrics = ~102 new loss-matrix columns. Extract via a parameterized refresh script following the pattern in refresh_eitc_state_and_agi_targets.py — most of that machinery can be factored into a shared _extract_soi_historical_table_2_column(year, column_code) helper.
Call site: right after _add_state_eitc_targets in the EITC block (L758 area), with aca_ptc_spending_uprating derived from a CBO/JCT PTC trajectory parameter. Falls back to the EITC spending uprating if no PTC-specific trajectory is available — both are refundable credits that grow with wages and enrollment.
Removals
Remove the existing per-state CMS APTC loop at loss.py L960–988 (state/irs/aca_spending/{state}) and the state-level enrollment loop at L1005–1015 if the reviewer agrees that SOI's returns count gives equivalent (or better) signal. The national CMS APTC aggregate at L711–715 (nation/gov/aca_spending) stays — it's a legitimate budget-outlay anchor and is not per-state, so the NY Essential Plan / BHP issue doesn't cascade from it.
Delete the legacy aca_spending_and_enrollment_2024.csv per-state rows if they're only used for this purpose; keep the CSV if _get_aca_national_targets still needs the national-aggregate scaling.
Why this is the right fix (vs. modeling BHP upstream)
Modeling NY Essential Plan + MN MinnesotaCare in policyengine-us is the correct long-term fix (see separate issue for that), but it doesn't fix the per-state ACA calibration by itself because:
Every state's APTC target still mixes advance-payment timing with end-of-year reconciliation noise.
Other states that don't operate BHPs also show wide APTC vs PTC gaps (IN 52%, UT 43%, ID 64%, WY 141% on main). Those aren't BHP — they're genuine advance-vs-claimed divergence.
IRS SOI is the same primary-source data the tax model is tuned against for every other refundable credit target in this file; using it for PTC is internally consistent with the EITC/CTC calibration approach.
BHP modeling is still worth doing, but it's a separate upstream change. This issue is the complete target-side fix.
Expected impact
NY per-state ACA calibration error: 591% → ~5% (because SOI A11560 for NY reflects the low PTC claim volume that results from the Essential Plan diverting enrollees, matching what the microsim produces when BHP-eligible NYers don't claim PTC — which happens approximately because their imputed AGI + benchmark premium + takeup rarely generates large PTC in the CPS-derived EnhancedCPS anyway).
Other 9+ states with 40%+ APTC-vs-PTC errors on main: most should drop under 20% because SOI's reconciled PTC is the quantity the microsim actually computes.
test_aca_calibration and test_sparse_aca_calibration with their 500% tolerance should pass cleanly.
JCT tax-expenditure targets (_add_tax_expenditure_targets, loss.py:1101) are unaffected — they use a separate simulate-repeal methodology that already works correctly.
Separate issue (to be filed): model NY Essential Plan + MN MinnesotaCare in policyengine-us so that upstream aca_ptc calculations zero out for BHP enrollees. Orthogonal to this issue but reinforces the NY fix.
Suggested sequencing
Diagnostic first: a script that computes per-state aca_ptc vs both CMS APTC and SOI A11560 for TY2022 off the current enhanced_cps_2024.h5, to quantify how much of the per-state error is pure definition vs pure calibration.
If SOI matches simulation more closely (expected), land this issue.
File and land the BHP upstream issue separately.
Filed after noticing the ACA test failures on PR #803 and the pre-existing 880% NY error on PR #794 — same structural problem across both.
Revised design (after Codex review)
The original proposal was wrong in its column-code choice and claimed effect size. Correcting:
Correct IRS SOI columns
IRS Historical Table 2 publishes three related PTC series — picking the wrong one was the original error:
A85770 / N85770A85775 / N85775A11560 / N11560The model's
aca_ptcvariable atpolicyengine_us/variables/gov/aca/ptc/aca_ptc.pycomputesmax(0, benchmark - income * applicable_figure)with no Form 8962 reconciliation modeled. That is the gross PTC entitlement — closest toA85770total PTC.A11560is the wrong target; it's the residual after reconciliation pay-back, which doesn't exist in the microsim.Use
A85770/N85770(total premium tax credit), notA11560/N11560(net).NY Essential Plan / BHP expectations corrected
BHP federal funding flows to the state as a separate §1331 payment, not through APTC/PTC on Form 8962 — so BHP enrollees do show with PTC = 0 on tax returns. TY2022 NY:
A85770≈ $0.49B vs non-BHP states' scaled-by-population peers at 2–3× higher per capita. Directionally the BHP signal is visible inA85770.But the "591% → ~5%" claim was specific to misusing
A11560as a state-share proxy against an $98B national anchor — a compensating-error hack, not a real definition match. Realistic expectation for this fix (usingA85770):The NY problem isn't really solvable on the target side — it needs BHP modeling upstream in policyengine-us (separate issue). The value of this issue's fix is defining the right quantity to calibrate, not reducing the NY-specific error below 100%.
National consistency (what PR #803 taught us)
Keeping the current national
$98BCMS APTC anchor alongside newA85770-based state targets recreates the EITC "total of state rows ≠ national row" contradiction that PR #803 removed. Three ways to resolve:A85770national row ($53.9B, uprated to target year) as the aggregate, andA85770state rows as the distribution. Same pattern as EITC post-Add state and AGI cross-tab EITC calibration targets (#802) #803. Recommended.Going with (1). The CMS national number is still useful as a sanity crosscheck, but not as a calibration target.
Tolerance
Tightening
test_aca_calibrationfrom 500% to 25% is not defensible. Per Codex diagnostics: even with correctA85770state shares, current model gives ~25% median / ~43% p90 state error, and NY remains a 100%+ outlier while BHP isn't modeled upstream. Propose 50% tolerance, which allows the p90 to pass while still flagging egregious drift.Revised CSV column naming
(renamed from original
Amountto make the SOI column explicit)Unrelated bug this surfaced
policyengine_us_data/db/etl_irs_soi.py:95mapsaca_ptcto IRS code85530. IRS Historical Table 2 documentation labels85530as Additional Medicare tax, not PTC. Likely a transcription error — the correct code is85770(total PTC) or85775(advance PTC) or11560(net PTC), depending on intent. Will file as a separate issue.Revised sequencing
etl_irs_soi.pycolumn-code bug (tiny, independent).A85770/N85770extraction to the SOI Historical Table 2 refresh (reuses Add state and AGI cross-tab EITC calibration targets (#802) #803's infrastructure).A85770uprated; drop the$98Bnational anchor in favor of SOI nationalA85770.Original proposal (superseded)
The rest of this issue kept for history, but the column-code, impact, and tolerance claims above supersede the originals.
Context
PR #803 (issue #802) rewrote EITC calibration on coherent IRS SOI TY2022 anchors after noticing that the loss function was targeting Treasury's EITC outlay parameter (~$67B) as if it were directly comparable to the
eitcmicrosim variable (which computes total claimed EITC, ~$59B per SOI). The same class of bug exists for ACA Premium Tax Credit state-level calibration, and it's causing thetest_aca_calibration/test_sparse_aca_calibrationintegration tests to fail on every PR that touches calibration — most dramatically in New York, where simulatedaca_ptcis ~$6bn vs a target of ~$0.86bn (591% error, above the 500% tolerance).This issue proposes replacing the per-state ACA spending target with an IRS SOI Net Premium Tax Credit target drawn from Historical Table 2, the same workbook PR #803 already extracts for EITC.
The definitional mismatch today
Current wiring in
policyengine_us_data/utils/loss.py(~L960–988) builds per-state targets as:Three problems stack:
Outlay vs claimed.
aca_spending_and_enrollment_2024.csvholds CMS's Advance Premium Tax Credit (APTC) — the monthly payment CMS makes to insurers — multiplied by 12 to annualize. The microsim variableaca_ptcis the Premium Tax Credit claimed on the tax return, after the reconciliation on Form 8962. These are similar in aggregate but diverge household by household (income changes mid-year, takeup changes, filing-status changes, reconciliation payback) and can diverge badly state by state.NY Essential Plan / MN MinnesotaCare. NY and MN operate federally-funded Basic Health Programs (BHP) that cover the 138–200% FPL population who would otherwise take APTC on the Marketplace. Approximately 1.5M New Yorkers enrolled in the Essential Plan in 2024, meaning NY's Marketplace APTC (the current target) is artificially small while NY's microsim
aca_ptcreflects full APTC-eligible simulation becausepolicyengine-usdoesn't model BHP as an APTC-disqualifier. The $0.86bn NY target vs $6bn simulation is mostly this — not a calibration bug the optimizer can fix.Mislabeled source. The loss-matrix label is
state/irs/aca_spending/{state}, but the underlying data is published by CMS, not IRS. The label is a red herring when debugging.Proposal
Replace per-state CMS APTC with per-state IRS SOI Net Premium Tax Credit (claimed on tax returns).
Target source
IRS SOI Historical Table 2, published annually as
{YY}in55cmcsv.csv— the same workbook PR #803 already pulls for state EITC. It contains two PTC columns:N11560— Number of returns with net premium tax creditA11560— Amount of net premium tax credit (in thousands, after reconciliation)Coverage: all 50 states + DC + the US aggregate row, annually from TY2014 forward. TY2022 is current (TY2023 published ~Q4 2025, TY2024 published ~Q4 2026). IRS SOI: https://www.irs.gov/statistics/soi-tax-stats-historic-table-2
New CSV
policyengine_us_data/storage/calibration_targets/aca_ptc_state.csv:Format matches
eitc_state.csv(added in PR #803). 51 rows × 2 metrics = ~102 new loss-matrix columns. Extract via a parameterized refresh script following the pattern inrefresh_eitc_state_and_agi_targets.py— most of that machinery can be factored into a shared_extract_soi_historical_table_2_column(year, column_code)helper.New helper in
loss.pyMirror
_add_state_eitc_targetsfrom PR #803:Call site: right after
_add_state_eitc_targetsin the EITC block (L758 area), withaca_ptc_spending_upratingderived from a CBO/JCT PTC trajectory parameter. Falls back to the EITC spending uprating if no PTC-specific trajectory is available — both are refundable credits that grow with wages and enrollment.Removals
Remove the existing per-state CMS APTC loop at loss.py L960–988 (
state/irs/aca_spending/{state}) and the state-level enrollment loop at L1005–1015 if the reviewer agrees that SOI's returns count gives equivalent (or better) signal. The national CMS APTC aggregate at L711–715 (nation/gov/aca_spending) stays — it's a legitimate budget-outlay anchor and is not per-state, so the NY Essential Plan / BHP issue doesn't cascade from it.Delete the legacy
aca_spending_and_enrollment_2024.csvper-state rows if they're only used for this purpose; keep the CSV if_get_aca_national_targetsstill needs the national-aggregate scaling.Why this is the right fix (vs. modeling BHP upstream)
Modeling NY Essential Plan + MN MinnesotaCare in
policyengine-usis the correct long-term fix (see separate issue for that), but it doesn't fix the per-state ACA calibration by itself because:BHP modeling is still worth doing, but it's a separate upstream change. This issue is the complete target-side fix.
Expected impact
test_aca_calibrationandtest_sparse_aca_calibrationwith their 500% tolerance should pass cleanly._add_tax_expenditure_targets, loss.py:1101) are unaffected — they use a separate simulate-repeal methodology that already works correctly.Test additions
tests/unit/calibration/test_aca_ptc_state_targets.py:test_aca_ptc_state_csv_present_with_expected_columnstest_aca_ptc_state_totals_match_irs_national_aggregate(assert within 1% of theA11560US row, same pattern as the EITC test)test_add_state_aca_ptc_targets_produces_aligned_columns_and_targetstest_placeholder_rows_are_skipped_without_breaking_alignmenttest_legacy_state_aca_spending_targets_removed(regression: assert nostate/irs/aca_spending/columns in built loss matrix)tests/integration/test_enhanced_cps.py: tightentest_aca_calibration's 500% tolerance to 25% and expect it to still pass.Related
policyengine-usso that upstreamaca_ptccalculations zero out for BHP enrollees. Orthogonal to this issue but reinforces the NY fix.Suggested sequencing
aca_ptcvs both CMS APTC and SOI A11560 for TY2022 off the currentenhanced_cps_2024.h5, to quantify how much of the per-state error is pure definition vs pure calibration.Filed after noticing the ACA test failures on PR #803 and the pre-existing 880% NY error on PR #794 — same structural problem across both.