Skip to content

Count Ohio premiums on medical worksheet lines 1 and 3 (R.C. 5747.01(A)(10)) - #10020

Merged
MaxGhenis merged 6 commits into
mainfrom
fix-oh-medical-premium-lines
Oct 9, 2026
Merged

MaxGhenis merged 6 commits into
mainfrom
fix-oh-medical-premium-lines

Conversation

@MaxGhenis

@MaxGhenis MaxGhenis commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

Fixes the defect behind PolicyBench scenario_025: Ohio's unreimbursed medical care worksheet never counted health insurance premiums that households report through the populated premium inputs.

Follows #9925 (one deduction per return, AGI floored at zero), merged as f9f3887431. This branch was started on #9925's head before it merged and has since merged main, so the diff holds only this fix.

The defect

R.C. 5747.01(A)(10)(a) gives a full deduction for premiums, except to "any taxpayer who is eligible to participate in any subsidized health plan maintained by any employer of the taxpayer or of the taxpayer's spouse, or to any taxpayer who is entitled to ... benefits under part A" of Medicare. Division (b) still counts those premiums as medical care above 7.5% of federal AGI. The 2025 worksheet (p. 41) splits them:

  1. Enter amounts paid for unreimbursed dental, vision, and health insurance premiums during any portion of the year in which you were not eligible for Medicare or an employer-paid health care plan through your or your spouse's employer
  2. Enter amounts paid for unreimbursed dental, vision, and health insurance premiums during any portion of the year in which you were eligible for Medicare or an employer-paid health care plan through your or your spouse's employer

Ohio's FAQ (Q8, Q9) puts after-tax premiums for an employer plan, or for private coverage bought instead of a spouse's employer plan, on line 3. Q2 and the Line 44 instructions (pp. 25-26) exclude premiums paid with pre-tax dollars.

The model had two errors:

  1. Line 3 used AND, not OR. It counted premiums only for a person who was Medicare-eligible and whose employer paid none of them. A person with an employer plan who was not on Medicare had no line 3 premiums.
  2. Line 1 read an input nothing fills. It read health_insurance_premiums, a direct input whose formula (adds of the decomposed premiums) Decompose SPM MOOP premiums #8178 removed. Microdata and most household inputs report premiums through health_insurance_premiums_without_medicare_part_b, so line 1 was zero for them.

For scenario_025 (head 61 with $6,500 of premiums and $800 of other medical expenses, both spouses with employer coverage), the $6,500 reached neither line, and the deduction was zero.

The fix

  • oh_medical_care_insurance_premiums (new, Person): the IRC 213 premium base the federal medical deduction already uses (medical_expense_health_insurance_premiums: the direct input if given, otherwise non-Medicare premiums plus the modeled Part B premium for enrollees), less the person's federal self-employed health insurance deduction (worksheet line 1 note). Pre-tax payroll premiums are a separate input excluded from wages, so they are not in this base.
  • oh_employer_subsidized_health_plan_eligible (new, TaxUnit): true when the head or spouse has employer coverage (has_esi), an offer of it (offered_aca_disqualifying_esi), or an employer that pays some or all of their premiums. A dependent's own employer does not count; the statute names the taxpayer's or spouse's employer.
  • Line 1 (oh_uninsured_unreimbursed_medical_care_expenses) takes a person's premiums when they are eligible for neither Medicare nor such a plan; line 3 (in oh_insured_unreimbursed_medical_care_expense_amount) takes them otherwise.
  • Replaces the dead tax.ohio.gov/static 2022 booklet links with the live 2025 booklet and adds the FAQ.

Invariants

Stated for every input and checked by the Hypothesis test in tests/core/test_oh_unreimbursed_medical_deduction_invariants.py, which now draws Medicare eligibility, employer coverage, offers and the employer contribution category:

  1. Partition: each person's premiums are on exactly one of lines 1 and 3, so line 1 plus the premiums on line 3 equals the premiums paid. Line 1 is zero for every Medicare-eligible person and for everyone on a return where the head or spouse has an employer plan. This is new in this PR.
  2. Conservation: the members' line 8 shares sum to the return's line 8 (from Count Ohio's unreimbursed medical care deduction once per return #9925).
  3. Differential: line 8 and each share equal an independent numpy worksheet (from Count Ohio's unreimbursed medical care deduction once per return #9925).
  4. Bounds and exact pass-through: 0 ≤ line 8 ≤ line 5, and removing all medical amounts raises Ohio AGI by exactly the deduction (from Count Ohio's unreimbursed medical care deduction once per return #9925).

Tests

YAML tests first (97a3b7ed06), then the fix (38b67fafdc):

  • PolicyBench scenario_025 (oh_unreimbursed_medical_care_expense_deduction.yaml), with the scenario's exact inputs: line 3 = 6,500, line 5 = 7,300, line 8 = 7,300 − 0.075 × 94,925.29 = 180.60, Ohio AGI 94,744.69, taxable income 90,944.69 after 2 × $1,900 exemptions, and tax before refundable credits = 332 + 2.75% × (90,944.69 − 26,050) − 200 retirement credit = 1,916.60 (was 1,921.57). The audit's 180.63 rounds federal AGI to 94,925; with the exact 94,925.29 the line is 180.60.
  • Line 1 and line 3 routing: a single filer with no plan (line 1), a spouse's employer plan moving the head's private premiums to line 3 (FAQ Q9), a dependent following the filer's plan, a dependent's own employer coverage not barring the filer, a declined offer, a Medicare-eligible spouse alongside a non-eligible one, and the self-employed reduction.
  • Unit tests for both new variables, including that pre-tax payroll premiums and the SPM component other_health_insurance_premiums are not counted again.
  • Three existing expectations encoded the AND gate and change: Medicare-eligible with category SOME is now on line 3 (600 → 1,100 and 0 → 500 in oh_insured_unreimbursed_medical_care_expense_amount.yaml; 0 → 350 and 950 → 1,150 in integration.yaml).

Local runs (macOS, Python 3.12; local results, not Linux CI). The first is on the merged head; the others ran on the pre-merge head 9bb7314d57, whose Ohio files are identical:

  • policyengine-core test over deductions/medical_expense, integration.yaml and integration_2025.yaml: 63 passed.
  • With the two pre-fix line formulas swapped back in, the new and changed cases fail: 14 failed, 14 passed. scenario_025 gives oh_insured_unreimbursed_medical_care_expense_amount [800, 0] instead of [7,300, 0].
  • pytest tests/core/test_oh_unreimbursed_medical_deduction_invariants.py: 2 passed in 202 s on a machine at load average about 60, with peak RSS 1.6 GB.
  • Reference-URL and static code-health tests: 99 passed.

Hub microsimulation impact (current head)

A real microsimulation on the default dataset, main at the merge base f9f3887431 against this PR's head 5897e94c23, 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)
oh_unreimbursed_medical_care_expense_deduction +6.04bn +6.25bn 423 (1,346,236) 422 (1,355,850)
oh_income_tax -134.36M -134.59M 307 (1,062,905) 311 (1,070,499)
state_income_tax -134.36M -134.59M 307 (1,062,905) 311 (1,070,499)
income_tax +0 +0 0 (0) 0 (0)
household_net_income +134.36M +134.59M 296 (1,006,695) 299 (1,013,887)
household_benefits +0 +0 0 (0) 0 (0)

Most of the change goes the direction the PR describes. Premiums that fell on worksheet line 1 for filers with neither an employer plan nor Medicare were previously zero, and after-tax premiums above the 7.5% floor now count on line 3. Ohio income tax falls about $134M a year. In each year 29 records' deduction falls and 23 tax units pay more Ohio tax. The hub hasn't traced those records one by one. oh_medical_care_insurance_premiums exists only on this branch, so the table doesn't compare it.

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

Impact

This lowers Ohio income tax for two groups: filers with an employer plan or Medicare who pay after-tax premiums above the 7.5% floor (line 3), and filers with neither who report premiums through the decomposed inputs (line 1, previously zero for them). I did not run a microsimulation, because the machine was under heavy load, so the aggregate change is not measured.

Not changed

  • Employer coverage (has_esi) or an offer of it counts as a subsidized plan even when employer_contribution_to_health_insurance_premiums_category is NONE. NONE is that variable's default, so it cannot tell "the employer pays nothing" from "not reported". An offer that is not ACA-disqualifying is not modeled, so such an offer does not bar line 1.
  • The worksheet applies eligibility "during any portion of the year"; the model has annual eligibility only, so a person eligible for part of the year has all their premiums on one line.
  • self_employed_health_insurance_premiums still reads only the direct health_insurance_premiums input, so the self-employed reduction applies only when premiums are entered that way. That is a federal-side question outside this PR.

axiom: R.C. 5747.01(A)(10) TheAxiomFoundation/rulespec-us#1523 queued

🤖 Generated with Claude Code

MaxGhenis and others added 6 commits October 6, 2026 14:20
oh_unreimbursed_medical_care_expense_deduction_person listed the TaxUnit
variable oh_insured_unreimbursed_medical_care_expenses in its adds.
policyengine-core evaluates adds with map_to="person", which projects a
group value onto every member, so each member deducted the full worksheet
line 8 amount and Ohio AGI (the sum over members) fell by n times it. Each
spouse's joint filing credit qualifying income also lost the whole amount.

Use the existing person-level oh_insured_unreimbursed_medical_care_expenses_person,
which prorates line 8 by each member's share of line 5 and sums to the
return amount. Also enter federal AGI below zero as zero on worksheet
line 6, as every year's instructions say, so line 8 never exceeds line 5.

Adds YAML tests that fail on main (person split, total once, Ohio AGI,
joint filing credit, negative AGI) and Hypothesis invariants (conservation,
an independent worksheet, bounds, once-only accounting). Updates the 2026
retired-couple case, whose Medicare Part B line 8 had been deducted twice.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The invariants do not vary by year, so run Hypothesis on 2022 and 2025
with four examples and the seeded population on 2024 only. Test time
falls from about 50 s to 15 s locally; both bugs still fail it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Adds failing cases for PolicyBench scenario_025 and for the line 1/line 3
split: line 3 takes premiums of anyone eligible for Medicare OR an
employer-paid plan, and line 1 reads the premium inputs the data fills.
Corrects existing expectations that encoded the AND gate.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Line 3 took premiums only of a person who was Medicare-eligible AND whose
employer paid none of them; the statute and worksheet say eligible for
Medicare OR an employer-paid plan. Line 1 read health_insurance_premiums,
an input nothing fills, so premiums reported through the decomposed inputs
(the data path) reached neither line. Together these zeroed the premium
deduction for PolicyBench scenario_025.

- oh_employer_subsidized_health_plan_eligible: the return is eligible when
  the head or spouse has employer coverage, an offer of it, or an employer
  paying some or all of their premiums.
- oh_medical_care_insurance_premiums: the IRC 213 premium base the federal
  deduction uses, less the federal self-employed health insurance
  deduction (worksheet line 1 note).
- Lines 1 and 3 split those premiums on Medicare or employer-plan
  eligibility; the property test checks the partition.
- Cite the live 2025 booklet in place of dead tax.ohio.gov/static links.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Line 44 heading starts at the foot of PDF p. 25; the operative text,
including the pre-tax exclusion, is on p. 26. Adds the Department's FAQ
(Q8 and Q9: after-tax premiums for an employer plan go on line 3).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#9925 squash-merged as f9f3887. Its two test files conflicted add/add
with this branch's copies; main's versions equal #9925's head, so this
branch's extended versions are kept.

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

codecov Bot commented Oct 9, 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 (5897e94).
⚠️ Report is 321 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff            @@
##              main    #10020   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files            4         6    +2     
  Lines           76        86   +10     
  Branches         2         0    -2     
=========================================
+ Hits            76        86   +10     
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

Handing off to the US + core hub.

Nothing is pending from the opening session.

@MaxGhenis
MaxGhenis merged commit abb68e3 into main Oct 9, 2026
28 checks passed
@MaxGhenis

Copy link
Copy Markdown
Contributor Author

US + core hub merge audit, us#10020 at 5897e94c23464ce05c9d05f0ebe1dd8842af43f9 (squash): Ohio counts premiums on medical worksheet lines 1 and 3 (R.C. 5747.01(A)(10)).

  • Context: fixes the defect behind PolicyBench scenario_025, which the reference adversary found (Add the reference adversary and record its first run (evidence only; no score changes) policybench#200; Max's d1022). It follows Count Ohio's unreimbursed medical care deduction once per return #9925, which is merged.
  • Review: independent review APPROVE at this head (Subfleet job 20261009-062025-pe-us-10020-review).
  • 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 review;
    • the semantic-overlap guard;
    • head pinned.
  • Impact: a hub microsim, merge base f9f3887431 against this head (table in the body). Ohio's unreimbursed medical deduction rises $6.04bn (2025) on about 1.35M weighted tax units, and Ohio income tax falls $134M in each year. It's a bug fix, so it merges at any aggregate size.
  • --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