Skip to content

Fix Idaho health insurance premium subtraction - #10023

Merged
MaxGhenis merged 16 commits into
mainfrom
hub-pb-id-premiums-1009
Oct 11, 2026
Merged

MaxGhenis merged 16 commits into
mainfrom
hub-pb-id-premiums-1009

Conversation

@MaxGhenis

@MaxGhenis MaxGhenis commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

Before this PR, Idaho omitted the subtraction for health insurance premiums paid by the taxpayer. This PR adds it using existing payer-attributed person-level premium facts. Payments by the tax unit's head and spouse qualify, including their payments covering dependents; their pretax premiums and federal self-employed health deductions are excluded.

Round 5 removes the process-wide replacement of Core's Holder.delete_arrays. policyengine_us/tools/period_branch.py and its shared branch tests are restored exactly to current main at e92f4c6b95458105134b4d88349f10a853dc8b50. No Core class or method is changed. The existing read-only, period/branch-aware has_input_for_period check now lives in tools/general.py and is called only by the two Idaho formulas. The supplied-medical-expense formula and both round-4 YAML regressions are unchanged.

Round 6 applies the hub's settled disjoint premium-input contract: the premium inputs contain payments made outside pretax payroll deductions, and pre_tax_health_insurance_premiums is a separate input that never enters the Idaho subtraction or claimant medical worksheet. The formulas retain the self-employed above-the-line deduction and dependent-payment exclusions. Four existing YAML cases (five pretax input occurrences) and the property test now use that contract; a fifth, vectorized household regression verifies identical Idaho outputs with $0 or $2,000 of separately supplied pretax premiums. A strict xfail records the known federal-first deletion limitation without changing Core or period_branch.py. PolicyBench cells 007 and 053 remain unchanged.

Round 7 verifies that Idaho Code 63-3022P and the 2025 Form 39R line 18 instructions and worksheet allow voluntarily enrolled Medicare Part B and Part D premiums paid by the taxpayer or spouse. Income-related adjustments are part of those premiums, so their eligibility follows the same rule; see 20 CFR 418.1125 and SSA Handbook 2505. The existing model derives out-of-pocket Part B premiums for enrolled people when no premium inputs are supplied, and main already includes those amounts in the federal Schedule A medical base. The senior-couple integration case now explicitly asserts the $4,440 subtraction: 2 × $185 × 12, below the $200,000 × 7.5% = $15,000 medical floor, leaving $200,000 − $60,000 − $6,000 − $4,440 = $129,560 taxable income. A subtraction-file regression covers the same enrolled couple without premium inputs. Round 7 also establishes by empirical bisection that Core 3.32.27 is the first fixed version for the deleted-input provenance limitation, matching the changelog and Core #561. The strict xfail now applies only below 3.32.27. Normal full-file validation gives 2 passed and 1 xfailed on Core 3.32.26, and 3 passed on Core 3.32.29. The dependency floor and lock remain unchanged. All 65 Idaho YAML files are being run individually in fresh foreground CLI processes, one at a time. The first three files have passed: integration (7 cases), premium subtraction (23 cases), and Idaho income tax before refundable credits (4 cases), for 34 passed, 0 failed. The PolicyBench file confirms unchanged cells 007 ($655.54) and 053 ($2,180.28). The local run of the other 62 Idaho files was cut off by the build job's time limit; CI's state shard at this head covers the whole folder and passes.

The retained supplied-expense regression has 2025 AGI of $30,000, $3,000 premiums, supplied itemized_medical_expenses of $6,000 and supplied Idaho itemized deductions of $20,000. The $3,750 federal medical deduction exhausts premium overlap: subtraction $0, taxable income $10,000. The other retained regression verifies that an authoritative supplied Schedule A subtotal already excludes Schedule 1 premiums and can include bills absent from the person inputs. Under the disjoint contract, $6,000 of premium inputs less the $1,000 Schedule 1 deduction plus $500 of those bills gives a supplied subtotal of $5,500. The $2,250 medical floor leaves a $3,250 federal and claimant deduction; $5,000 qualified premiums less that overlap preserves the $1,750 subtraction. The separate $2,000 pretax input does not enter either calculation.

Idaho Code 63-3022P limits the subtraction to premiums paid by the taxpayer that have not otherwise been deducted or accounted for. 2025 Form 39R instructions and worksheet, PDF pages 48–49 allocate itemized medical deductions to health premiums first and preserve the subtraction when Idaho's standard deduction is elected. The 2025 Schedule A instructions limit medical expenses to bills the claimant paid and exclude pretax premiums and premiums already deducted on Schedule 1.

The declared id_health_insurance_premiums_medical_deduction derives the claimant's worksheet deduction from qualified filer premiums plus filer-paid other_medical_expenses, less the medical floor. It excludes dependent-paid expenses before applying the floor and caps derived results by the nonnegative federal medical deduction. Both id_itemizes and id_health_insurance_premiums_subtraction consume this value. A federal medical_expense_deduction or itemized_medical_expenses subtotal explicitly supplied for the requested tax year represents the claimant's actual Schedule A amount and is honored even when underlying person inputs are omitted. A read-only input check distinguishes visible source inputs from calculated values using Core provenance. On policyengine-core < 3.32.27, deleted inputs recalculated before that check have the known limitation below. Inputs from another year or sibling branch cannot override the claimant calculation. An explicit Idaho medical-deduction override remains available for a different state-specific claim. Federal medical and itemized-deduction formulas are unchanged.

When both the federal itemized subtotal and final total are derived, id_itemized_deductions also replaces the shared federal medical component with this claimant deduction. Explicitly supplied federal itemized totals and subtotals remain authoritative. Supplied final totals receive the existing Idaho SALT adjustment; supplied subtotals first flow through the existing federal limitation and then the Idaho SALT adjustment. Neither is reconstructed medically from potentially incomplete component inputs. With the same 2025 payer facts and $20,000 of allowable interest deductions, the shared federal itemized total remains $21,250; Idaho's total is $20,750. The Idaho deduction plus premium subtraction is $23,000, leaving $7,000 taxable income. This prevents independently paid dependent costs from surviving in the state itemized total after premium overlap is corrected.

For 2026, the replacement also adjusts the overall itemized reduction under P.L. 119-21 §70111 (IRC 68), using the existing federal limitation parameters and income calculation. Removing medical costs before applying the 2/37 reduction can change that reduction. The new high-income regression corrects a $70,500 federal subtotal to $70,000, yielding approximately $66,216.21622 of Idaho itemized deductions. The older Pease reduction is unaffected because medical deductions were excluded from its ceiling.

Choose the larger combined Idaho deduction plus premium subtraction, retaining the standard deduction on ties and requiring itemization for separate filers whose spouses itemize. Idaho Code 63-3022(j) and the Idaho Desk Audit Manual support this election. The provision predates 2021, so both existing subtraction-list periods include it.

PolicyBench cells

Cell 2.35.3 state tax Audited premium fix
007 $765.78 $655.54
053 $2,445.28 $2,180.28

The regressions preserve every supplied household fact and assert these aggregate state tax totals, including the $10 permanent building fund tax. The Idaho income-tax components are $645.54 and $2,170.28. The audited independent verification gives the component deltas as $2,080 × 5.3% = $110.24 and $5,000 × 5.3% = $265.

Known limitation

This limitation affects policyengine-core versions below 3.32.27. Core 3.32.27 fixes input-record deletion and memory/disk provenance in Core #561, Keep the record of set_input values in step with storage. The PR continues to allow policyengine-core>=3.32.8, with uv.lock pinned to 3.32.8; a separate floor bump to >=3.32.27 would remove this limitation for all allowed installations.

On older Core versions, _user_input_keys can remain after an input array is deleted. The exact regression has 2025 AGI $30,000, parent-paid premiums $3,000, dependent-paid premiums $500, standard deduction $15,750 and supplied Idaho itemized deductions $20,000: explicitly call set_input and then delete_arrays on medical_expense_deduction. If the federal deduction is calculated before the Idaho helper, the recomputed $1,250 is misclassified as supplied. The subtraction is $1,750 instead of $2,250, and taxable income is $8,250 instead of $7,750. Calculating the Idaho helper first gives the correct result. The committed regression expects the correct $2,250 subtraction using only public simulation APIs. Its pytest.mark.xfail(condition=Version(version("policyengine-core")) < Version("3.32.27"), strict=True, reason=...) applies only on affected versions, using importlib.metadata.version("policyengine-core") and packaging.version.Version; on fixed versions the test runs as an ordinary passing regression.

The trigger remains explicit set_input followed by delete_arrays on a supplied medical_expense_deduction, itemized_medical_expenses, or itemized input used by Idaho (itemized_taxable_income_deductions or total_itemized_taxable_income_deductions). Review r5's caller survey found no PolicyEngine-US path that performs that sequence: API household inputs are set once; Simulation.__init__ deletes unrelated inputs; bulk branch loops skip input_variables; New York branches delete only EITC or CTC variables; branch exit deletes calculated variables; and drop_inherited_values keeps inputs. The PR adds no deletion callers.

For affected older versions, deletion and recalculation can finish before the first input-helper call, leaving indistinguishable supplied and recomputed values. The helper remains read-only; this PR changes no Core or period_branch behavior. The Core release fix now supplies the missing provenance cleanup. Historical round-4/6 failure logs remain untracked evidence, including the $1,750 result on Core 3.32.15. No public issue was opened.

Validation

Round 7, at d3f56815167726b809be586b9510882852751565. Both changes are committed and pushed. make format (7,068 files unchanged), Ruff lint, source review, and git diff --check passed. Core validation is complete. CI at this head passes all 28 checks, including Full Suite - Baseline (states-shard-3), the job that runs the Idaho folder and that failed on its integration file at the previous head.

The empirical Core version bisect ran only test_deleted_medical_input_recalculated_federal_first with pytest --runxfail, using the PR worktree and specifically pinned Core versions in a scratch venv. Each invocation was a fresh foreground process, one at a time. The raw completed version matrix is:

policyengine-core Selected regression with --runxfail Exit code
3.32.15 1 failed 1
3.32.22 1 failed 1
3.32.25 1 failed 1
3.32.26 1 failed 1
3.32.27 1 passed 0
3.32.29 1 passed 0

The four diagnostic failures reproduce [1750.0] versus the expected [2250]; they demonstrate the older-Core limitation and are not ordinary regression-suite failures. The adjacent 3.32.26 failure and 3.32.27 pass establish 3.32.27 as the first fixed version, matching Core #561 and the 3.32.27 changelog. Read-only inspection of the published 3.32.8 wheel confirms the same stale registry behavior at the allowed floor. The dependency floor and uv.lock remain unchanged.

The full policyengine_us/tests/core/test_id_health_insurance_premiums.py file then ran normally, without --runxfail, on the last affected release and the CI release. Both processes exited 0, verifying a strict xfail on 3.32.26 and an ordinary passing regression on 3.32.29:

policyengine-core Passed Strict xfailed Pytest reported time Full command wall time
3.32.26 2 1 751.31 s 1157.88 s
3.32.29 3 0 518.56 s 981.01 s

Long startup and collection delays coincided with high local macOS/Python 3.13.9 host load. The full-file commands used approximately 44.35 s and 38.03 s of combined child user/system CPU time respectively; these wall-time measurements do not establish Linux CI resource impact. An earlier 3.32.25 diagnostic attempt exited with signal 11 before collection; its log is retained, and a fresh retry without the optional traceback timer produced the normal older-Core assertion failure shown above. That attempt and the earlier interrupted startup diagnostic are excluded from the completed version matrix.

Idaho YAML files run locally: the three files below, individually, in fresh foreground CLI processes, one at a time: 34 passed, 0 failed, 0 xfailed, 0 skipped, 0 errors, with exit code 0 for every command. The local run of the other 62 Idaho files was cut off by the build job's six-hour limit under high host load. CI's state shard at this head covers the whole folder and passes.

File Passed Exit code Full command wall time Retained local log
policyengine_us/tests/policy/baseline/gov/states/id/tax/income/integration.yaml 7 0 1817.95 s hub-evidence/idaho-yaml/01-integration.log
policyengine_us/tests/policy/baseline/gov/states/id/tax/income/subtractions/id_health_insurance_premiums_subtraction.yaml 23 0 725.74 s hub-evidence/idaho-yaml/02-id_health_insurance_premiums_subtraction.log
policyengine_us/tests/policy/baseline/gov/states/id/tax/income/id_income_tax_before_refundable_credits.yaml 4 0 351.74 s hub-evidence/idaho-yaml/03-id_income_tax_before_refundable_credits.log

The four passing id_income_tax_before_refundable_credits.yaml cases include the unchanged PolicyBench aggregate state-tax assertions: cell 007 $655.54 and cell 053 $2,180.28, including the $10 permanent building fund tax. The corresponding Idaho income-tax components remain $645.54 and $2,170.28. Each file reports one PytestAssertRewriteWarning for the already imported anyio module; no test failed. The historical round-6 counts below remain historical. No microsimulation, folder invocation, full suite, or partner test is included in this round.

Historical round-6 validation

Final-source round-6 validation at 6657e9331136cc7602684f02ff2f43547270f577: 42 passed, 1 strict xfailed, 0 skipped. Every PR-changed test file ran individually, one at a time in the foreground, with Python 3.13.9 and policyengine-core 3.32.15. No folder run, full suite or microsimulation was run.

File Passed Xfailed Pytest reported test time
policyengine_us/tests/core/test_id_health_insurance_premiums.py 2 1 7.71 s
policyengine_us/tests/policy/baseline/gov/states/id/tax/income/subtractions/id_health_insurance_premiums_subtraction.yaml 22 0 17.69 s
policyengine_us/tests/policy/baseline/gov/states/id/tax/income/id_deductions.yaml 7 0 11.25 s
policyengine_us/tests/policy/baseline/gov/states/id/tax/income/id_itemized_deductions.yaml 7 0 9.92 s
policyengine_us/tests/policy/baseline/gov/states/id/tax/income/id_income_tax_before_refundable_credits.yaml 4 0 7.22 s

These pytest times exclude interpreter startup before pytest begins and are not full command wall times or Linux CI group measurements.

The earlier untracked round-4 deletion regression reproduced the known limitation: 4 failed, 4 passed, 2 deselected. All four federal-first cases failed with a recomputed $1,250 claimant deduction instead of $750; all four helper-first cases passed. This covers US and Core simulations, and simulation and holder deletion. Round 6 tracks the deterministic federal-first sequence with the committed strict xfail. An isolated diagnostic run with --runxfail intentionally failed at [1750.0] != [2250], confirming the exact defect independently of test order; its log remains untracked evidence and is excluded from the 42 passing cases. The earlier reproduction is also excluded from the current validation counts.

Round-6 Ruff formatting and lint on the changed files and git diff --check passed. The main restoration from round 5 remains untouched, and no Core or period_branch method is changed. No partner tests were edited or run.

The property test independently enumerates the standard and itemized routes across 20 generated vector batches plus two explicit batches. It checks head/spouse/dependent payment scope, invariance to separately supplied pretax premiums, ALD exclusions, nonnegative and bounded subtraction, the largest combined deduction and subtraction, mandatory itemization, standard on ties, and independent tax units. The year/branch regression checks supplied claims for the requested year, sibling isolation, inherited branch inputs and deletion before federal recalculation. YAML covers the derived claimant medical floor, supplied aggregates, state deduction election and the 2026 limitation.

The Python addition tracks Core deletion provenance and calculation order, which YAML cannot exercise. The property file remains in the existing Rest/core CI group and the YAML files remain in the baseline states shards.

Local macOS/Python 3.13.9 full-command wall time, including model setup, was 32.90 s for the original two-test file and 36.51 s for the three-test round-6 file (including the strict xfail), a 3.61 s increase in these runs. Pytest reported 28.27 s and 7.71 s respectively. These single local runs do not establish a reduction in resource cost. Peak local RSS is unavailable because the sandbox rejected /usr/bin/time -l's sysctl kern.clockrate call after the baseline tests passed; /usr/bin/time -p completed for round 6.

The existing successful main CI run 37931391733, Rest job, head 0d934958068c7036074007a3684add97723be245 on Linux/Python 3.14.8, records the Rest/core subgroup at 11:16.36 and peak RSS 6,757,164 KiB in artifact 11620880923, rest-python-core-resources.txt. Those are baseline group measurements; updated Linux CI impact remains pending.

Dependencies, CI configuration, federal formulas and partner contract files are unchanged. Refreshed Linux CI group measurements and microsimulation impact remain pending with the hub. Changelog: changelog.d/hub-pb-id-premiums-1009.fixed.md.

Methodology

The hub applies Max's 2026-10-07 rule, "in general we should make inputs as leaf-nodey as possible." This settles the contract for this PR: pre_tax_health_insurance_premiums and the premium inputs (health_insurance_premiums or the applicable decomposed premium inputs) are disjoint leaves. The premium inputs contain payments made outside pretax payroll deductions. Total premiums paid is the sum of those non-pretax payments and the separate pretax input; no pretax component is carved out of the premium inputs. Max may overrule the hub's application of his input rule.

Federal and Idaho now read the premium inputs the same way. The existing federal medical deduction counts the premium inputs without subtracting the separate pretax input, and Idaho likewise starts its qualified-premium and claimant-medical calculations from those inputs as supplied. Pretax payroll premiums are excluded because they never enter those inputs. Idaho still removes premiums already deducted through the self-employed above-the-line deduction where required and excludes independent dependent payments from the claimant calculation.

The contract change has no microsimulation effect: the pinned default dataset, populace-us-2024-spm-20260909 at Hugging Face revision 9a814a3b, has no pre_tax_health_insurance_premiums column, and that variable has no formula. It is therefore zero for every record in those runs. The change affects callers who explicitly supply pretax premiums; shared variable documentation, federal formulas and SPM MOOP are outside this round's scope.

The existing joint standard-versus-itemized optimization also remains: choose the larger combined deduction and premium subtraction, use standard on ties, and honor mandatory itemization. The statutory election, Form 39R standard-deduction exception, and Desk Audit Manual support that choice.

Limitations

The state correction assumes person-level medical amounts identify the payer. Payments by the head or spouse for a dependent belong with the payer; a dependent's independent payments do not enter the claimant's worksheet. When neither the medical deduction nor the expense subtotal is supplied, the claimant's underlying person-level medical costs must be complete. A supplied federal medical deduction or expense subtotal is authoritative and must represent the claimant's actual Schedule A amount for the requested tax year.

The shared federal medical and itemized aggregates retain their existing payer and exclusion limitations. The Idaho medical replacement corrects their effect on the derived state itemized total without changing federal calculations. Supplied federal itemized aggregates must represent the actual claimant return and the requested tax year. Those authoritative aggregate totals are preserved rather than reconstructed from potentially incomplete person-level inputs; the existing Idaho SALT adjustment still applies. The 2026 adjustment preserves the existing federal limitation's income calculation and parameters.

The inputs do not identify premiums paid from an already-deducted Idaho medical savings account. That remains a separate limitation. No partner contract files were edited or run locally. The prior read-only inventory of 144 partner YAML files found no Idaho scenarios or Idaho tax assertions.

axiom: Idaho Code 63-3022P TheAxiomFoundation/rulespec-us#1625 queued

The previously filed pe-parity issue supplies corpus and module citations, verbatim law, required outputs, and companion tests independently calculated from the 2025 Form 39R instructions and worksheet. Encoding remains blocked until the Axiom hub pins a corpus release containing §63-3022P, which exists in the newer corpus but is absent from rulespec-us's current pin. Under Max's d1142 ruling, the Axiom engineering hub runs the signed encoder. The queue entry remains pending.

Hub microsimulation impact (current head)

A real microsimulation on the default dataset, main at the merge base c05b85ec1c against this PR's head d3f5681516, for 2025 and 2026. The runs went one at a time under the shared heavy-job lock. Sums are weighted.

Output 2025 change 2026 change 2025 records changed (weighted) 2026 records changed (weighted)
id_income_tax -100.90M -114.04M 481 (414,251) 500 (426,658)
id_taxable_income -2.12bn -2.34bn 522 (441,543) 526 (444,697)
id_deductions -50.12M -50.86M 27 (19,684) 27 (19,824)
state_income_tax -100.90M -114.04M 481 (414,251) 500 (426,658)
income_tax +0 +0 0 (0) 0 (0)
household_net_income +100.90M +114.04M 435 (387,535) 451 (398,817)
household_benefits +0 +0 0 (0) 0 (0)

Comparison files are in fixes/10023-impact/ on the hub host.

Hub microsimulation impact

Prior round, head f44901ee32; the hub will refresh impact after this round.

A real microsimulation on the default dataset, main at the merge base c05b85ec1c against this PR's head f44901ee32, for 2025 and 2026. The runs went one at a time under the shared heavy-job lock. Sums are weighted.

Output 2025 change 2026 change 2025 records changed (weighted) 2026 records changed (weighted)
id_income_tax -101.36M -114.54M 476 (414,250) 495 (426,656)
id_taxable_income -2.13bn -2.35bn 517 (441,542) 521 (444,695)
id_deductions -46.17M -46.75M 20 (18,150) 20 (18,280)
state_income_tax -101.36M -114.54M 476 (414,250) 495 (426,656)
income_tax +0 +0 0 (0) 0 (0)
household_net_income +101.36M +114.54M 431 (387,535) 447 (398,817)
household_benefits +0 +0 0 (0) 0 (0)

Comparison files are in fixes/10023-impact/ on the hub host.

No microsimulation was run in this round; refreshed impact is pending with the hub.

@MaxGhenis
MaxGhenis marked this pull request as ready for review October 10, 2026 09:35
@codecov

codecov Bot commented Oct 10, 2026 •

Copy link
Copy Markdown

Codecov Report

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

Additional details and impacted files
@@            Coverage Diff            @@
##              main    #10023   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files            4         6    +2     
  Lines           76       111   +35     
  Branches         2         3    +1     
=========================================
+ Hits            76       111   +35     
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.

@MaxGhenis

Copy link
Copy Markdown
Contributor Author

US + core hub merge audit, us#10023 at d3f56815167726b809be586b9510882852751565 (squash): Idaho subtracts health insurance premiums paid by the taxpayer and spouse that were not deducted or excluded elsewhere (Idaho Code 63-3022P; 2025 Form 39R line 18 and worksheet).

  • Context: a PolicyBench engine fix (Max, 2026-10-09).
  • Review: seven GPT-6.1 Sol rounds. Delta review r7 approves this exact head, with no P1 or P2.
  • Medicare: voluntary Medicare Part B and Part D premiums the taxpayer or spouse pays qualify (Form 39R line 18). The model counts its modeled net Part B premium as paid, as the federal Schedule A base already does.
  • Premium inputs: pre_tax_health_insurance_premiums and the premium inputs are disjoint (a hub decision under Max's 2026-10-07 leaf-input rule; Max may overrule). The pinned dataset has no pretax premium column, so this moves no aggregate.
  • Known limitation: on policyengine-core below 3.32.27, an explicit set_input then delete_arrays of a supplied medical input can make the result depend on calculation order. Core 3.32.27 (core#561) fixes it. A version-gated test pins both behaviours.
  • PolicyBench: cells 007 ($655.54) and 053 ($2,180.28) land on the audited targets.
  • Gates checked live at merge:
    • gh pr checks exits 0;
    • the latest Pull request run at the head concluded success;
    • MERGEABLE, not a draft, no CHANGES_REQUESTED;
    • the semantic-overlap guard;
    • head pinned.
  • Impact: a hub microsim at this head against its merge base, for 2025 and 2026; see the body's "Hub microsimulation impact" section.
  • Axiom: see the PR body's axiom: line (rulespec-us#1625 queued).
  • --admin: used only because a required approving review is the sole block.

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.

1 participant