Repository navigation
Test that Schedule A medical expenses exclude pre-tax payroll premiums - #10067
Conversation
A premium paid by salary reduction under a section 125 cafeteria plan is paid by the employer and excluded from wages under IRC 106(a) (Rev. Rul. 2002-3), so it is not an expense paid by the taxpayer under IRC 213(a). pre_tax_health_insurance_premiums holds those premiums and is disjoint from the other premium inputs, so the Schedule A formulas already leave it out. Nothing tested that. Add eight YAML cases (worker who itemizes, both payment methods, direct and decomposed premium inputs, a working Medicare enrollee, joint filers, an itemizer end to end and a non-itemizer) and a Hypothesis property test of five invariants. Both fail if the pre-tax input is added to, or subtracted from, the Schedule A premium base. No formula changes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review r1: the property test gave dependents their own premiums and medical expenses and asserted they reach the filers' Schedule A. Whether a dependent's own payments belong there is a separate question, so leave dependents out and state the invariants for single and joint wage earners with no self-employment, Medicare premiums or reimbursed expenses. Say what the file adds beyond the YAML cases (relations between paired runs of the same generated households, in one vectorized batch). Each example takes about 0.1 seconds, so run 20 instead of 4. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Hand-off to the US + core hubHead: Independent review (GPT-6.1 Sol):
Passed:
Pending:
Depends on: nothing for content. It shares no file with #10046, #10055 or #10023 and can land in any order relative to them. Holds or decisions: none queued. The description names the alternative contract (premium inputs as totals with a pre-tax subset) as a choice Max may overrule; if he does, these tests change with the formulas. Follow-ups owed: none by this session. If #10055's head moves, re-run the two files against it: none of the cases has a self-employed health insurance deduction, so they should be unaffected. The Microcosm side (ship only the employer premium in microcosm#1189; pre-tax column later, netted, gated on an engine with #10046) is with that PR's session. |
|
Reopened by the US + core hub to start a fresh CI run at the same head. The previous run failed on |
|
US + core hub merge audit, us#10067 at
|
Tests only. No formula or parameter changes, so no model output moves.
Why
PolicyEngine/microcosm#1189 set out to populate
pre_tax_health_insurance_premiums(about $181B before calibration). Its body noted that the engine "counts the whole reported premium in the itemized medical-expense base, so a premium marked pre-tax here is also there." This PR was opened to remove that double count from the federal Schedule A path.The Schedule A formulas turn out to be right already, and a subtraction would break them:
0590757714),itemized_medical_expensesaddsmedical_expense_health_insurance_premiumsandother_medical_expenses. Neither readspre_tax_health_insurance_premiums. That input is used only to reduce wages (gov.irs.gross_income.pre_tax_contributions,fica_pre_tax_contributions, andca_taxable_wages).pre_tax_health_insurance_premiumsor in the other premium inputs, never both. Document disjoint health premium inputs and correct consumer treatment #10046 documents that contract, and Fix Idaho health insurance premium subtraction #10023 and Exclude SE health insurance deductions from Schedule A medical expenses #10055 are built on it.The double count came from the data side. microcosm#1189 as opened wrote the reported premium (
PHIP_VAL) topre_tax_health_insurance_premiumsand left the same dollars inhealth_insurance_premiums_without_medicare_part_b. I raised it there. Its author agrees, plans to ship only the employer premium in that PR, and will add the pre-tax column, netted out of the other premium inputs, in a follow-up that waits for an engine with #10046. #10064, which asked for the engine subtraction, is closed as not planned.Nothing in this repo tested that Schedule A leaves the pre-tax input out. This PR adds that guard, so that neither a later "fix" by subtraction nor adding the input to the medical base can land unnoticed.
The disjoint contract is the US hub's ruling of 2026-10-10 under the leaf-input rule. The alternative is to define the existing premium inputs as totals that contain a separately identified pre-tax subset; these tests would then need to change with the formulas.
Law
A salary-reduction premium is paid by the employer and never enters the employee's wages, so it is not an expense the taxpayer paid under section 213(a).
Invariants
These hold for the filers the tests cover: single and joint wage earners under 65 who pay their own costs, with no self-employment (so no self-employed health insurance deduction, which #10055 nets out of Schedule A), no Medicare premiums and no reimbursed expenses. The premium inputs are disjoint.
Tests
itemized_medical_expenses.yaml: eight new cases.tests/core/test_schedule_a_pre_tax_premium_invariants.py: a Hypothesis property test of the five invariants. It draws 20 batches of 5 to 30 single and joint filers for 2024 and 2026, runs each batch three ways (as drawn, pre-tax premiums removed, pre-tax premiums paid after tax), and adds five explicit edge cases, including pre-tax premiums above wages. Dependents are left out: whether a dependent's own payments belong on the filer's Schedule A is a separate question.No counterexamples.
Why a Python test, and what it costs
The YAML cases pin hand-computed households. The Python file adds what YAML cannot express: relations between paired runs of the same generated households (invariant 5), and the same checks over a mixed batch in one vectorized simulation. It compares the model with an independent numpy calculation; it does not copy a formula.
It rebuilds no policy. Each simulation shares the reference parameter tree and variable objects and gets only its own variable registry and entities (
share_spm_policyinpolicyengine_us/spm.py). Each batch computes six variables.Local measurements (macOS, Python 3.13, policyengine-core 3.32.15, machine load average about 60, so these are upper-range):
from policyengine_us import Simulation)In that run pytest reported 6.2 seconds and the test call took 2.7 seconds (20 examples at 0.07 to 0.5 seconds each). Under heavier contention the same file took up to 176 seconds by pytest's clock, so local times are not a CI estimate.
CI group: Rest/core (
make test-other-python-rest, thecoregroup). No job, matrix entry or process is added. Linux CI figures, from each run'srest-test-timingsartifact:0590757714667b6cd35c, run 38074706313 (4 examples)The two runs are on different runners, so the group totals differ by more than this test costs; the per-test time is the comparable figure. At the current head (20 examples) the Quick Feedback job runs the file alone on Linux: the test call takes 9.30 seconds in a 19.39-second pytest session (job 114288089193). The Rest/core report for
f966680049will be in run 38077671135's artifact.Validation
At
f966680049:medical_expense_health_insurance_premiumsfails 7 of the 9 YAML cases and the property test. Subtracting it fails 3 of the 9 and the property test.bba165d129), Document disjoint health premium inputs and correct consumer treatment #10046 (70c8e5a8df) and Fix Idaho health insurance premium subtraction #10023 (d3f5681516). This PR touches no file those PRs touch. None of the cases has a self-employed health insurance deduction, so Exclude SE health insurance deductions from Schedule A medical expenses #10055's exclusion does not move them.ruff format --check .andruff check .pass.CI: this PR's first run failed "Python tests / Full Suite - Microsimulation" in a test it does not touch (
test_spm_system.py::test_subsampling_never_changes_which_policy_a_holder_belongs_to,KeyError: 'reform_only_income'). The run installed policyengine-core 3.33.1, released at 17:41Z on 2026-10-10. An unrelated branch's run with the same core version fails the same way (run 38074181952). The US hub is fixing that test in its own PR.Review
Independent review r1 (GPT-6.1 Sol) at
667b6cd35c: REQUEST_CHANGES. It agreed that no formula change is right under the disjoint contract, recomputed all eight YAML cases by hand and reproduced the mutation check. Its findings, fixed inf966680049and in this description:encoded-correctneeds an Axiom companion case for the same situation, and there is none. The line below is nown/a.Delta review r2 at
f966680049: APPROVE, all five r1 findings resolved or addressed, no P1 or P2. Its three optional wording points are applied in this description and in the Microcosm comment.axiom: n/a: tests only; no policy formula or parameter changes
Related Axiom coverage:
us:statutes/26/213#medical_care_deduction(rulespec-us main16a6677190) takesmedical_care_expenses_paid_not_compensated_by_insurance_or_otherwiseas a single input and has no premium components or payment-method facts, so it has no case for a pre-tax premium. rulespec-us has no section 106 or 125 module; the payroll health exclusion for earned income is queued in TheAxiomFoundation/rulespec-us#1554.🤖 Generated with Claude Code