Skip to content

Exclude employer-plan-eligible premiums from the self-employed health insurance deduction - #10122

Open
MaxGhenis wants to merge 8 commits into
mainfrom
fix-sehi-employer-plan-exclusion
Open

MaxGhenis wants to merge 8 commits into
mainfrom
fix-sehi-employer-plan-exclusion

Conversation

@MaxGhenis

@MaxGhenis MaxGhenis commented Oct 11, 2026 •

Copy link
Copy Markdown
Contributor

What changes

We exclude medical premiums from the self-employed health insurance deduction when the taxpayer can join a subsidized employer plan. self_employed_health_insurance_employer_plan_eligible supplies the person-level flag; the existing earned-income limit still caps the deduction.

Congress bars a month's deduction when the taxpayer can join a subsidized plan maintained by their employer, their spouse's employer, a dependent's employer, or an under-27 child's employer. 26 U.S.C. 162(l)(2)(B)

The IRS excludes these amounts from Form 7206 line 1: “Amounts for any month you were eligible to participate in a health plan subsidized by your employer or your spouse's employer …”. The IRS also includes a dependent's or under-27 child's employer in that exclusion.

The IRS applies the bar “even if you didn’t actually participate” in its 2025 instructions, “Other coverage”. The IRS also treats QSEHRAs as subsidized employer plans in its 2022 and 2018 Form 1040 instructions.

Methodology choice

  1. For the head and spouse, we count has_esi, offered_aca_disqualifying_esi, or employer contributions of SOME or ALL as access through their own or their spouse's employer. We bar both joint filers if either meets that test. These annual inputs identify neither the employer maintaining a plan nor its subsidy.
  2. For tax unit dependents, we count only their own employer contributions of SOME or ALL. We leave enrollment and offer flags out because they can describe a parent's plan. Treasury includes offers to a “related individual” in 26 CFR 1.36B-2(c)(3)(i)(A). Congress does not list a parent's employer for the dependent's own deduction in section 162(l)(2)(B).
  3. We retain an annual approximation because these inputs cannot establish the statutory month-by-month conditions or each person's access through another family member. We state the remaining limits below and in the variable documentation.

Not modeled

  • Employer subsidies and family access. We treat enrollment or an offer as access to a subsidized plan, even when the contribution category says NONE. We also assume that the taxpayer can join a spouse's plan. The inputs record neither the subsidy nor family eligibility.
  • A nondependent filer's parent's plan. We cannot distinguish that coverage from the filer's own employer plan, including for a 25-year-old on a parent's plan.
  • Unaffordable offers. We miss subsidized offers that fail the ACA affordability or minimum-value tests, although Congress can bar the deduction for those offers.
  • QSEHRAs. No input records one; a QSEHRA counts only if the user enters it through the contribution category.
  • Months and other tax units. We apply an annual flag to the whole year and cannot use a spouse's flags from another tax unit. We also cannot establish the taxpayer's access to a dependent's or under-27 child's employer plan.
  • Long-term care plans. Congress applies the bar separately to plans that cover long-term care under section 162(l)(2)(B)(i)–(ii). Since Count eligible long-term care premiums in Schedule A medical expenses #10265, we include eligible long-term care premiums on Form 7206 line 2 in the deduction. No input records eligibility for an employer long-term care plan, so we apply the bar only to line 1.

We avoid reading filing_status because its calculation reaches dependents' incomes and this deduction. We use the existing head and spouse roles to pass eligibility between the two filers.

Examples and downstream effects

For example, a joint couple has $50,000 of Schedule C profit, $40,000 of spouse wages, $6,000 of medical premiums, and spouse ESI. Half the self-employment tax equals 50,000 × 0.9235 × 0.153 ÷ 2 = $3,532.3875, leaving a $46,467.6125 earned-income limit. Main deducts $6,000; this branch deducts $0, raising AGI from $80,467.61 to $86,467.61.

A 22-year-old dependent with $3,000 of profit and $1,000 of premiums for their own policy deducts $1,000 despite an enrollment or offer flag. Half the self-employment tax equals 3,000 × 0.9235 × 0.153 ÷ 2 = $211.94325, leaving a $2,788.05675 limit. We bar that deduction if their own employer pays SOME or ALL.

With the same joint couple and $500 of eligible long-term care premiums, we exclude the $6,000 of medical premiums and retain the $500 deduction for long-term care. The long-term care part equals $500, AGI equals 90,000 − 3,532.3875 − 500 = $85,967.61, and Schedule A medical expenses before its floor equal $6,000.

self_employed_health_insurance_premiums has two readers:

  • self_employed_health_insurance_ald_person adds the line 1 medical premiums and line 2 eligible long-term care premiums before applying the earned-income limit.
  • self_employed_health_insurance_ald_long_term_care_part subtracts the medical part from the person's deduction to identify its long-term care part.

The deduction feeds federal AGI, the QBI deduction definition, student-loan-interest MAGI, dependent Social Security calculations, and Schedule A premium exclusions. Its readers also feed deductions or income calculations in Alabama, Idaho, Michigan, Missouri, Mississippi, Montana, New Jersey, Ohio, and South Carolina.

We leave the original premium inputs from #10046 unchanged. We can still change spm_unit_health_insurance_premiums through AGI → Medicaid MAGI → ACA MAGI → premium tax credit → used credit → net Marketplace premium.

For example, the joint couple above takes up Marketplace coverage with annual benchmark and selected-plan premiums of $10,000. We hold the head's PTC eligibility true and the spouse's false to isolate the AGI path.

Annual output, 2025 Main premium formula This branch
AGI $80,467.61 $86,467.61
ACA premium tax credit $3,301.07 $2,650.25
SPM health insurance premiums $6,698.93 $7,349.75

We leave coordination between the deduction and the premium tax credit to #10124.

Properties and tests

test_self_employed_health_insurance_employer_plan_properties.py compares each drawn tax unit with a copy whose coverage flags we clear. Across 2024–2026, we check:

  1. Each person's premium base against a reference that uses all three flags for filers and contributions alone for dependents.
  2. Nonnegative amounts and the bounds deduction ≤ premium base ≤ paid medical premiums for these generated units, which have no long-term care premiums.
  3. That clearing coverage flags never lowers a person's premium base or deduction.
  4. That the twins' AGI difference equals the opposite change in the head's and spouse's deductions, for the generated units with wages and self-employment income only.

The reference repeats the formula's flag interpretation. We also compare each drawn household with a copy whose eligibility flags we clear. We check bounds and relationships between those copies across generated households, income boundaries, years, and flag combinations. Fixed YAML expectations cannot express those generated relationships. A deterministic grid covers every combination of each filer's coverage, offer, and contribution category, with a dependent's ESI flag clear or set.

The AGI identity does not hold for all incomes. A single 67-year-old with $20,000 of profit, $20,000 of Social Security, $6,000 of premiums, and an employer offer loses the $6,000 deduction. We include another $1,793.52 of taxable benefits, increasing AGI by $7,793.52.

We retain the head, spouse, housemate, and parent cases in the renamed YAML file. We add dependent enrollment and offer flags, dependent SOME contributions, filer ALL contributions, and the medical-versus-long-term-care example, with hand computations beside their expectations.

CI passed all 147 tests in the 15 files below at this head.

File CI job Result
self_employed_health_insurance_employer_plan_eligible.yaml Baseline (irs) 14 passed
test_self_employed_health_insurance_employer_plan_properties.py Rest Python shard 4, remaining-1 7 passed
test_self_employed_health_insurance_properties.py Rest Python shard 5, remaining-2 2 passed
test_medical_expense_premium_conservation.py Rest Python shard 2, core-2 5 passed
self_employed_health_insurance_ald.yaml Baseline (irs) 8 passed
self_employed_health_insurance_earned_income_limit.yaml Baseline (irs) 10 passed
eitc_earned_income.yaml Baseline (irs) 18 passed
policybench_ira.yaml Baseline (irs) 8 passed
ca_income_tax_before_refundable_credits.yaml Baseline (states-shard-1) 5 passed
ca_itemized_deductions_pre_limitation.yaml Baseline (states-shard-1) 17 passed
oh_uninsured_unreimbursed_medical_care_expenses.yaml Baseline (states-shard-1) 10 passed
heat_and_eat/snap.yaml Baseline (ssa-usda) 2 passed
owners_draw_self_employed_health_insurance_earned_income_limit.yaml Contrib (other-shard-3) 11 passed
self_employed_health_insurance_ald_long_term_care_part.yaml Baseline (irs) 5 passed
id_health_insurance_premiums_subtraction.yaml Baseline (states-shard-3) 25 passed

CI recorded 4.473 seconds for the employer-plan property file, summing its seven testcase times including setup and teardown. It ran in Rest Python shard 4, group remaining-1. CI recorded 1,490.94 seconds of wall time and 7,542,788 KiB (7.193 GiB) of peak resident memory for that whole group process. CI does not report this file's peak memory separately.

We ran each mutant separately against each test file and restored the original code after each run, checking git diff --exit-code. We recorded the capped M6 property run as unfinished; it gives no code verdict.

Mutant YAML result and killing case Property result and killing test
M1: main's adds = ["health_insurance_premiums"] Killed: 8 failed, 6 passed; Enrolled in employer coverage, so no premiums count. Killed: 4 failed, 3 passed; test_premium_base_matches_the_form_7206_reference and test_grid_bars_on_head_or_spouse_coverage_only[2024–2026].
M2: drop the spouse route Killed: 3 failed, 11 passed; The spouse's employer plan bars a joint filer's deduction. Killed: 4 failed, 3 passed; test_premium_base_matches_the_form_7206_reference and test_grid_bars_on_head_or_spouse_coverage_only[2024–2026].
M3: count dependents as filers Killed: 5 failed, 9 passed; A dependent's ESI flag leaves the parents' employer-plan eligibility clear. Killed: 4 failed, 3 passed; test_premium_base_matches_the_form_7206_reference and test_grid_bars_on_head_or_spouse_coverage_only[2024–2026].
M4: count dependents' enrollment and offer flags Killed: 3 failed, 11 passed; A dependent's employer offer flag leaves premiums for their own policy deductible. Killed: 4 failed, 3 passed; test_premium_base_matches_the_form_7206_reference and test_grid_bars_on_head_or_spouse_coverage_only[2024–2026].
M5: drop the contribution flag from own Killed: 3 failed, 11 passed; An employer pays part of the premiums, so no premiums count. Killed: 4 failed, 3 passed; test_premium_base_matches_the_form_7206_reference and test_grid_bars_on_head_or_spouse_coverage_only[2024–2026].
M6: drop ALL from employer_pays Killed: 1 failed, 13 passed; An employer pays all premiums for a plan, so the filer's own-policy premiums do not count. Unfinished: run_capped: killed (rss) after 489.1s: footprint 1.95 GB, rss 2.00 GB > cap 2.00 GB. No assertion result.

Microsimulation impact

We estimate that this branch lowers the self-employed health insurance deduction by $1.00bn in 2026 on the default dataset, and raises federal income tax by $119.9m and state income tax by $27.1m. We compared main at the merge base 4279ce2e63 with this branch at b184b86cda.

The premium base falls by $1.05bn for 75 self-employed people (231,552 weighted) whose premiums the employer-plan test now excludes. The deduction takes the smaller of the premiums and the earned-income limit, so it falls by less where that limit already capped the deduction on main. Household net income falls by $147.2m, and household_benefits doesn't change.

Output Main total Change Records changed (weighted)
self_employed_health_insurance_premiums $22.14bn -$1.05bn 75 (231,552)
self_employed_health_insurance_ald $17.31bn -$1.00bn 74 (231,551)
adjusted_gross_income $18,986.0bn +$1.00bn 74 (231,551)
income_tax $2,595.3bn +$119.9m 70 (231,551)
state_income_tax $578.0bn +$27.1m 49 (106,684)
household_benefits $2,060.0bn $0 0 (0)
household_net_income $17,161.5bn -$147.2m 71 (231,551)

Related work and follow-ups

#10120 remains open. Users should enter a business's own payment or reimbursement of premiums for its proprietor, partner, or more-than-2% S corporation shareholder as that person's premiums. Users should not flag those payments as access to another employer plan. Whoever lands second should add this distinction to the input documentation.

Proposed follow-ups:

  • Reuse the federal employer-plan flag in Ohio's deduction.
    oh_employer_subsidized_health_plan_eligible repeats the three-input test for the head and spouse.
    Read the federal person flag for those filers and retain Ohio's tax-unit aggregation.
  • Add monthly subsidized employer-plan eligibility.
    Annual flags cannot distinguish a partial year of eligibility from a full year.
    Add month-level inputs and exclude premiums for eligible months, with separate long-term care eligibility.
  • Add QSEHRA eligibility as an input.
    The existing contribution input captures a QSEHRA only when the user enters it there.
    Add a separate eligibility input, including months when the employee receives no reimbursement.
  • Allocate the Schedule A exclusion to the premiums the deduction took.
    We currently exclude medical premiums before long-term care premiums, even when the deduction took only long-term care premiums.
    Use the deduction's actual premium split so state deductions receive the corresponding medical and long-term care amounts.

The author describes PolicyEngine's three-flag test for every person in the linked Axiom addendum. We count only employer contributions for dependents here. We cannot represent all its monthly companion examples with annual inputs.

impact: in 2026 on the default dataset, the self-employed health insurance deduction falls by $1.00bn, federal income tax rises by $119.9m, state income tax rises by $27.1m and household net income falls by $147.2m.

axiom: us/statute/26/162/l queued (TheAxiomFoundation/rulespec-us#1676; (l)(2)(B) addendum with monthly companion tests: TheAxiomFoundation/rulespec-us#1676 (comment))

🤖 Generated with Claude Code

MaxGhenis and others added 2 commits October 11, 2026 16:21
… insurance deduction

26 U.S.C. 162(l)(2)(B) disallows the deduction for any month the taxpayer
is eligible for a subsidized plan of their own or their spouse's employer
(Form 7206 line 1). The premium base now leaves out the premiums of a
person who has or is offered employer coverage, whose employer pays part of
the premiums, or whose spouse on a joint return does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…oyer-plan bar

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…issue (#10124)

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

Copy link
Copy Markdown
Contributor Author

Correction to this PR's body, from the session that wrote it. The US + core hub now owns this PR. Head 7cebc0f41b has no finished review, no impact run and no reliable broad test result, and the body reads as if the tests were settled.

Local runs: don't rely on the 28-file and 9-file results.

  • I ran them as one chain (tests/run_targeted.zsh), not one file per heavy_run.sh call at nice 0 as the task required. The chain ran as a background Bash job, so it probably ran at nice 5.
  • The chain started around 16:25 UTC-4 on 10/11. My in-place mutation checks M1–M3 ran in the same worktree and finished at 16:29, so files that started in that window may have imported mutated sources. The log has no timestamps, so I can't name which files.
  • Every local result also predates the rebase. Rerun the set at the new head, one file per heavy_run.sh call, with nothing editing the worktree meanwhile. Run mutation checks in a separate checkout.

Impact: nothing ran. The pair (base 065d0e9560, PR 508d09200c) never left the heavy queue. impact/out/ holds no files, and the launchd job no longer exists. IMPACT_PENDING stays a real gap, and that pair's base no longer matches main.

Review: no verdict at any head. Subfleet run 20261011-164036-sehi-esi-review ended cancelled with no accepted output:

  • Attempt 1 hit a session limit.
  • Attempt 2 hit a weekly limit.
  • Attempt 3 exited 143.

None of them wrote findings. Their progress notes are unaccepted evidence, not a review.

Stale lines in the body.

Still current. The law and form citations, the five barred cases failing on main at the time, the not-modeled list, #10124 (open) and the axiom: line (rulespec-us#1676, open). Sources and evidence: ~/reviews/sehi-premium-base-2026-10/.

@MaxGhenis

Copy link
Copy Markdown
Contributor Author

Picking this up for the US hub. I'll post a hand-off comment here when it's ready.


Generated by Claude Code

The only conflict is the documentation of self_employed_health_insurance_premiums,
which #10046 rewrote to say pretax payroll payments belong only in
pre_tax_health_insurance_premiums and that the pretax and non-pretax sets are
disjoint. Keep this PR's formula and documentation and add those statements:
the formula still reads health_insurance_premiums, the non-pretax input, and
returns 0 when self_employed_health_insurance_employer_plan_eligible holds.

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

Copy link
Copy Markdown
Contributor Author

Picking this up for the US hub, continuing an earlier session's work on this brief (that session pushed nothing here). I'll post a hand-off comment here when it's ready.


Generated by Claude Code

Brings in #10554 (California state supplement for disabled dependent
children) and the version bump; no file overlaps this PR.

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

Copy link
Copy Markdown
Contributor Author

Ready for hub review

Head: bc6f8b6958df5ee29744a9f69aba5c63423336ae (still a draft). This continues an earlier session's work on this brief; that session pushed nothing here, so everything below is new.

1. Merge main. Two merge commits, no rebase:

2. Re-verified on the merged tree.

  • has_esi, offered_aca_disqualifying_esi and employer_contribution_to_health_insurance_premiums_category are still plain inputs with no formula, and they mean what the body says. They last changed in 2025-05, 2025-05 and 2024-01.
  • Readers of self_employed_health_insurance_premiums on main and on this head: only self_employed_health_insurance_ald_person, which should see the bar and does. I rewrote the body's "Consumers checked" section from the code. It lists every reader of the deduction. Document disjoint health premium inputs and correct consumer treatment #10046's premium consumers (medical_expense_health_insurance_premiums, snap_allowable_medical_expenses, pa_ccw_medical_expenses, spm_unit_health_insurance_premiums) read the raw premium inputs, so the bar correctly doesn't reach them.
  • Limit the self-employed health insurance deduction to net profit less half of SE tax and plan deductions #10109's cap: half of SE tax = 50,000 × 0.9235 × 0.153 / 2 = 3,532.39, so the limit is 46,467.61, above the $6,000 of premiums. AGI 86,467.61 = 50,000 + 40,000 − 3,532.39, and the YAML case asserts it (passes).
  • The filing_status cycle still exists: filing_status → surviving_spouse_eligible → tax_unit_child_dependents → is_child_dependent → is_qualifying_relative_dependent → dependent_gross_income → dependent_taxable_social_security → dependent_taxable_ss_magi → the deduction. The new variable reads only the three inputs and is_tax_unit_head_or_spouse, so it closes no new cycle; no test raised a CycleError.
  • Data counts: I removed "0 of 130,729 marital units" and "515 of the 1,379 records" from the body, because neither names its data build or date. I kept the 75-record / 228,000-person / $0.87 billion paragraph, which names populace_us_2024 build populace-us-2024-spm-20260909, and labelled it as the author's October run, not rerun.

3. Tests at this head, one file per process, with nothing editing the checkout: 109 passed, 0 failed, in 13 files (the body has the table). All but one ran at f603bf212a; the contrib owners-draw file (11) ran at bc6f8b6958. #10109's test_self_employed_health_insurance_properties.py (2) and #10046's test_medical_expense_premium_conservation.py (5) set no has_esi and pass unchanged. All six files that combine premiums with coverage inputs pass: eitc 15, policybench_ira 8, CA tax 5, CA itemized 17, OH 10, heat-and-eat SNAP 2. No expectation moved.

  • Environment notes. The first premium-conservation run failed to collect because hypothesis wasn't installed (uv sync without --extra dev). After the install it passed. The contrib owners-draw file was OOM-killed twice at about 7.1 GB in this sandbox's memory cgroup while other test processes ran. Run alone, it passed 11/11.

Mutants, run at f603bf212a in a separate scratch checkout, one process per file, restored afterwards (git status clean):

Mutant YAML (9) Properties (7)
M1 main's premium variable 5 fail (the 5 barred cases, each off by $6,000) 4 fail
M2 drop the spouse route 2 fail 4 fail
M3 dependents counted as spouses 2 fail 4 fail

All three are killed. The old body had 3 property failures for M3; I got 4. Hypothesis draws vary, and the test still kills the mutant either way.

4. Body, posted and read back. It matches my file except for one line. I removed the 28-file/314-case and 9-file/51-case claims and the lane-folder log, and added what I ran at this head. IMPACT_PENDING is now impact: pending (hub), and the axiom: line for rulespec-us#1676 is unchanged. The GitHub proxy replaces the closing "🤖 Generated with [Claude Code]…" line with its own footer on every write; I tried twice. The hub may want to restore that line.

Not done / judgment calls


Generated by Claude Code

@DTrim99

DTrim99 commented Nov 4, 2026

Copy link
Copy Markdown
Collaborator

PR Review: Exclude employer-plan-eligible premiums from the self-employed health insurance deduction

Summary

The PR adds self_employed_health_insurance_employer_plan_eligible (Person, bool) and makes self_employed_health_insurance_premiums 0 when it holds. A person is eligible when they have employer coverage (has_esi), are offered it (offered_aca_disqualifying_esi), or have an employer that pays some or all of their premiums. On a joint return, the other filer's eligibility counts too. A tax unit dependent's flags never bar the head or spouse, and the head's or spouse's flags never bar a dependent.

I checked the law first. The approach follows it:

  • Statute. 26 U.S.C. 162(l)(2)(B): "Paragraph (1) shall not apply to any taxpayer for any calendar month for which the taxpayer is eligible to participate in any subsidized health plan maintained by any employer of the taxpayer or of the spouse of, or any dependent, or individual described in subparagraph (D) of paragraph (1) with respect to, the taxpayer." Clauses (i) and (ii) apply that rule separately to long-term care plans and to other plans.
  • Form 7206 line 1 (2023, 2024, 2025, p. 1) leaves out "Amounts for any month you were eligible to participate in a health plan subsidized by your employer or your spouse's employer or the employer of either your dependent or your child who was under the age of 27 at the end of 2025."
  • The instructions (2025, p. 2, "Other coverage"; the 2023 and 2024 text is the same) say: "You can't take the deduction for any month you were eligible to participate in any employer (including your spouse's) subsidized health plan at any time during that month, even if you didn't actually participate." The rule for a dependent's or child's employer applies only "if you were eligible ... to participate" in that plan. "These rules are applied separately to plans that provide long-term care insurance and plans that don't."
  • Before Form 7206. The Form 1040 instructions for 2022 (p. 88, line 17) and 2018 (p. 93, line 29) give the same rule. They add that a QSEHRA "is considered to be a subsidized health plan maintained by an employer". Their example: eligibility for a spouse's plan from September 30 through December 31 bars the September–December premiums.

The PR applies the bar per person, before the earned income limit (Form 7206 line 1, then line 14). It handles the spouse's plan and leaves out a dependent's own coverage. It documents what annual inputs can't show: the months, a spouse filing separately, and a dependent's or child's plan that the taxpayer can join. Long-term care premiums are not in this base, so the separate long-term care test doesn't arise.

Open finding. One should-address item. The coverage flags don't record whose employer maintains the plan, so a person covered by or offered a parent's employer plan is barred, though the statute doesn't list a parent's employer. The code comment and test docstring claim the opposite.

There are no critical findings. CI is 35/35 green, with 3 jobs skipped by an earlier run.

Critical

None.

Should address

1. The own-coverage test treats any ESI enrollment or offer as the person's own employer's plan, but the inputs don't say whose employer it is. A parent's plan therefore bars the deduction, against the statute and against the PR's own comment (self_employed_health_insurance_employer_plan_eligible.py:43-51; test docstring lines 8-9).

  • The code. own = has_esi | offered_aca_disqualifying_esi | employer_pays, with no check on whose employer maintains the plan. The comment at lines 48-51 says: "A tax unit dependent's own deduction is not barred by the plan of a parent's employer, which the statute does not list." The Hypothesis docstring says: "A tax unit dependent is barred only by their own coverage, never by a parent's."
  • What the flags mean in the model. They are per-person ACA inputs. is_aca_eshi_eligible is has_esi | offered_aca_disqualifying_esi, and it makes a person ineligible for the premium tax credit. A child enrolled in a parent's family plan has ESI. A child who can enroll through a parent is offered disqualifying ESI: 26 CFR 1.36B-2(c)(3)(i)(A) counts "an individual who may enroll in the plan because of a relationship to the employee (a related individual)". So for a dependent, and for a young adult on a parent's plan, the flags usually describe the parent's employer's plan.
  • The law. 162(l)(2)(B) bars the deduction only for a plan of an employer "of the taxpayer or of the spouse of, or any dependent, or individual described in subparagraph (D) ... with respect to, the taxpayer". A self-employed dependent's parent is none of these. Neither is the parent of a 25-year-old who files their own return.
  • Example (2025), following the formula. Start from the PR's last YAML case: a dependent aged 22 with $3,000 of profit and $1,000 of premiums for their own policy. Add offered_aca_disqualifying_esi: true for the child, because the parent's employer offers family coverage. Then own is true, the premiums become 0 and the deduction becomes 0. The statute allows $1,000. The child's limit is 3,000 − 211.94 = 2,788.06.
  • Fix. No input separates a person's own employer's plan from a parent's, so a documentation fix is enough:
    • State in the variable documentation that has_esi, the offer flag and the contribution flag are read as coverage from the person's own employer, or from the spouse's. That includes coverage through a parent's plan, which the statute does not bar. List it with the other cases that aren't modeled.
    • Correct the comment at lines 48-51 and the docstring at lines 8-9. They hold only when the dependent's own flags are clear.
    • Optionally, count a dependent's own flags only when they have wages of their own. That is a heuristic, so document it if you adopt it.

Suggestions

  1. An unverified data claim in permanent documentation (lines 17-18). The documentation says "the default dataset has no married couple split across tax units". The PR body says that count "came from a run whose data build and date were not recorded, and it has not been rerun". Drop the clause, or tie it to how the dataset builds tax units.
  2. Other approximations to document. List these with the not-modeled cases:
    • A spouse's plan is assumed open to the taxpayer. The statute's test is whether the taxpayer can take part in the spouse's employer's plan. The IRS wording ("any employer (including your spouse's) subsidized health plan") supports the assumption, but the documentation's "the person, or their spouse, is eligible" restates the test loosely.
    • Only some offers count. offered_aca_disqualifying_esi covers only affordable, minimum-value offers. An offer of a subsidized plan that is unaffordable also bars the deduction.
    • QSEHRAs. The model has no QSEHRA input, which the 1040 instructions treat as a subsidized employer plan. A QSEHRA counts only if it is entered through the contribution category.
  3. Simplification (lines 43-54). person.tax_unit.sum(filer_own) > filer_own.astype(int) gives the same result as person.tax_unit.any(filer_own) inside own | (filer & ...): when filer_own is 1, own is already true. Also, has_esi | offered_aca_disqualifying_esi is is_aca_eshi_eligible. Neither change is required.
  4. YAML file name. CLAUDE.md asks for variable-named YAML files. Rename self_employed_health_insurance_employer_plan.yaml to self_employed_health_insurance_employer_plan_eligible.yaml.
  5. PR body.
    • The Form 7206 line 1 quote stops at "your spouse's employer" with no ellipsis. The form continues "or the employer of either your dependent or your child who was under the age of 27".
    • CLAUDE.md "Test design and CI cost" asks for timing and memory for new simulation-building Python tests. CI shows the new property file is cheap: 1.98 s for the Hypothesis test plus 1.15 s of grid fixture setup, in Rest Python shard 5, group remaining-2. Add that, and say what the Python file adds beyond the YAML. The reference property re-implements the formula; the bounds, monotonicity and AGI-differential invariants are what the YAML can't express.
  6. Optional monthly input. A person-level count of eligible months, defaulting to 12 when a flag is set, would let household users reproduce the IRS September 30–December 31 example. That is companion test I on rulespec-us#1676, which PE can't represent today.
  7. Ohio duplicate. oh_employer_subsidized_health_plan_eligible applies the same three-flag head-or-spouse test. A follow-up could have it read the new federal flag (tax_unit.any(head_or_spouse & flag)), so the two can't drift.

Verified correct

  • Order of operations. The bar applies to the line 1 premium base (self_employed_health_insurance_premiums). self_employed_health_insurance_ald_person then takes min(base, earned income limit), as on lines 1 and 14. 162(l)(2)(A) and Limit the self-employed health insurance deduction to net profit less half of SE tax and plan deductions #10109's limit are unchanged.
  • Spouse route. The other of the head and spouse in the tax unit (is_tax_unit_head_or_spouse, from the model's standard head and spouse roles). A housemate in another tax unit does not bar the deduction.
  • Dependents. A dependent's own employer plan does not bar the parent. That matches the statute, which requires that the taxpayer can take part in the plan. The head's or spouse's flags don't bar a self-employed dependent's own deduction. Both are pinned (YAML lines 136, 170).
  • Long-term care. The separate test in (l)(2)(B)(i)–(ii) can't arise, because the base has no long-term care premiums. health_insurance_premiums has none, and the earned income limit documentation says so.
  • Schedule A spillover. self_employed_health_insurance_ald_excluded_premiums and itemized_medical_expenses_excluded_premiums take min(premiums, deduction). Premiums the bar removes therefore stay in Schedule A medical expenses, as the instructions say ("any medical insurance payments not deductible on Schedule 1 ... line 17, can be included as medical expenses on Schedule A").
  • Premium tax credit. The PR doesn't net the credit. Its documentation correctly cites 280C(g) and the AGI circularity (Rev. Proc. 2014-41) and points to Net the premium tax credit out of the self-employed health insurance deduction (26 U.S.C. 280C(g), Rev. Proc. 2014-41) #10124, which is OPEN.
  • No new cycle. The new variable reads only three plain inputs and is_tax_unit_head_or_spouse. head_or_spouse_candidates reads only stored is_tax_unit_dependent values. CI ran the full suite, including Microsimulation, with no CycleError.
  • Mutation. Every new term is pinned:
    • has_esi, the offer and SOME by YAML lines 18, 30 and 42;
    • ALL, and NA not barring, by the 16 × 16 × 2 grid;
    • the spouse route by lines 54 and 85;
    • the filer gate, both ways, by lines 136 and 170.
  • References. Each is a single page that shows the text: Form 7206 p. 1, i7206 2025 p. 2 ("Other coverage") and i1040gi 2022 p. 88 (line 17's subsidized-plan paragraph). No source conflict needs recording: the form, the instructions and the statute agree. The diff has no conflict-sounding comments.
  • Changelog. changelog.d/fix-sehi-employer-plan-exclusion.fixed.md is top-level, with type fixed.
  • Vectorization and periods. All variables are YEAR. The enum comparisons, where and the tax-unit sum project per person.
  • Partner tests. No partner test file changed, and the Household API Partners job passes.
  • Hand checks (2025). Half of SE tax on $50,000 = 50,000 × 0.9235 × 0.153 / 2 = 3,532.3875, so the limit is 46,467.61, above $6,000.
    1. No plan: eligible false, base 6,000, deduction min(6,000, 46,467.61) = 6,000.
    2. has_esi: eligible true, base 0, deduction 0.
    3. Offered: true, 0, 0.
    4. Contribution SOME: true, 0, 0.
    5. Spouse with ESI: eligible [true, true]. The base is [0, 0]: the head is barred and the spouse is not self-employed. The deduction is 0. AGI = 50,000 + 40,000 − 3,532.39 = 86,467.61.
    6. The same without marital units: base [0, 0], deduction 0. The spouse role comes from the next-oldest non-dependent adult.
    7. Housemate: eligible [false, true], base [6,000, 0], tax-unit deductions [6,000, 0].
    8. Dependent child with own ESI: eligible [false, false, true], base [6,000, 0, 0]. The tax-unit deduction is 6,000, because tax_unit_non_dep_sum leaves out the child.
    9. Parent with ESI and a self-employed dependent: eligible [true, false], base [0, 1,000]. The child's SE tax is 3,000 × 0.9235 = 2,770.50 × 0.153 = 423.89, half 211.94, so the limit is 2,788.06 and the deduction is [0, 1,000].
    10. Grid twins: each deducts 6,000. Barred drawn units have AGI exactly 6,000 higher than their twins.

Axiom line assessment

axiom: us/statute/26/162/l queued (TheAxiomFoundation/rulespec-us#1676; (l)(2)(B) addendum ...) is valid:

  • Issue status. rulespec-us#1676 is OPEN and labelled pe-parity. It is dispatch-ready: module path us/statutes/26/162/l.yaml, corpus citation us/statute/26/162/l, verbatim law, required outputs and companion tests A–G. The blocker is recorded (a corpus pin advance and approval of the billed run).
  • The addendum. Comment 6113504746 quotes (l)(2)(B), (i) and (ii) verbatim from the corpus lock. It replaces output 2's leaf with a month-by-month, two-class rule and adds companion tests H–K from Form 7206, its instructions and the 1040 instructions, the same sources as the YAML.
  • Match to the head. The addendum's description of PE (three flags, head and spouse, months, separate filers, dependents' plans, no long-term care) matches the head. The formula is unchanged since 53fe0ac784 (2026-10-11); later commits changed only documentation and merged main.
  • After the fix. A documentation-only fix for should-address 1 needs no issue change. If you adopt the wage heuristic, update the addendum's PE coverage paragraph.

CI status

35 checks pass at bc6f8b6958, re-checked just before this report: all Baseline, Contrib, Python Rest shards 1–8, Microsimulation, Household API Partners, Package and compatibility, PR validation, Quick Feedback. Three checks show "skipping"; they belong to an earlier run. The new YAML ran in Baseline (irs), 9/9 passing. The property file ran in Rest Python shard 5, 7/7 passing.

Branch status

Verdict: REQUEST_CHANGES

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

Requesting changes: 0 critical, 1 should. The § 162(l)(2)(B) bar is applied correctly, and the axiom line is valid. Open: has_esi and the offer and contribution flags don't record whose employer maintains the plan, so a dependent on a parent's plan is barred though (l)(2)(B) doesn't list a parent's employer, and the code comment and test docstring claim the opposite. Document the approximation and correct both. Full report in the comment above.

@MaxGhenis

Copy link
Copy Markdown
Contributor Author

Hub review in progress

Head reviewed: bc6f8b6958df5ee29744a9f69aba5c63423336ae (unchanged since the hand-off). Findings so far, still being checked:

  • Defect reproduces at main. Joint couple, 2025, head $50,000 Schedule C profit and $6,000 premiums, spouse $40,000 wages with has_esi: main 4279ce2e63 deducts $6,000 (AGI 80,467.61); this head deducts 0 (AGI 86,467.61).
  • Main has moved under the PR. Count eligible long-term care premiums in Schedule A medical expenses #10265 merged on 11/05 (after this head's CI run). It adds self_employed_health_insurance_ald_long_term_care_part, a second reader of self_employed_health_insurance_premiums, and adds Form 7206 line 2 premiums to the deduction. The body's "only self_employed_health_insurance_ald_person" reader list and "Long-term care plans ... not in this premium base" are now out of date. The merge is clean; I'm running the tests on the merged tree.
  • A parent's employer plan still bars a dependent whose own offered_aca_disqualifying_esi or has_esi flag comes from that parent's plan. The code comment says it doesn't. Witness at this head: parent with ESI and a 22-year-old self-employed dependent with $1,000 premiums and offered_aca_disqualifying_esi: true gives the child premiums 0, deduction 0 (main: 1,000).
  • Sources: irs.gov, govinfo.gov, uscode.house.gov and ecfr.gov all refused connections from this VM, so I can't confirm any quote independently.

Generated by Claude Code

@MaxGhenis

Copy link
Copy Markdown
Contributor Author

Ready for hub review
VERDICT: REQUEST_CHANGES
bc6f8b6
METHODOLOGY_CHOICE: yes

Independent review of the head above. It is unchanged since the hand-off. Main has since moved to 4279ce2e63, 35 commits ahead, and one of those commits (#10265) changes the code this PR feeds. git merge-tree --write-tree origin/main bc6f8b6958 merges cleanly, so I ran every test both at the head and on that merge (the "merged tree").

The bar works for the case it targets: a spouse's employer plan on a joint return. It reproduces at main and is fixed here. I'm requesting changes for three reasons: the code says it protects dependents from a parent's plan, but it doesn't; main's #10265 has made the body's reader list and its long-term care statement untrue; and the documentation carries an unverified data claim.

Findings

P1

1. A parent's employer plan bars a dependent (and a young filer) whose own coverage flags come from that plan. The code comment, the test docstring and the body all say the opposite.

  • Where. self_employed_health_insurance_employer_plan_eligible.py:43-51. own is has_esi | offered_aca_disqualifying_esi | employer_pays, read off the person's own record. The comment at :49-51 says "A tax unit dependent's own deduction is not barred by the plan of a parent's employer". The property file's docstring (lines 8-9) says "never by a parent's", and the body's "Change" section says "A parent's employer is not among the employers the statute lists for the dependent's own deduction".
  • Why it fails. None of the three flags records whose employer maintains the plan. A child on a parent's family plan has has_esi. A child who can enrol through a parent is offered ESI as a related individual. The statute's list (taxpayer, spouse, dependent, child under 27) doesn't include the taxpayer's parent. The PR's last YAML case ("A parent's employer plan does not bar a self-employed dependent's own deduction") passes only because the child's flags are clear. It asserts a weaker claim than its name.
  • Witnesses (2025, my probe script, year-keyed inputs, run at each tree):
Case main 4279ce2e63 this head
Parent with ESI; dependent aged 22, $3,000 profit, $1,000 premiums, offered_aca_disqualifying_esi: true child premiums 1,000, deduction 1,000 eligible [True, True], premiums [0, 0], deduction [0, 0]
Single filer aged 24, $50,000 profit, $6,000 premiums, has_esi: true (e.g. on a parent's plan), contribution NONE deduction 6,000, AGI 40,467.61 deduction 0, AGI 46,467.61
  • Fix. No input can tell these cases apart, so fix the documentation rather than the formula.
    • Replace the comment at :48-51 with: # The plan of the spouse's employer also bars the deduction: on a joint return, the other of the head and spouse. The flags don't record whose employer maintains the plan, so a person whose own flags come from a parent's plan (which the statute doesn't list) is barred too; see the documentation.
    • In the property docstring, replace "A tax unit dependent is barred only by their own coverage, never by a parent's." with "A tax unit dependent is barred only by their own flags, never by the head's or spouse's."
    • Add this sentence to the variable documentation's not-modeled list: "The coverage inputs do not record whose employer maintains the plan, so coverage through a parent's employer, which the statute does not list, is treated as the person's own."
    • Make the same correction in the body's "Change" and "Not modeled" sections.

P2

2. Main has moved under the PR. Since #10265, the body's reader list and its long-term care statement are false, and the green CI predates that merge.

  • What changed. Count eligible long-term care premiums in Schedule A medical expenses #10265 merged on 11/05, after this head's only CI run (41734027549, 11/03). It adds self_employed_health_insurance_ald_long_term_care_part, which reads self_employed_health_insurance_premiums (self_employed_health_insurance_ald_long_term_care_part.py:27). It also makes self_employed_health_insurance_ald_person add self_employed_long_term_care_premiums (Form 7206 line 2).
  • False lines in the body.
    • "Readers of self_employed_health_insurance_premiums: only self_employed_health_insurance_ald_person … No parameter list or other variable reads the premium variable." The merged tree has two readers.
    • "Long-term care plans. … They are not in this premium base."
    • "Re-read on the merged tree (main 9f0242f6de)."
  • Behaviour on the merged tree. It is coherent with (l)(2)(B)(i)–(ii) as long as no input records an employer long-term care plan: the bar removes line 1 and leaves line 2.
    • Case: the spouse has ESI; the head has $6,000 of medical premiums and $500 of long-term care premiums.
    • Result: premiums 0, ald_person 500, long-term care part 500, AGI 85,967.61, Schedule A itemized_medical_expenses 6,000.
    • That reading, and the absence of any long-term care employer-plan input, now need saying in the documentation.
  • Fix.
    1. Merge main. It is clean.
    2. Replace the long-term care bullet in "Not modeled" with: "Long-term care plans. (l)(2)(B)(i)–(ii) apply the bar separately to plans that cover long-term care. Since Count eligible long-term care premiums in Schedule A medical expenses #10265, eligible long-term care premiums (Form 7206 line 2) enter the deduction; no input records eligibility for an employer long-term care plan, so the bar applies only to line 1."
    3. Add self_employed_health_insurance_ald_long_term_care_part to the reader list.
    4. Let CI run on the merge.

3. The variable documentation states an unverified data fact. At self_employed_health_insurance_employer_plan_eligible.py:17-18: "and the default dataset has no married couple split across tax units". The body says this count "came from a run whose data build and date were not recorded, and it has not been rerun".

  • Replace ", and the default dataset has no married couple split across tax units. The plan" with ". The plan".
  • Also replace the comma before "it cannot use the filing status" with a semicolon, if the sentence still needs it.

P3

  1. The proxy fires without a subsidy, and misses some subsidized offers. The documentation doesn't say so.
    • has_esi bars the deduction even when the contribution category is NONE. That is the default, so NONE can't be read as "unsubsidized". See the single-filer witness above.
    • offered_aca_disqualifying_esi covers only affordable, minimum-value offers, so an unaffordable subsidized offer doesn't bar.
    • A spouse's coverage or offer is taken to mean that the taxpayer can join that plan.
    • Fix: add one sentence to the documentation: "Enrollment or an offer is taken as eligibility for a subsidized plan, and a spouse's plan as open to the taxpayer; the inputs record neither the subsidy nor family eligibility."
  2. QSEHRAs. The body's law section says the 2018 and 2022 Form 1040 instructions treat a QSEHRA as a subsidized employer plan. But the model has no QSEHRA input (grep -rli "qsehra\|small employer health reimbursement" policyengine_us/variables policyengine_us/parameters finds nothing), and "Not modeled" doesn't mention it. Add: "QSEHRAs. No input records one; it counts only if entered through the contribution category."
  3. The data paragraph reads as current scope. It is labelled "not rerun". But "leaving the spouse case (75 records, 228,000 weighted people, $0.87 billion…) that this PR closes" still reads as the size of the change. It counts only the spouse route; the offer and contribution routes in the data are uncounted. Suggest: "In the original author's October 2026 run (build populace-us-2024-spm-20260909, not rerun), 75 records … had premiums and a spouse with ESI. That count doesn't cover the offer or contribution routes. The hub's impact run at a head that includes Count eligible long-term care premiums in Schedule A medical expenses #10265 is the current measure."
  4. Tests.
    • My own mutant M4 (drop ALL from employer_pays) passes all 9 YAML cases and is killed only by the property file.
    • Add a YAML case with contribution ALL.
    • Rename the YAML file to self_employed_health_insurance_employer_plan_eligible.yaml, which is the variable name CLAUDE.md asks for.
    • After finding 1, add a case where a dependent's own offer flag is set, with the expected (barred) result and a comment saying why.
  5. Cost of the new Python file. CLAUDE.md asks for this, and the body doesn't give it. My local measurement on this VM (Python 3.14, not Linux CI): test_self_employed_health_insurance_employer_plan_properties.py takes 49.8 s wall including import, 11.3 s in pytest, with 0.97 GB peak RSS. Its "Reference" property re-implements the formula; the bounds, monotonicity and AGI-differential properties are what the YAML can't express. Say so in the body.
  6. "Tests run at this head" says most files ran at f603bf212a. I reran them all at bc6f8b6958 (table below), so that caveat can go.

Follow-up (identical on main; not this PR's defect)

  1. The Schedule A exclusion takes medical premiums first, even when the deduction took only long-term care premiums. itemized_medical_expenses_excluded_premiums is min(filer medical premiums, tax-unit deduction).
    • Witness on main 4279ce2e63: the head is self-employed with only $500 of long-term care premiums, and the spouse (not self-employed) pays $6,000 of medical premiums. The results are ald 500, excluded_premiums 500, excluded_long_term_care_premiums 0.
    • The total (itemized_medical_expenses 6,000) is right, but the split between medical and long-term care is wrong.
    • Readers of the split: OH oh_long_term_care_insurance_premiums, ID, and MT mt_premium_deduction_joint / mt_medical_expense_deduction_joint.
    • This PR makes the case reachable for every barred self-employed person with long-term care premiums (merged-tree witness: excluded medical 500, excluded long-term care 0).

Defect, rule and hand values

  • The defect reproduces at main. Take a joint couple in 2025: the head has $50,000 of profit and $6,000 of premiums; the spouse has $40,000 of wages and has_esi. Main deducts 6,000 and gives AGI 80,467.61. This head deducts 0 and gives AGI 86,467.61. The merged tree also gives 86,467.61.
  • Hand values, computed before reading the code.
    • Self-employment tax on $50,000: 50,000 × 0.9235 = 46,175; × 0.153 = 7,064.775; half = 3,532.3875.
    • Earned income limit: 50,000 − 3,532.39 = 46,467.61, which is above $6,000.
    • Barred AGI: 50,000 + 40,000 − 3,532.39 = 86,467.61. Unbarred AGI: 80,467.61.
    • Dependent's limit: 3,000 × 0.9235 = 2,770.50; × 0.153 = 423.89; half = 211.94; limit 2,788.06, which is above $1,000.
    • Every YAML expectation matches these.
  • Coverage of years, filing statuses and entities.
    • The formula is the same for every year (all variables are YEAR).
    • Joint: head and spouse each see the other.
    • Separate: in PE a separate filer is a one-adult tax unit (filing_status.py: is_separated and not tax_unit_married), so the spouse's plan is not seen. Witness: head and spouse in separate units, spouse with has_esi. filing_status is [SEPARATE, SEPARATE] and the head keeps 6,000. The body and the documentation both say so.
    • Dependents: barred by their own flags only (finding 1).
    • Months: any eligibility counts for the whole year. The body and the documentation both say so.

Checks on the brief's points

  • The statute's employer list.
    • Head or spouse through their own or the other's plan: that matches "employer of the taxpayer or of the spouse".
    • A dependent through their own employer: that matches "employer of the taxpayer".
    • A self-employed dependent through a parent's employer: the parent isn't listed, so the plan doesn't bar the deduction. The code bars it anyway whenever the parent's plan shows up in the child's own flags (finding 1).
    • The plan of a dependent's or an under-27 child's employer: the bar needs "the taxpayer is eligible to participate", and no input records that. The body's reason holds.
  • The filing-status cycle. My mutant M5 makes the new variable read filing_status, and it raises CycleError: self_employed_health_insurance_employer_plan_eligible → filing_status → surviving_spouse_eligible → tax_unit_child_dependents → is_child_dependent → is_qualifying_relative_dependent → dependent_gross_income → dependent_taxable_social_security → dependent_taxable_ss_magi → self_employed_health_insurance_ald_person → self_employed_health_insurance_premiums → (back). This is the body's chain exactly.
  • Axiom line. It is present. Encode IRC §162(l): the self-employed health insurance deduction and its 401(c) earned-income limit TheAxiomFoundation/rulespec-us#1676 is open and labelled pe-parity. Its 10/11 addendum covers (l)(2)(B), and a comment from 11/05 (the Count eligible long-term care premiums in Schedule A medical expenses #10265 parity request) notes the separate long-term care test. impact: pending (hub) is present.
  • What the build brief asked for that the PR doesn't do.
    • The closing "🤖 Generated with [Claude Code]" line isn't in the body. The author says the proxy replaced it.
    • The author did not confirm the Axiom issue's state. I have confirmed it above.
    • Everything else is done: the merge, the documentation sentence from Document disjoint health premium inputs and correct consumer treatment #10046, impact: pending (hub), ready for review, and CI green at the time.

Readers (merged tree)

  • self_employed_health_insurance_employer_plan_eligible: only self_employed_health_insurance_premiums.
  • self_employed_health_insurance_premiums:
  • Readers of the deduction that see the smaller amount:
    • federal:
      • self_employed_health_insurance_ald
      • dependents_self_employed_health_insurance_ald
      • dependent_taxable_ss_magi
      • self_employed_health_insurance_ald_excluded_premiums
      • itemized_medical_expenses_excluded_premiums
      • itemized_medical_expenses_excluded_long_term_care_premiums
      • the parameter lists irs/ald/deductions, qbi/deduction_definition and student_loan_interest/magi/person_alds
    • states:
      • AL (AGI deductions)
      • ID id_qualified_health_insurance_premiums
      • MI mi_household_resources
      • MO AGI and qualified premiums
      • MS adjustment
      • MT long-term care and joint premium deductions
      • NJ medical expense deduction
      • OH medical-care and long-term care premiums
      • SC two-wage-earner earned income
  • No reader gets worse at this head: Schedule A takes back premiums the bar removes (merged-tree witness: itemized_medical_expenses 6,000). The one defect is finding 10, which is identical on main.
  • Baseline effect. The diff moves outputs at baseline for every self-employed person with premiums whose own record has a coverage, offer or SOME/ALL flag, or whose joint filer's does. The population check is the hub's impact pair at a head that includes Count eligible long-term care premiums in Schedule A medical expenses #10265. It should report, separately for the own-flag route and the spouse route, how many records change.

Tests run (one file per process)

File bc6f8b6958 merged tree
self_employed_health_insurance_employer_plan.yaml 9 passed 9 passed
test_self_employed_health_insurance_employer_plan_properties.py 7 passed 7 passed
test_self_employed_health_insurance_properties.py (#10109) 2 passed 2 passed
test_medical_expense_premium_conservation.py (#10046) 5 passed 5 passed
self_employed_health_insurance_ald.yaml 8 passed 8 passed
self_employed_health_insurance_earned_income_limit.yaml 10 passed 10 passed
eitc_earned_income.yaml 15 passed 18 passed (main added 3)
policybench_ira.yaml 8 passed 8 passed
ca_income_tax_before_refundable_credits.yaml 5 passed 5 passed
ca_itemized_deductions_pre_limitation.yaml 17 passed 17 passed
oh_uninsured_unreimbursed_medical_care_expenses.yaml 10 passed 10 passed
heat_and_eat/snap.yaml 2 passed 2 passed
owners_draw_self_employed_health_insurance_earned_income_limit.yaml (contrib) 11 passed (rerun alone after an OOM kill at 6.5 GB alongside other runs) 11 passed (9.5 GB peak)
self_employed_health_insurance_ald_long_term_care_part.yaml (#10265) n/a 5 passed
id_health_insurance_premiums_subtraction.yaml (#10265) n/a 25 passed

CI at bc6f8b6958: run 41734027549 ("Pull request", 11/03) concluded success, with all 35 jobs success. Its merge base predates #10265.

Mutants

I ran these at bc6f8b6958 in a separate scratch worktree, one process per file, and restored afterwards (git status clean).

Mutant YAML (9) Properties (7) Killed by
M1 main's premium variable (adds) 5 fail 4 fail both
M2 drop the spouse route (return own) 2 fail 4 fail both
M3 every member a filer 2 fail 4 fail both
M4 (mine) drop ALL from employer_pays 0 fail: survives 4 fail property file only
M5 (mine) read filing_status 2 fail (CycleError) 7 fail both
M6 (mine) dependents' own flags never bar them 1 fail 1 fail both

Checked and sound

  • The formula and its vectorization: tax_unit.sum(filer_own) > filer_own is true exactly when the other filer is eligible, and the housemate and dependent YAML cases pin it.
  • The changelog fragment is a top-level fixed file.
  • Nothing under .github/ or tests/policy/baseline/partners/ changed.
  • The only open PR that matches the changed file names is Let farm, partnership and S corporation plans set the self-employed health insurance limit #10120. It shares no files with this PR (gh api repos/.../pulls/10120/files).
  • The months and separate-filer limits are stated in both the body and the documentation.

What I couldn't check

  • Primary sources. irs.gov, govinfo.gov, uscode.house.gov and ecfr.gov all refused connections from this VM (proxy 403). I could not confirm the 162(l)(2)(B) text, the Form 7206 line 1 wording (2023–2025), the i7206 "Other coverage" text, or the 2018/2022 Form 1040 QSEHRA statements. My statutory reading uses only the 162(l)(2)(B) text as quoted in the PR body and the brief.
  • Data. I could not check how the default dataset sets offered_aca_disqualifying_esi or the contribution category for self-employed people with premiums, or the body's 75-record figures. No microsimulation was run.

Generated by Claude Code

MaxGhenis and others added 3 commits November 5, 2026 19:36
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@DTrim99

DTrim99 commented Nov 9, 2026

Copy link
Copy Markdown
Collaborator

PR Review (round 2): Exclude employer-plan-eligible premiums from the self-employed health insurance deduction

Summary

Since our round 1 review (2026-11-04, head bc6f8b6958), the author has pushed two commits (add2d865c7, b184b86cda) and one clean merge of main (264fec1c0c, no hand-resolved blocks).

Round 1 findings. The should-address item is fixed, through a behavior change rather than only a documentation change. A tax unit dependent is now barred only when their own employer pays SOME or ALL of the premiums. Their has_esi and offer flags no longer count, because those flags can describe a parent's plan. The comment, docstring and documentation now match the code, and new YAML cases pin the change. Suggestions 1–5 are done. Suggestions 6 (monthly input) and 7 (Ohio) are listed in the body as follow-ups.

New in the fix commits. An ALL case and a medical-versus-long-term-care case for the #10265 merge. I checked every new or changed expectation by hand, and all are correct.

Two new should-address items. Neither is a formula error at the head.

  1. Let farm, partnership and S corporation plans set the self-employed health insurance limit #10120 merged on 2026-11-07, so this PR now lands second. On the merged tree, a plan that the person's own partnership or S corporation establishes bars the deduction if its payment is flagged as employer coverage. The PR body itself says the second PR to land must add this distinction to the documentation.
  2. The rulespec-us#1676 addendum still describes the old three-flag test for dependents.

CI: 36/36 pass at b184b86cda. The branch merges cleanly with current main.

Prior findings

Finding Status Evidence
Should 1: a parent's plan, seen in the dependent's own flags, bars a self-employed dependent, against the statute and the PR's own comment and docstring Fixed self_employed_health_insurance_employer_plan_eligible.py:63-68: own = employer_pays | (filer & (has_esi | offered_aca_disqualifying_esi)). The comment (60-62, 70-71), the property docstring and the documentation now describe this rule. The documentation lists "a parent's plan for a nondependent filer, such as a 25-year-old" as a case the inputs can't distinguish. Mutation: drop filer & at line 64 and the dependent becomes eligible in YAML cases 202 (offer flag) and 235 (ESI flag), which expect the child's base to be 1,000, and in case 136, which expects [false, false, false]. All three fail, and so does the grid reference. The body's M3/M4 runs agree.
Suggestion 1: unverified "default dataset" claim Fixed The clause is gone from the documentation.
Suggestion 2: document the approximations (spouse's plan assumed open, only affordable offers count, QSEHRA) Fixed Documentation lines 20-29: "assumes that each joint filer can join the other's plan"; "misses unaffordable subsidized offers because offered_aca_disqualifying_esi covers only affordable, minimum-value offers"; "No input records a QSEHRA; the model counts one only through the contribution category."
Suggestion 3: simplification Fixed (partly adopted) Line 73 uses person.tax_unit.any(filer_own). It does not reuse is_aca_eshi_eligible, which was optional and fits less well now that the two flags are gated by filer.
Suggestion 4: rename the YAML to the variable name Fixed self_employed_health_insurance_employer_plan_eligible.yaml
Suggestion 5: PR body quote and CI cost Fixed The Form 7206 quote now ends with "…", and the body adds the dependent and under-27 child employers. CI cost: 4.473 s for the property file (Rest Python shard 4, remaining-1). The group process peaked at 7.19 GiB, and the body says CI gives no peak memory for the file alone. It also says what the Python file adds beyond YAML: generated relationships, while the reference property repeats the formula.
Suggestion 6: optional monthly input Deferred, with a reason Listed under the body's "Proposed follow-ups", with separate long-term care eligibility. Acceptable for an optional item.
Suggestion 7: Ohio duplicate Deferred, with a reason Listed as a follow-up: oh_employer_subsidized_health_plan_eligible would read the federal person flag. Acceptable.

New findings

Critical

None.

Should address

1. #10120 has merged, so this PR now lands second. It must add the input distinction its own body names. On the merged tree, flagging the payment of a plan the person's own partnership or S corporation establishes bars the deduction (self_employed_health_insurance_employer_plan_eligible.py:9-39, 63-73).

  • What changed. Let farm, partnership and S corporation plans set the self-employed health insurance limit #10120 merged on 2026-11-07, after this head was pushed. Main's self_employed_health_insurance_plan_business and s_corp_shareholder_medicare_wages tell a partner or more-than-2% shareholder whose business pays or reimburses the premiums to enter them in health_insurance_premiums and to set is_self_employed.

    • Neither input says anything about the coverage flags.
    • This PR's documentation reads a contribution of SOME or ALL, or has_esi, as "access through their own or their spouse's employer". An S corporation is the shareholder's W-2 employer, so a user would naturally record its payment that way.
    • The body's "Related work" section still says "Let farm, partnership and S corporation plans set the self-employed health insurance limit #10120 remains open … Whoever lands second should add this distinction to the input documentation."
  • The law. Notice 2008-1:

    • Section 1372(a): "an S corporation shall be treated as a partnership, and any 2-percent shareholder of the S corporation shall be treated as a partner".
    • "A 2-percent shareholder is not an employee for purposes of §106."
    • Example 2 (pp. 3-4): "the S corporation obtains an accident and health insurance plan in the name of the S corporation … The S corporation makes all the premium payments … shareholder B is allowed the deduction under § 162(l)". The examples assume only that no shareholder is eligible for "any subsidized health plan maintained by an employer of the shareholder or the shareholder's spouse". The corporation's own plan therefore isn't one of the plans that bar the deduction.
    • Rev. Rul. 91-26 treats premiums a partnership pays for a partner the same way.
  • Example (2025, merged tree, following the formula). A more-than-2% shareholder has these inputs:

    • is_self_employed: true;
    • $60,000 of s_corp_shareholder_medicare_wages, inside employment_income;
    • plan business S_CORPORATION;
    • $6,000 of corporation-paid premiums in health_insurance_premiums, as main's documentation says;
    • employer_contribution_to_health_insurance_premiums_category: ALL.

    Then own is true (line 63), the line 1 base becomes 0 and the deduction becomes 0. The Notice allows $6,000, and the line 11 limit is $60,000. has_esi: true gives the same result. That flag is a natural input for coverage under the corporation's group plan, and the ACA variables also read it.

  • Fix.

    1. Add a sentence to this variable's documentation, and ideally to self_employed_health_insurance_plan_business or the contribution input. It should say that the plan the person's own partnership or S corporation establishes (26 U.S.C. 162(l)(5), 1372(a); Notice 2008-1) is not an employer plan for this test, so the business's payment should not be recorded as an employer contribution of SOME or ALL. It should also say that has_esi for coverage under that plan bars the deduction in the model.
    2. Alternatively, in code: ignore the person's own flags when the plan business is PARTNERSHIP or S_CORPORATION.
    3. Update the body's "Let farm, partnership and S corporation plans set the self-employed health insurance limit #10120 remains open" line.
    4. A YAML case with plan business S_CORPORATION and the documented inputs would pin whichever route you take.

2. The queued rulespec-us issue no longer matches the head after the dependent fix (rulespec-us#1676, addendum comment).

  • What is stale.
    • The addendum's "PE coverage after Exclude employer-plan-eligible premiums from the self-employed health insurance deduction #10122" says the variable "is true when the person, or the other of the head and spouse on a joint return, has employer coverage (has_esi), is offered it (offered_aca_disqualifying_esi), or has an employer that pays some or all of the premiums." At the head, a dependent's has_esi and offer flags no longer count; only SOME or ALL does (lines 63-68).
    • Companion test J cites the PE case "A dependent child's own employer plan does not bar the parent's deduction". That case is now "A dependent's ESI flag leaves the parents' employer-plan eligibility clear", and the child's eligibility expectation changed from true to false.
  • No update since. The only later comment (2026-11-05, the Count eligible long-term care premiums in Schedule A medical expenses #10265 extension) correctly records that the bar covers line 1 only, but it doesn't touch the dependent rule. The PR body acknowledges the mismatch ("The author describes PolicyEngine's three-flag test for every person in the linked Axiom addendum. We count only employer contributions for dependents here.") but doesn't correct the issue. This is a material behavior change, so the issue should follow it.
  • Fix.
    • Post a short comment on rulespec-us#1676. It should correct the PE coverage paragraph (filers: the three flags; dependents: only their own employer's SOME or ALL; line 1 only since Count eligible long-term care premiums in Schedule A medical expenses #10265) and test J's case name.
    • The required outputs and companion tests H–K need no change: they encode the law, and their values still hold.
    • Optionally, add a statute-sourced companion test: a self-employed dependent covered through a parent's employer plan is not barred, because 162(l)(2)(B) doesn't list the taxpayer's parent.

Suggestions

  1. State the other side of the dependent rule. A dependent enrolled in, or offered, a subsidized plan by their own employer is now barred only when the contribution category is SOME or ALL. With NONE, which is the default, the deduction stands.
    • The documentation implies this ("It ignores their enrollment and offer flags"). But its "cannot distinguish" list names only the cases where the model over-bars.
    • Add the under-bar case to that list.
    • Also note that a contribution category entered on a child for a parent's family plan would bar the child, because the documentation reads that input as the dependent's own employer.
    • The heuristic itself is reasonable and documented: a dependent's coverage flags more often describe a parent's plan.
  2. Track the deferred follow-ups. The body lists four: the Ohio flag reuse, monthly eligibility, a QSEHRA input, and splitting the Schedule A exclusion between medical and long-term care premiums. Consider opening issues so they don't live only in the PR body.

Verified

  • The dependent fix is pinned term by term (mutation).

    • filer & on the ESI and offer flags (line 64): YAML cases 136, 202 and 235, and the grid's dependent with has_esi set.
    • The dependent's own employer_pays: case 268 expects the child to be eligible.
    • & filer in filer_own (line 72): if it is dropped, case 268's parent becomes eligible, and the case expects false.
    • filer & in the return (line 73): case 172 expects the child to be clear while the parent is barred.
    • ALL: case 301, which the round 1 hub review found missing (its M4 survived the YAML). The body's M6 is now killed by that case.
  • Hand checks for every new or changed expectation (2025). For profit of $50,000, half of SE tax = 50,000 × 0.9235 × 0.153 / 2 = 3,532.3875, so the limit is 46,467.6125. For the child's $3,000, half of SE tax = 3,000 × 0.9235 × 0.153 / 2 = 211.94325, so the limit is 2,788.05675.

    1. Case 136: the child is not a filer and their contribution is NONE, so eligibility is [false, false, false]. The base is [6,000, 0, 0]: the spouse and child are not self-employed (defined_for). The tax-unit deduction is min(6,000, 46,467.61) = 6,000.
    2. Case 172: the expectation is unchanged; the new comment's arithmetic is right. Deduction [0, min(1,000, 2,788.06) = 1,000].
    3. Case 202, child's offer flag: [true, false], base [0, 1,000], deduction [0, 1,000].
    4. Case 235, child's ESI flag: the same as case 202.
    5. Case 268, child's SOME: the head has no flags, and the child's own isn't a filer's, so the head is false and the child true. Base [0, 0] and deduction [0, 0].
    6. Case 301, ALL: eligible true, base 0, deduction min(0, 46,467.61) = 0.
    7. Case 317, long-term care: eligible [true, true] and line 1 [0, 0]. Line 2 = min(500, the 2025 limit of $900 for ages 41–50) = 500. Per-person deduction [min(0 + 500, 46,467.61) = 500, 0], and the tax-unit deduction is 500. The long-term care part is min(500, 500 − min(0, 500)) = 500. AGI = 90,000 − 3,532.3875 − 500 = 85,967.61. itemized_medical_expenses = 6,000 − min(6,000, 500) + (500 − min(500, 500 − 500)) = 6,000.
    8. Grid: the dependent is aged 19, self-employed, with $50,000 of profit and $1,000 of premiums. With no flags or with has_esi set, the expected base is 1,000 either way. The head's base is 0 exactly when the head or spouse is eligible. The twins' tax-unit deduction is 6,000, because tax_unit_non_dep_sum leaves out the dependent. The grid comment's 3,532.3875 and 46,467.6125 are right.
  • Long-term care is handled consistently with 162(l)(2)(B)(i)–(ii). The bar stays on line 1, and line 2 still enters the deduction. The documentation says so (lines 36-39), and so does the body's "Not modeled" section.

  • Schedule A split. itemized_medical_expenses_excluded_premiums takes medical premiums first, even when a barred filer's deduction took only long-term care premiums. The body lists this as a follow-up. I checked every reader of the split on the merged tree:

    • Montana 2021–2023: lines 2–3 and the line 1 subtotal.
    • New Jersey: dependents' exclusion, bounded by deduction minus the long-term care part.
    • Ohio, Idaho and Michigan: the per-person long-term care part.

    Each either uses the per-person long-term care part or deducts both kinds of premium in full. A barred filer's totals are therefore right; only the medical and long-term care labels shift. This is not a finding.

  • References. The added eCFR link (26 CFR 1.36B-2(c)(3)(i)(A)) is a single source with a paragraph anchor, and it supports the "related individual" statement. The other references are unchanged from round 1.

  • Main-side guards on the merged tree.

  • Python test cost. It is reported as CLAUDE.md asks, with local and CI figures kept apart. The file is still the only Python addition.

  • Partner tests and changelog. No partner test file changed. The changelog fragment is unchanged, top-level, type fixed.

Axiom

axiom: us/statute/26/162/l queued (TheAxiomFoundation/rulespec-us#1676; (l)(2)(B) addendum with monthly companion tests: …).

  • The issue. rulespec-us#1676 is OPEN and labelled pe-parity. It is dispatch-ready: module path, corpus citation, verbatim (l)(2)(B) with (i) and (ii), required outputs, companion tests H–K from Form 7206, its instructions and the 1040 instructions, and a recorded blocker.
  • The 2026-11-05 Count eligible long-term care premiums in Schedule A medical expenses #10265 extension correctly says this PR bars line 1 only.
  • What is stale. The addendum's description of PE's dependent rule and test J's case name no longer match the head (should-address 2).

CI

36/36 checks pass at b184b86cda, re-checked just before this report. The body reports that run 42150367766 ran the renamed YAML in Baseline (irs), 14 passed, and the property file in Rest Python shard 4, 7 passed. Nothing is pending or failing.

Branch

Verdict: REQUEST_CHANGES

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

Round 2: the round 1 should-address item and suggestions 1-5 are fixed. Two new should-address items: #10120 has merged, so this PR lands second and needs the own-business plan documentation the body describes, and rulespec-us#1676 needs an update for the new dependent rule. Details in the review comment above.

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.

3 participants