Skip to content

Count the owners' draw in the SEHI limit's line 5 under the owners' draw reform - #10571

Merged
MaxGhenis merged 7 commits into
mainfrom
owners-draw-sehi-line5
Nov 3, 2026
Merged

MaxGhenis merged 7 commits into
mainfrom
owners-draw-sehi-line5

Conversation

@MaxGhenis

@MaxGhenis MaxGhenis commented Nov 1, 2026 •

Copy link
Copy Markdown
Contributor

With the owners' draw reform on, the self-employed health insurance earned income limit now counts in Form 7206 line 5 the draw profits whose SECA enters the deductible SE tax, so the plan business's share of that tax no longer absorbs the draw's SECA.

The interaction

self_employed_health_insurance_earned_income_limit (#10109) follows Form 7206 lines 4–13:

  • line 4 holds the Schedule C profit (total_self_employment_income);
  • line 5 sums the net profit of every business subject to SE tax (adding farm income and partnership SE earnings);
  • line 6 takes the plan business's share of line 5;
  • lines 7–8 multiply self_employment_tax_ald_person by that share.

The owners' draw reform (#10483) overrides self_employment_tax_ald_person, so with the reform on (and half_seca_deduction at its default, true), line 7 includes the draw's SECA. Line 5 left out the draw's profits, so the Schedule C business took too large a share of that tax and the limit fell.

Hand check

Single filer, 2027, synthetic $200,000 caps:

  • Inputs: $40,000 Schedule C; S-corporation profit $64,970.22, which gives draw net earnings 64,970.22 × 0.9235 = $59,999.998; $8,000 of premiums.
  • SE tax: Schedule C 40,000 × 0.9235 × 15.3% = 5,651.82, plus draw OASDI 59,999.998 × 12.4% = 7,440.00 (HI on the draw defaults off). Deductible half: 6,545.91.
  • Main: line 5 = 40,000, share 1, limit 40,000 − 6,545.91 = 33,454.09.
  • This PR: line 5 = 40,000 + 64,970.22 = 104,970.22, share 0.3810605, line 7 = 2,494.39, limit 37,505.61.
  • The $8,000 deduction doesn't change, because both limits exceed the premiums. A second case with $36,000 of premiums moves the deduction from $33,454.09 to $36,000.

The change

policyengine_us/reforms/ssa/owners_draw.py installs an override of the limit variable:

  • With the reform off for the year, it returns the baseline formula.
  • With it on, it repeats the baseline's lines 4–13 and adds where(half_seca_deduction, owners_draw_niit_exclusion, 0) to line 5.

The reform computes owners_draw_niit_exclusion, its "gross owners' profits subject to SECA", as owners_draw_oasdi_base divided by the net-earnings factor (q by default) with HI off, and as the whole owners_draw_gross with include_hi on. Line 5 counts only these, and only when half_seca_deduction puts their SECA into self_employment_tax_ald_person (line 7). An earlier head of this PR added the whole owners_draw_gross, which raised the limit above main in three setups where line 7 holds none or only part of the draw's SECA:

2027, $40,000 Schedule C, $64,970.22 S-corp profit, $200,000 caps Line 7 Main Whole gross draw This PR
half_seca_deduction: false 2,825.91 37,174.09 38,923.16 37,174.09
Wages $300,000, HI off: the draw bears no SECA 535.63 39,464.37 39,795.89 39,464.37
Wages $150,000, HI off: draw OASDI base 13,060 of 60,000 (gross 14,141.85) 3,635.63 36,364.37 38,614.61 37,314.00

Form 7206 line 5 counts the net profit of every business subject to SE tax, and a real business above the wage base counts all of its profit. The draw becomes SE income only through this reform, so we chose to count in line 5 the part of it whose SECA line 7 carries. Line 6 then splits the deductible tax at matching rates per dollar of profit: with HI on and no OASDI room, the limit equals line 4 less Schedule C's own deductible tax (39,464.37).

We don't change the baseline variable file.

Line 5 sums net profits before the 0.9235 net-earnings factor, so the override counts gross profits, not net earnings: line 4's Schedule C profit and the K-1 partnership SE amount both come before the factor. owners_draw_niit_exclusion holds a gross profit amount (the OASDI base divided back by q), and owners_draw_net_earnings holds gross × 0.9235, the Schedule SE line 4a analogue. Using net earnings would give share 0.4 and a limit of 37,381.64 in the hand check.

We added a section on this override to docs/contributed_reforms/owners_draw.md, after the cap-priority section.

Invariants (tested with Hypothesis, plus explicit examples)

  1. With the reform disabled, the reform's limit equals the baseline formula on generated vectors: Schedule C, farm, partnership SE, S-corp profit, wages and pension deduction, signed where the model allows.
  2. With it enabled, the limit equals a lines 4–13 oracle, max(0, line4 − ALD × line4 / line5 − pension). The oracle derives line 5's draw term from the rule rather than from the reform's own variables: with HI on, the gross draw; with HI off, min(gross × q, room) / q, where q comes from the rates and room from the cap less taxable wages and existing capped SE earnings (or no limit with room_cap_basis 1); zero when half_seca_deduction is off. The generated inputs vary the rates, include_hi, half_seca_deduction, room_cap_basis, phase_in_share and the cap, with the hand case, the three setups above, HI on with the deduction off and $300,000 of wages, HI on with $300,000 of wages, HI on with $150,000 of wages, and a half phase-in with HI off as explicit examples.
  3. Against main's formula run separately on the same SE tax, the limit never falls below main's, and it equals main's whenever the draw term is zero (no draw, no deductible draw SECA, or no OASDI room with HI off).

The earlier upper bounds (line 4 less line 8, and line 4 less the pension deduction) followed from the oracle's equality, so the tests drop them.

The invariant "never exceeds line 4 less the plan business's own deductible SE tax" fails under Form 7206's pro-rata rule. In the hand check, the Schedule C business's own deductible SE tax comes to 2,825.91, which puts that bound at 37,174.09, while the limit reaches 37,505.61. Line 6 allocates the total deductible tax by profit share. The draw bears 12.4% × 0.9235 per dollar of profit, against Schedule C's 15.3% × 0.9235, so its profit share exceeds its tax share. The tests use the line 6 share instead.

Readers of the deductible SE tax or SE profit shares

Reader What it does Finding
self_employed_health_insurance_earned_income_limit Form 7206 line 6 share of SE tax ALD Fixed here
qualified_business_income (QBID) Prorates self_employment_tax_ald_person (via qbi.deduction_definition) across SSTB and non-SSTB QBI; qbi.income_definition includes partnership_s_corp_income Consistent: the pool that bears the draw's SECA deduction includes its source income
self_employed_pension_contribution_ald_person (SEP/pension) Caps contributions at Schedule C profit; doesn't read SE tax No inconsistency of this kind (whether the draw supports plan contributions raises a separate question)
eitc_earned_income Wages + Schedule C + farm + partnership SE − SE tax ALD Same kind: subtracts the draw's SECA deduction from earned income that excludes the draw
ca_eitc_earned_income Same structure as federal Same kind
ira_compensation SE earnings − SE tax ALD − pension Same kind
standard_deduction_worksheet_earned_income_person Earned-income sources − SE tax ALD Same kind (dependents' standard deduction)
nd_mpc Qualified income (wages, Schedule C, pensions) − SE tax ALD Same kind
mn_marriage_credit Income sources (wages, Schedule C, SS, pensions) − SE tax ALD Same kind
sc_gross_earned_income (two-wage-earner credit) earned_income (wages, Schedule C) − subtractions including SE tax ALD Same kind
self_employment_tax_ald, the ALD lists, dependent_taxable_ss_magi, student-loan MAGI, MO AGI, IA adjustments, MI/MA/AL lists Deduction in AGI-type totals Consistent: an above-the-line deduction of tax actually owed
CTC refundable social_security.add Sums payroll/SE taxes paid Consistent

Other overrides in the reform:

  • self_employment_social_security_tax and self_employment_medicare_tax add the draw's own taxes.
  • ss_aime adds credited draw earnings.

Neither applies an SE profit share. We leave the earned-income readers marked "same kind" for a follow-up. Treating the draw's net earnings as SE earnings under those definitions takes a larger policy decision than this fix.

Tests

  • policyengine_us/tests/policy/contrib/ssa/owners_draw_self_employed_health_insurance_earned_income_limit.yaml: 11 cases. They are the hand check, premiums above the old limit, no draw, no Schedule C, the three setups in the table above, and four more:

    • HI on with nondeductible draw SECA and $300,000 of wages: limit 39,464.37 (line 5 holds 40,000; counting the draw would give 39,795.89).
    • HI on with no OASDI room ($300,000 of wages): owners_draw_niit_exclusion 64,970.22, line 7 1,405.63, limit 39,464.37 (main 38,594.37).
    • HI on with 13,060 of OASDI room ($150,000 of wages): owners_draw_niit_exclusion 64,970.22, line 7 1,716.92, limit 38,283.08 (counting only the OASDI-taxed 14,141.85 of draw would give 36,671.24; main 35,494.37).
    • phase_in_share 0.5 with HI off: gross draw 32,485.11, OASDI base 30,000, line 7 4,685.91, limit 37,414.14 (main 35,314.09; the whole S-corp profit in line 5 would give 38,214.38).
  • policyengine_us/tests/reforms/test_owners_draw.py:

    • The two Hypothesis properties above. The generated SEHI test runs an explicit @example for each YAML case with both a draw and a Schedule C business. Those cases include three with HI on (the deduction off, no OASDI room, and some OASDI room) and a half phase-in with HI off.
    • BASELINE_VARIABLES includes the baseline limit, so the existing disabled-equality tests, including the generated real-Simulation one, also compare it.
    • The pure harness stores integer money inputs as floats, as Core does.
    • The whole file: 38 passed in 188 s at bed691b7ff, run in its own process.
  • policyengine_us/tests/code_health/test_source_conflicts.py: 103 passed.

  • Mutants on the line 5 draw term at owners_draw.py:304-305, each in its own process, with the code restored after each:

    Mutant YAML (10 cases) test_owners_draw.py -k "sehi or disabled" (5 tests)
    M6: gate on half_seca_deduction | include_hi 1 failed: "With HI on, nondeductible draw SECA still keeps the draw out of line 5" (39,795.90 vs 39,464.37) test_generated_sehi_limit_counts_seca_taxed_draw_in_line_5 failed
    Whole s_corp_income in place of the draw term 3 failed: above the OASDI cap with HI off (39,795.90), partly within the cap (38,614.61), half phase-in (38,214.39) same test failed
    Gate on include_hi in place of half_seca_deduction 5 failed: the hand check, premiums above the old limit, partly within the cap, HI on with nondeductible SECA, half phase-in same test failed

    The tests killed the round-1 mutants (ignore the gate, whole owners_draw_gross) at an earlier head, and the code they mutate hasn't changed.

Overlap

impact: none at baseline. create_structural_reforms_from_parameters calls create_owners_draw_reform (reforms.py:372), which returns the reform only when some gov.contrib.ssa.owners_draw.in_effect value is true and otherwise None, which reforms.py:754 filters out; in_effect defaults to false.

axiom: n/a: contributed reform

CI: Pull request run 41537180716 at bed691b7ff completed with conclusion success (35/35 jobs succeeded; Documentation run 41537180467 also succeeded).

🤖 Generated with Claude Code

With the owners' draw reform on, the deductible SE tax on Form 7206 line 7
includes the draw's SECA, but line 5 left out the draw's profits, so the
Schedule C plan business took too large a share of that tax and the limit
fell (40,000 Schedule C with a 60,000 net-earnings draw: 33,454.09 instead
of 37,505.61). The reform now overrides the limit, adding the draw's gross
profit to line 5 when the reform is in effect; the baseline formula is
untouched.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Disabled, the reform's limit must equal the baseline formula; enabled, it
must match an independent lines 4-13 oracle with the draw's gross profit in
line 5, sit at or above main's draw-free result, and never exceed line 4
less the line 6 share of deductible SE tax. The pure harness now stores
integer money inputs as floats, as Core does.

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

Copy link
Copy Markdown
Contributor Author

Ready for hub review

Head: 30fd2d7f6ac30f225b4bcf52c8a3990d1955d47d (branched from main 469ad12cb9, which includes #10483 and #10109).

What the item asked and how it's resolved

  • Interaction verified before changing anything. With the reform on, line 7 uses the reform's self_employment_tax_ald_person, which includes the draw's SECA when half_seca_deduction is true (the default). Line 5 didn't include the draw. Hand check (also in the body): main gives 33,454.09; Form 7206 logic with the draw in line 5 gives 37,505.61. The $8,000 deduction is unchanged, because the limit isn't binding at $8,000; a $36,000-premium case moves the deduction from 33,454.09 to 36,000.
  • Fix: a reform override of the limit (policyengine_us/reforms/ssa/owners_draw.py:290, registered at :359). When the reform is off for the year it returns the baseline formula. When on, it adds max_(0, owners_draw_gross) to line 5. The baseline variable file is untouched. Changelog: changelog.d/owners-draw-sehi-line5.fixed.md.
  • Reader check: the table is in the PR body. EITC, CA EITC, IRA compensation, the dependent standard-deduction earned income, ND MPC, MN marriage credit and SC two-wage-earner earned income all subtract the deductible SE tax, which now includes the draw's SECA, from an earnings measure that leaves out the draw. QBID, the ALD and AGI lists, and the CTC payroll-tax add-back are consistent. The pension deduction doesn't read SE tax.

Judgment calls

  1. Line 5 uses owners_draw_gross, not owners_draw_net_earnings. Form 7206 line 5 sums net profits before the 0.9235 factor, like line 4's Schedule C profit and the K-1 partnership SE amount. The draw's matching figure is its gross; net earnings is gross × 0.9235. In the hand case, net earnings would give share 0.4 and a limit of 37,381.64. Both differ from main by more than 3,900. The YAML pins gross.
  2. The brief's second invariant is false as first worded. Read as "line 4 less the plan business's own deductible SE tax", the bound is 40,000 − 2,825.91 = 37,174.09, but Form 7206's pro-rata result is 37,505.61. Line 6 allocates by profit share, and the draw bears 12.4% × 0.9235 per dollar of profit against Schedule C's 15.3% × 0.9235. The Hypothesis test instead asserts four things:
    • the limit never exceeds line 4 less the line 6 share of deductible SE tax;
    • it is never below main's draw-free result;
    • it is never above line 4 less the pension deduction;
    • it equals an independent lines 4–13 oracle.
  3. Test harness change. The pure harness in test_owners_draw.py now converts integer inputs to float, as Core stores them. Without that, np.zeros_like produced an integer output array and the limit's division raised on an integer probe in test_generated_disabled_reform_matches_public_baseline.

Tests (one file per process)

File Result
policyengine_us/tests/policy/contrib/ssa/owners_draw_self_employed_health_insurance_earned_income_limit.yaml 4 passed: hand check, binding premiums, no draw (unchanged, 37,174.09), no Schedule C (0)
policyengine_us/tests/reforms/test_owners_draw.py 38 passed, 143 s. Includes test_generated_disabled_sehi_limit_equals_baseline (line 748), test_generated_sehi_limit_counts_the_draw_in_line_5 (line 759), and the existing disabled-equality tests, which now also compare the limit, including the generated real-Simulation test

Mutants (each in its own process; restored and checked with cmp)

  • Draw dropped from line 5: killed by test_generated_sehi_limit_counts_the_draw_in_line_5 and by the 2 YAML cases with a draw and a Schedule C.
  • Draw added twice: killed by the same tests.

CI

Draft, so CI hasn't run. No microsimulation was run; the hub confirms impact: none at baseline (reform off by default).

Proposed follow-up

  • Treat owners' draw net earnings as SE earnings in earned-income definitions under the reform. EITC, CA EITC, IRA compensation, dependent standard-deduction earned income, ND MPC, MN marriage credit and SC two-wage-earner income subtract the draw's deductible SECA from earnings that leave out the draw. Decide whether the reform's draw counts as net earnings from self-employment for these definitions, and include it or exclude its SECA consistently.

Generated by Claude Code

@MaxGhenis
MaxGhenis marked this pull request as ready for review November 1, 2026 19:39
@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

Line 5 apportions line 7's deductible SE tax by business profits. Adding
the whole gross draw raised the limit above main when the draw's SECA was
not in line 7: with half_seca_deduction off, and for draw profits beyond
the OASDI room with HI off. Line 5 now adds owners_draw_niit_exclusion,
the gross profits that bear SECA, only when half_seca_deduction is on.

YAML cases cover the three setups, the generated test varies
half_seca_deduction, the cap, room_cap_basis and phase_in_share with an
oracle that derives the draw term from the rule, and the docs describe
the override.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LwQq6H6zhX5KYCDLztuSH4
@MaxGhenis

MaxGhenis commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor Author

Ready for hub review

Head: 8d96db39acbf1129f20c036d9dab0c8b17998565 (one commit on 30fd2d7f; nobody else pushed in between).

Findings

P2-1, line 5 counts draw profits whose SECA isn't in line 7. Fixed in policyengine_us/reforms/ssa/owners_draw.py:302-315. Line 5 now adds where(draw.half_seca_deduction, owners_draw_niit_exclusion, 0), where draw = parameters(period).gov.contrib.ssa.owners_draw. Before using them I read owners_draw_niit_exclusion (owners_draw.py:225-241: owners_draw_oasdi_base / owners_draw_net_earnings_factor with HI off, owners_draw_gross with HI on, 0 when disabled) and half_seca_deduction.yaml (default true). I rewrote the comment at :294-301 to describe what the code does now.

P2-2, nothing tests these setups.

  • YAML: three cases in owners_draw_self_employed_health_insurance_earned_income_limit.yaml, each asserting the limit. They also assert the intermediate draw variables.
  • Hypothesis: test_generated_sehi_limit_counts_seca_taxed_draw_in_line_5 (test_owners_draw.py:795; its policy strategy and examples start at :757) now varies half_seca_deduction, the cap (1 to 400,000, applied to both the payroll cap and current_law_cap), room_cap_basis, phase_in_share and include_hi, along with the rates. It has the hand case and all three witnesses as @examples.
  • The oracle builds line 5's draw term from the rule, not from owners_draw_niit_exclusion. With HI on it is the gross draw. With HI off it is min(gross × q, room) / q, where q comes from the rates and room = max(0, cap − taxable_earnings_for_social_security − social_security_taxable_self_employment_income), or infinity with room_cap_basis 1. It is zero when half_seca_deduction is off.

P3 items.

Hand values

All five setups use 2027, $40,000 Schedule C, $64,970.22 S-corp profit, $200,000 caps and q = 0.9235. They match the review's table.

Case Line 7 Main Old head This head
half_seca_deduction: false 2,825.91 37,174.09 38,923.16 37,174.09
Wages $300k, HI off 535.63 39,464.37 39,795.89 39,464.37
Wages $150k, HI off 3,635.63 36,364.37 38,614.61 37,314.00

How the $150k case works out:

  • The draw's OASDI room is 200,000 − 150,000 − 36,940 = 13,060, so the draw's OASDI tax is 1,619.44.
  • The draw term is 13,060 / 0.9235 = 14,141.85, so line 5 is 54,141.85.
  • The limit is 40,000 − 3,635.63 × 40,000 / 54,141.85 = 37,313.997.

The existing hand case (37,505.61) and the four existing YAML values are unchanged.

Tests (one file per process)

  • uv run pytest -q -p no:cacheprovider policyengine_us/tests/reforms/test_owners_draw.py: 38 passed in 179 s.
  • policyengine_core ... test owners_draw_self_employed_health_insurance_earned_income_limit.yaml -c policyengine_us: 7 passed.
  • make format and ruff check are clean.

Mutants (each applied, run and reverted)

  • Gate on draw.include_hi instead of draw.half_seca_deduction: killed by test_generated_sehi_limit_counts_seca_taxed_draw_in_line_5 and by 3 of the 7 YAML cases. Those are the cases with a nonzero draw term at defaults: the hand case, premiums above the old limit, and the partial-room case.
  • Ignore the gate (True): killed by the same generated test.
  • The old whole-owners_draw_gross term: killed by the same generated test.

CI

Pull request run https://github.com/PolicyEngine/policyengine-us/actions/runs/41397745379 was still in_progress 60 minutes after the push. At that point every job had succeeded except Contrib tests / Full Suite - Contrib (other-shard-1), which was still running. Documentation passed.

Judgment calls

Follow-up

Owners' draw: count the draw in earned-income readers that subtract SE tax ALD. eitc_earned_income, ira_compensation and the other readers marked "same kind" in the PR body subtract the draw's SECA deduction from earned income that excludes the draw. Deciding whether the draw is earned income there is a policy call, separate from this fix.


Generated by Claude Code

@MaxGhenis

Copy link
Copy Markdown
Contributor Author

Ready for hub review
VERDICT: REQUEST_CHANGES
8d96db3
METHODOLOGY_CHOICE: yes

Independent round-2 review. The head hasn't moved past 8d96db39ac. I found no **Hub review in progress** comment from an earlier session after 2026-11-02T17:55:30Z, so this review starts from the beginning.

Summary

The code is right. I hand-computed ten setups from the Form 7206 rule before reading the code, including the two this round asks about (phase_in_share 0.5, and HI on with the OASDI room used up). The real model matches every value. Both r1 P2s are fixed in the code. The defect reproduces at main, and the baseline can't move.

The one blocking gap is in the tests. Neither test file pins the limit with HI on and half_seca_deduction false, or with phase_in_share between 0 and 1 and HI off. The generated test spends its random examples almost entirely on one combination. A plausible mutant in exactly that gap survives every test in both files: it counts the whole draw in line 5 whenever HI is on. The fix is three YAML cases (given below, already run at this head) plus matching @examples. There are also two small docs fixes.

Findings

P2-1: no test pins the limit with HI on and half_seca_deduction false, or with a partial phase-in and HI off, and a mutant survives there.

  • Where: policyengine_us/tests/reforms/test_owners_draw.py:756-793 (_SEHI_POLICY and the @examples) and policyengine_us/tests/policy/contrib/ssa/owners_draw_self_employed_health_insurance_earned_income_limit.yaml. All seven YAML cases use phase_in_share: 1 and HI off.

  • Coverage probe. I called the real test_generated_sehi_limit_counts_seca_taxed_draw_in_line_5 with a wrapper around _calculate that records each row with a positive draw and a positive Schedule C. That gave 44 examples and 35 such rows:

    Rows phase_in_share include_hi half_seca_deduction Draw's OASDI room
    28 0.5 true true none
    1 0.5 false false partial
    1 each 1 false true full / partial / none (the @examples)
    1 1 false false full (the @example)
    2 1 false true full, room_cap_basis 1

    It never reaches HI on with half_seca_deduction false, HI on with any OASDI room left, or share 0.5 with HI off and the deduction on.

  • Mutant M6 survives. Changing owners_draw.py:304 from draw.half_seca_deduction, to draw.half_seca_deduction | draw.include_hi, means "with HI on, the whole draw bears SECA, so count it." That passes test_owners_draw.py (38 passed) and the PR's YAML (7 passed). My 6-case witness file, run against M6, gives 1 failed, 5 passed. Only one of its cases has HI on and the deduction off, the one setup M6 changes. By hand, M6 gives 39,795.89 there instead of 39,464.37: line 7 holds none of the draw's SECA, but line 5 counts all of its profit.

  • Fix. Add these three cases to the PR's YAML file. I ran them at this head, and they pass (part of the 6-case witness file below). Then add matching @examples with include_hi=True, half_seca_deduction=False and phase_in_share=0.5. A st.sampled_from that gives each combination an @example would avoid relying on the random draw.

- name: With HI on, nondeductible draw SECA still keeps the draw out of line 5
  period: 2027
  absolute_error_margin: 0.02
  reforms: policyengine_us.reforms.ssa.owners_draw.owners_draw
  input:
    gov.contrib.ssa.owners_draw.current_law_cap: 200_000
    gov.contrib.ssa.owners_draw.in_effect: true
    gov.contrib.ssa.owners_draw.phase_in_share: 1
    gov.contrib.ssa.owners_draw.include_hi: true
    gov.contrib.ssa.owners_draw.half_seca_deduction: false
    gov.irs.payroll.social_security.cap: 200_000
    gov.simulation.aime_lookback_years: 0
    payroll_tax_gross_wages: 300_000
    self_employment_income: 40_000
    s_corp_income: 64_970.22
  output:
    # 59,999.998 x 2.9% = 1,740.00 of draw HI, none of it deductible.
    owners_draw_hi_tax: 1_740
    # Schedule C HI only: 36,940 x 2.9% / 2 = 535.63.
    self_employment_tax_ald_person: 535.63
    # Line 5 is 40,000: 40,000 - 535.63. Counting the draw would give 39,795.89.
    self_employed_health_insurance_earned_income_limit: 39_464.37

- name: With HI on and no OASDI room, the whole gross draw is in line 5
  period: 2027
  absolute_error_margin: 0.02
  reforms: policyengine_us.reforms.ssa.owners_draw.owners_draw
  input:
    gov.contrib.ssa.owners_draw.current_law_cap: 200_000
    gov.contrib.ssa.owners_draw.in_effect: true
    gov.contrib.ssa.owners_draw.phase_in_share: 1
    gov.contrib.ssa.owners_draw.include_hi: true
    gov.irs.payroll.social_security.cap: 200_000
    gov.simulation.aime_lookback_years: 0
    payroll_tax_gross_wages: 300_000
    self_employment_income: 40_000
    s_corp_income: 64_970.22
  output:
    owners_draw_oasdi_base: 0
    owners_draw_hi_tax: 1_740
    owners_draw_niit_exclusion: 64_970.22
    # (1,071.26 + 1,740.00) / 2 = 1,405.63.
    self_employment_tax_ald_person: 1_405.63
    # Line 6: 40,000 / 104,970.22 = 0.3810605; 40,000 - 535.63 = 39,464.37.
    # Main (draw left out of line 5) gives 38,594.37.
    self_employed_health_insurance_earned_income_limit: 39_464.37

- name: A half phase-in counts the same half in line 5 as line 7 taxes
  period: 2027
  absolute_error_margin: 0.02
  reforms: policyengine_us.reforms.ssa.owners_draw.owners_draw
  input:
    gov.contrib.ssa.owners_draw.current_law_cap: 200_000
    gov.contrib.ssa.owners_draw.in_effect: true
    gov.contrib.ssa.owners_draw.phase_in_share: 0.5
    gov.irs.payroll.social_security.cap: 200_000
    gov.simulation.aime_lookback_years: 0
    self_employment_income: 40_000
    s_corp_income: 64_970.22
  output:
    owners_draw_gross: 32_485.11
    owners_draw_oasdi_base: 30_000
    owners_draw_niit_exclusion: 32_485.11
    # (5,651.82 + 30,000 x 12.4%) / 2 = 4,685.91.
    self_employment_tax_ald_person: 4_685.91
    # Line 5: 72,485.11; line 6: 0.5518375; 40,000 - 2,585.86 = 37,414.14.
    # Main gives 35,314.09; the whole S-corp profit in line 5 would give 38,214.38.
    self_employed_health_insurance_earned_income_limit: 37_414.14

P3-1: the new docs section takes over a paragraph about the cap. docs/contributed_reforms/owners_draw.md:99-110.

  • The section went in above the paragraph at :112 ("For the current-law cap basis, the taxed draw is nonnegative … The 1 cap option intentionally permits earnings above C."). That paragraph closes the "Gross draw, adjusted earnings, and cap priority" section. It now reads as part of "Self-employed health insurance limit", which it has nothing to do with.
  • Fix: move lines 99-111 (the heading through the blank line after "Disabled years use the baseline formula.") to just before ## Benefit credit and active NIIT interaction (:120).
  • Nit, same section: "draw_oasdi_base / q" is right only when net_earnings_factor is 0. The code divides by owners_draw_net_earnings_factor, which follows the explicit override when one is set. The NIIT section at :135 uses the same shorthand, so either both should say "divided by the net-earnings factor (q by default)" or neither.

P3-2: the hand-off's CI line is stale. It says run 41397745379 was still in_progress. It has now finished, and all 34 jobs on 8d96db39ac succeeded, none cancelled or skipped (Pull request run 41397745379; Documentation 41397745317). The body's "check its Pull request run before landing" is accurate. No change is needed beyond knowing this.

r1 findings at this head

r1 Status Witness I ran
P2-1 line 5 counts draw profits whose SECA isn't in line 7 Resolved Real model, PR YAML at head: half_seca_deduction: false 37,174.09; wages $300k, HI off 39,464.37; wages $150k, HI off 37,314.00 (7 passed). The same three at main give 37,174.09, 39,464.37 and 36,364.37. My hand script gives identical values, and 38,923.16 / 39,795.89 / 38,614.61 for the old whole-gross term.
P2-2 nothing tests these setups; gate mutant survives Resolved for HI off, open for HI on and partial phase-in (P2-1 above) M2 (ignore the gate) and M1 are now killed. M6 survives.
P3-1 copied formula can drift; overlap Guarded M7 (unfloor farm in the baseline file, standing in for #10120/#10131) is killed by test_generated_sehi_limit_counts_seca_taxed_draw_in_line_5's check against main. #10560 now merges with this PR without conflict (git merge-tree exit 0), and the merged owners_draw.py keeps _enabled and owners_draw_niit_exclusion. #10120 and #10131 conflict only in files this PR doesn't touch; they already conflict with main.
P3-2 docs not updated Resolved, with P3-1 above
P3-3 stale body; footer; redundant bounds Resolved The body ends with the required line and no longer says draft/no CI. The redundant bounds are gone.

The questions this round asked

phase_in_share 0.5. Line 5's draw term and line 7's tax use the same share. owners_draw_gross is share × max(0, active income). Both owners_draw_net_earnings (line 7's tax base) and owners_draw_niit_exclusion (line 5's term) derive from it. Witness (real model, head): 2027, $40,000 Schedule C, $64,970.22 S-corp profit, $200,000 caps.

  • Gross 32,485.11 and OASDI base 30,000, so line 7's draw tax is 3,720 = 12.4% × 32,485.11 × 0.9235.
  • Line 5's term is 32,485.11, the same half.
  • The limit is 37,414.14 (main 35,314.09).
  • With wages of $150,000 and share 0.5, the term is 14,141.85 and the limit 37,314.00, the same as share 1, because the room binds either way.

So line 5 never counts more draw than line 7 taxes. Counting only the phased-in half, rather than the whole S-corp profit, is a methodology choice; see below.

HI on with OASDI room used up. Witness (wages $300,000, HI on): owners_draw_oasdi_base 0, owners_draw_hi_tax 1,740.00 = 2.9% × 59,999.998 (HI on all of the gross × q), and owners_draw_niit_exclusion 64,970.22 (all of the gross). self_employment_tax_ald_person is 1,405.63 = (1,071.26 + 1,740.00) / 2, so half of the draw's HI is deductible. Line 5 then counts the whole gross. The limit is 39,464.37, equal to line 4 less Schedule C's own deductible tax, as it should be: both businesses then bear 2.9% × 0.9235 per profit dollar. With HI on and the room free (wages 0), both bear 15.3% × 0.9235 and the limit is again exactly the own-tax figure, 37,174.09.

The oracle. It now derives line 5's draw term from the rule (test_owners_draw.py:812-830): gross with HI on, min(gross × q, room) / q with HI off, room from the cap less the baseline's capped bases, and zero with the deduction off. It never reads owners_draw_niit_exclusion, owners_draw_oasdi_base, owners_draw_oasdi_room or owners_draw_net_earnings. It still takes three things from upstream:

  • owners_draw_gross, so the phase-in and the $400 floor come from the code under test;
  • line 7 (self_employment_tax_ald_person);
  • q derived from the rates. The harness fixes net_earnings_factor at 0, so the override path isn't exercised here.

Those are covered by other tests in the file, so I don't count this as a finding. The two remaining checks against main's formula (:850-854) follow from the oracle's equality while the copy matches the baseline. They can fail, and do: M7, a change to the baseline file only, is caught by them, not by the oracle. They are the drift guard r1 asked for.

Is this a methodology choice? Yes. Form 7206 line 5 counts the net profits of businesses subject to SE tax, and a real business above the wage base still counts its whole profit. The PR instead counts only the draw profits whose SECA is in line 7, which makes line 6 allocate the deductible tax at matching per-dollar rates. The HI-on witnesses above show that this reproduces each business's own tax exactly when rates match. That choice has no statute behind it either way. I think it's the defensible reading for a parallel draw base, but the hub should own it.

Checked and sound

  • Defect at main. I ran the PR's YAML file with main's expected values (origin/main 4bfc548de3, worktree, PYTHONPATH checked) and got 7 passed: hand case 33,454.09, binding premiums 33,454.09, partial room 36,364.37, other cases unchanged. Main has no change to owners_draw.py, the limit variable, the docs or the test file since the merge base 469ad12cb9.

  • Hand values, computed in a standalone script from the rule (Schedule C SE tax, the draw's OASDI room after wages and Schedule C, line 5 = Schedule C + SECA-taxed gross draw if deductible) before reading the override.

    Setup Line 7 Draw term Limit Main
    Hand case 6,545.91 64,970.22 37,505.61 33,454.09
    half_seca_deduction false 2,825.91 0 37,174.09 37,174.09
    Wages $300k, HI off 535.63 0 39,464.37 39,464.37
    Wages $150k, HI off 3,635.63 14,141.85 37,314.00 36,364.37
    Share 0.5 4,685.91 32,485.11 37,414.14 35,314.09
    Share 0.5, wages $150k 3,635.63 14,141.85 37,314.00 36,364.37
    HI on, wages $300k 1,405.63 64,970.22 39,464.37 38,594.37
    HI on, wages $300k, deduction off 535.63 0 39,464.37 39,464.37
    HI on, wages 0 7,415.91 64,970.22 37,174.09 32,584.09
    Share 0 2,825.91 0 37,174.09 37,174.09

    Each matches the PR's YAML or my witness YAML run on the real model at this head (6 passed: share 0.5, share 0.5 with wages $150k, HI on with wages $300k with and without the deduction, HI on with wages 0, share 0).

  • The brief's own-tax invariant. Rejecting it is still right. The hand case's 37,505.61 is above line 4 less Schedule C's own deductible tax (37,174.09).

  • Tests at head, one file per process:

    • policyengine_core ... test owners_draw_self_employed_health_insurance_earned_income_limit.yaml -c policyengine_us: 7 passed;
    • pytest -q -p no:cacheprovider policyengine_us/tests/reforms/test_owners_draw.py: 38 passed (230 s).
  • Mutants, each in its own detached worktree and process. I ran pytest -k "sehi or disabled" (5 tests) and the PR's YAML, then removed the worktree.

    Mutant Killed by
    M1 gate on include_hi (hand-off) generated SEHI test; 3 YAML cases
    M2 no gate (hand-off) generated SEHI test; 1 YAML case
    M3 whole owners_draw_gross (hand-off) generated SEHI test; 2 YAML cases
    M4 mine: owners_draw_oasdi_base (net, not gross) generated SEHI test; 3 YAML cases
    M5 mine: farm loss unfloored in the override's line 5 generated SEHI test only
    M6 mine: count the whole draw whenever HI is on survives (38 passed, 7 passed); see P2-1
    M7 mine: baseline formula drifts (farm unfloored there) generated SEHI test's main check

    git status is clean, and the only worktrees left are the main checkout and the main comparison.

  • Coverage. _enabled is checked per period, and disabled years call baseline_sehi_limit.formula. The variable is per person, so filing status doesn't enter. abolish_self_emp_tax makes line 7 zero, so the draw term in line 5 then has no effect.

  • Readers.

    • The only reader of the limit is self_employed_health_insurance_ald_person (min(premiums, limit)). From there it reaches the SEHI ALD lists, QBI deduction_definition, dependent_taxable_ss_magi, self_employed_health_insurance_ald_excluded_premiums, and SC, MS, OH, MO and MT items. None treats it differently from main. Under the reform they all get a larger deduction where premiums bind, which is the intended correction.
    • owners_draw_niit_exclusion and half_seca_deduction are only read, not changed. Their other reader, s770_active_business_income, is unaffected.
    • The PR's table of earned-income readers that subtract the SE tax ALD matches my reading of r1's list. Those mismatches exist on main with the reform on, so they stay in the proposed follow-up.
  • Baseline. The override is installed only by the reform, and the reform is created only when an in_effect value is true (body's reforms.py:372/:754 claim). All 34 CI jobs on this head succeeded, including every baseline shard and Household API Partners. The diff can't move any baseline output, so the hub's reform-on microsim is the only population check needed. It should see the limit rise wherever premiums bind it for a Schedule C filer with a SECA-taxed draw.

  • Body lines. impact: and axiom: n/a: contributed reform are present. The docs sentences match the code, apart from P3-1: line 5 adds owners_draw_niit_exclusion only with half_seca_deduction true; with it false the reform's line 7 equals the baseline deduction, max(0, SE tax − draw taxes) × 0.5, so "follows the baseline formula" holds. Commits carry the required trailer.

  • Scope. Nothing under .github/ or tests/policy/baseline/partners/. Overlap search on the changed file names finds Add annual Social Security earnings histories and per-year reforms #10560, Let farm, partnership and S corporation plans set the self-employed health insurance limit #10120 and Let farm and partnership plans take the self-employed plan deduction #10131; see the r1 table.

What I couldn't check

  • Sources. The session's network policy blocks irs.gov, govinfo.gov, uscode.house.gov, ecfr.gov, law.cornell.edu and ssa.gov (proxy 403 / DNS refusal). I couldn't confirm Form 7206 line 5's wording or 26 U.S.C. 162(l)(2)(A) myself, and I quote neither. My hand rule follows the line structure the PR and the baseline variable's docstring describe, which r1 confirmed against the forms.
  • Population impact with the reform on: no microsimulation, as instructed.
  • The hand-off's own logs on the hub machine. I reran every test it cites instead.

Generated by Claude Code

@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

Bring the branch up to date with main before the round-2 test and docs fixes. No conflicts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The round-2 review found that no test reached HI on with half_seca_deduction
false, HI on with OASDI room used, or phase_in_share 0.5 with HI off, so a
mutant counting the whole draw whenever HI is on survived. Add the three
YAML cases and matching @examples so each setup is pinned.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The section sat above the paragraph that closes the cap-priority section.
Also say the OASDI base is divided by the net-earnings factor (q by
default), since an explicit net_earnings_factor overrides q.

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

Copy link
Copy Markdown
Contributor Author

Ready for hub review

Head: bed691b7ff86066710cc469a62c608abc309f447

Round-2 fix round on the review at #10571 (comment). owners_draw.py is unchanged.

Merge

Fast-forwarded to 8d96db39ac (no new commits since the item), then merged origin/main with a merge commit (a6d18eb5). No conflicts. Main touched nothing in owners_draw.py, the SEHI limit variable, the owners' draw docs or tests (its changes under reforms/ssa/ are trustees_2026.py and __init__.py).

Findings

1 (P2): HI on with the deduction off, and a half phase-in with HI off.

  • Added the review's three cases, as written, to policyengine_us/tests/policy/contrib/ssa/owners_draw_self_employed_health_insurance_earned_income_limit.yaml:163, :186 and :210.
  • Hand values, from the parameter files (12.4% / 2.9% SE rates, employer share 0.5, percent deductible 0.5, so q = 0.9235), cap $200,000:
    • HI on, deduction off, wages $300,000. No OASDI room. Schedule C HI 36,940 × 2.9% = 1,071.26. Draw net 64,970.22 × 0.9235 = 59,999.998, HI 1,740.00, none deductible. Line 7 = 1,071.26 / 2 = 535.63. Line 5 = 40,000, limit 39,464.37. Under M6, line 5 = 104,970.22, share 0.3810605, limit 39,795.89.
    • HI on, wages $300,000. Line 7 = (1,071.26 + 1,740.00) / 2 = 1,405.63. Line 5 = 104,970.22, line 8 = 1,405.63 × 0.3810605 = 535.63, limit 39,464.37 (main 38,594.37).
    • Share 0.5, HI off. Gross 32,485.11, net 30,000.00, room 163,060, so the OASDI base is 30,000 and the draw tax 3,720. Schedule C SE tax 36,940 × 15.3% = 5,651.82. Line 7 = 4,685.91. Line 5 = 72,485.11, share 0.5518375, line 8 = 2,585.86, limit 37,414.14 (main 35,314.09; whole S-corp profit 38,214.38).
    • My run of the real model at head agrees: 10 passed.
  • Added three @examples to test_generated_sehi_limit_counts_seca_taxed_draw_in_line_5 (policyengine_us/tests/reforms/test_owners_draw.py:795-813), following the existing rows:
    • $300,000 wages, include_hi=True, half_seca_deduction=False;
    • $300,000 wages, include_hi=True;
    • the hand case with phase_in_share=0.5 (HI off, deduction on).
    • I kept st.booleans() for the flags rather than switching to st.sampled_from. With these three, the explicit examples now cover every setup the YAML pins, and the change stays to @example rows only.

2 (P3): docs section placement. Moved "Self-employed health insurance limit" to just before ## Benefit credit and active NIIT interaction (docs/contributed_reforms/owners_draw.md:107). The cap paragraph ("For the current-law cap basis …", :100-105) now closes "Gross draw, adjusted earnings, and cap priority".

3 (P3): / q. I read owners_draw_net_earnings_factor (owners_draw.py:116-134): it returns net_earnings_factor when set, otherwise 1 − employer share × (OASDI + HI rates). owners_draw_niit_exclusion (:237-244) divides by that variable. Both doc spots now say "divided by the net-earnings factor (q by default)" (:114-115 and :136-137). I rewrapped the two edited paragraphs. The PR body's "The change" section had the same shorthand, so I changed it there too.

4: the body. Written with the PATCH call from a file and checked against GitHub's readback. Every line matches except the last: the write path replaced the closing "🤖 Generated with [Claude Code]" line with its own --- / "Generated by Claude Code" footer. I left that as it is.

  • The methodology paragraph is added after the owners_draw_niit_exclusion paragraph and its table. The 39,464.37 matches my run of the second new case.
  • The Tests section has this round's counts and mutant table, M6 included.
  • CI: names Pull request run 41537180716 at this head, success.
  • Kept the impact: line, axiom: n/a: contributed reform and the closing line. I also updated one sentence in invariant 2 to list the new explicit examples.

Tests at head (one file per process)

File Result
owners_draw_self_employed_health_insurance_earned_income_limit.yaml 10 passed (241 s)
policyengine_us/tests/reforms/test_owners_draw.py 38 passed (188 s; 4 min 19 s wall)
policyengine_us/tests/code_health/test_source_conflicts.py 103 passed
make format no changes; ruff check clean

Mutants

Each ran in its own process: the YAML file, then test_owners_draw.py -k "sehi or disabled" (5 tests). git checkout restored the code after each, and git status was clean.

Mutant (owners_draw.py:304-305) YAML pytest
(a) M6: draw.half_seca_deduction | draw.include_hi 1 failed: "With HI on, nondeductible draw SECA still keeps the draw out of line 5" (39,795.90 vs 39,464.37) test_generated_sehi_limit_counts_seca_taxed_draw_in_line_5 failed (1 failed, 4 passed)
(b) s_corp_income in place of owners_draw_niit_exclusion 3 failed: "A draw above the OASDI cap with HI off …" (39,795.90), "A draw partly within the OASDI cap …" (38,614.61), "A half phase-in …" (38,214.39, the comment's 38,214.38 within the 0.02 margin) same test failed
(c) gate on draw.include_hi 5 failed: hand check and premiums case (33,454.09), partly within the cap (36,364.37), HI on nondeductible (39,795.90), half phase-in (35,314.09) same test failed

CI

Pull request run 41537180716 at bed691b7ff: completed, success, 35/35 jobs succeeded, none cancelled or skipped. Documentation run 41537180467: success.

Judgment calls

  • Kept st.booleans() and added explicit @examples (see finding 1).
  • I applied the "(q by default)" wording to the PR body as well as the docs, since the body repeated the shorthand.
  • The new YAML comments compare against main and against the mutants. They don't use any source-conflict phrasing, and test_source_conflicts.py passes. I see this as a methodology choice for a contributed reform, not a conflict between sources, so it has no register entry.
  • Setup: uv sync alone doesn't install hypothesis, so I ran uv sync --extra dev before running the Python tests.
  • I didn't fetch Form 7206 or 26 U.S.C. 162(l) this round, and I quote neither. The methodology paragraph's account of line 5 is the brief's wording, and it matches the review's.

No new follow-up beyond the earned-income readers the body already lists.


Generated by Claude Code

@MaxGhenis

MaxGhenis commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor Author

Hub review in progress (superseded)

The round-3 review is complete. See #10571 (comment).


Generated by Claude Code

@MaxGhenis

Copy link
Copy Markdown
Contributor Author

Ready for hub review
VERDICT: APPROVE
bed691b
METHODOLOGY_CHOICE: yes

Independent round-3 review. The head hasn't moved past bed691b7ff. The range since round 2 is 8d96db39ac..bed691b7ff: the merge of main a6d18eb5 and two fix commits, 80f3fd9dff (tests) and bed691b7ff (docs).

Summary

Every round-2 finding is closed at this head. The merge adds nothing beyond main, owners_draw.py is unchanged, and I recomputed the three new YAML cases by hand. The defect reproduces at main. All three hand-off mutants are killed, and so is one of my two. The other (M9) survives every test in both files. It lives in a setup no test reaches: HI on with part of the draw inside the OASDI room. It is a coverage gap, not a wrong result, and the variable it reads is pinned elsewhere, so I rank it P3 and don't block on it. CI is green on every job.

Findings

P3-1: no test reaches HI on with OASDI room left, and mutant M9 survives there.

  • Where: policyengine_us/tests/policy/contrib/ssa/owners_draw_self_employed_health_insurance_earned_income_limit.yaml (both HI-on cases use $300,000 of wages, so there is no room) and the @example rows at policyengine_us/tests/reforms/test_owners_draw.py:795-813.

  • Coverage probe. I wrapped _calculate in a pytest plugin and ran test_generated_sehi_limit_counts_seca_taxed_draw_in_line_5. It made 47 examples: the 7 @examples plus 40 generated draws, which are fixed because of derandomize=True. Rows with a positive draw and a positive Schedule C, by (phase_in_share, include_hi, half_seca_deduction, room_cap_basis, the draw's OASDI room):

    (0.5, True,  True,  0, none)    28
    (1,   False, True,  1, full)     2
    (1,   False, True,  0, full)     1
    (1,   False, False, 0, full)     1
    (1,   False, True,  0, none)     1
    (1,   False, True,  0, partial)  1
    (1,   True,  False, 0, none)     1   <- new @example
    (1,   True,  True,  0, none)     1   <- new @example
    (0.5, False, True,  0, full)     1   <- new @example
    (0.5, False, False, 0, partial)  1
    

    So the answer to "can a generated draw now reach each setup" is no. The 40 generated draws are the same as in round 2, and each of the three new setups is reached once, by its @example. Keeping st.booleans() is fine for those three, since the @examples pin them. No example of any kind has HI on with full or partial room.

  • Mutant M9 (mine). At owners_draw.py:305 I replaced person("owners_draw_niit_exclusion", period), with
    where(draw.include_hi & (person("owners_draw_oasdi_base", period) > 0), person("owners_draw_oasdi_base", period) / person("owners_draw_net_earnings_factor", period), person("owners_draw_niit_exclusion", period)),
    That means "with HI on, count only the OASDI-taxed part whenever some room is left." Results:

    • the YAML file: 10 passed;
    • pytest -k "sehi or disabled": 5 passed;
    • the whole test_owners_draw.py: 38 passed.

    The witness below kills it: 1 failed, giving 36,671.24 against 38,283.08. At head it gives 1 passed.

  • Why P3, not P2. The HI-on branch of owners_draw_niit_exclusion is pinned elsewhere, with room left and with none: owners_draw_net_earnings.yaml, "HI taxes all adjusted draw earnings and makes M7 exclude the full actual gross draw", which has wages of $100,000 and $250,000. (test_vector_invariants_and_phase_monotonicity pins the HI-off branch.) So only an edit to this PR's three-line override that depends on the room can get past the tests, and M9 is built to do exactly that. The code is right in this setup. A mutant that makes the same mistake without depending on the room (the term becomes oasdi_base / q whenever HI is on) is caught by the existing HI-on case with no room.

  • Fix (optional). Add this case. I hand-computed it, then ran it at head (passes) and under M9 (fails). Also add the matching @example(rows=[(40_000, 0, 0, 64_970.22, 150_000, 0)], rates=(124, 29, 500), policy=_SEHI_WITNESS_POLICY | {"include_hi": True}).

- name: With HI on and part of the draw in the OASDI room, the whole gross draw is in line 5
  period: 2027
  absolute_error_margin: 0.02
  reforms: policyengine_us.reforms.ssa.owners_draw.owners_draw
  input:
    gov.contrib.ssa.owners_draw.current_law_cap: 200_000
    gov.contrib.ssa.owners_draw.in_effect: true
    gov.contrib.ssa.owners_draw.phase_in_share: 1
    gov.contrib.ssa.owners_draw.include_hi: true
    gov.irs.payroll.social_security.cap: 200_000
    gov.simulation.aime_lookback_years: 0
    payroll_tax_gross_wages: 150_000
    self_employment_income: 40_000
    s_corp_income: 64_970.22
  output:
    # Room 200,000 - 150,000 - 36,940 = 13,060 of the draw's 59,999.998.
    owners_draw_oasdi_base: 13_060
    owners_draw_hi_tax: 1_740
    owners_draw_niit_exclusion: 64_970.22
    # (4,580.56 + 1,071.26 + 1,619.44 + 1,740.00) / 2 = 4,505.63.
    self_employment_tax_ald_person: 4_505.63
    # Line 5: 104,970.22; line 6: 0.3810605; 40,000 - 1,716.92 = 38,283.08.
    # Counting only the OASDI-taxed 14,141.85 would give 36,671.24; main 35,494.37.
    self_employed_health_insurance_earned_income_limit: 38_283.08

P3-2: three small wording slips in the body and one YAML comment.

  • The body's Tests section says the generated test has "explicit @examples for each setup the YAML pins … so none of them depends on the random draw." Two of the ten YAML cases, "no draw" and "no Schedule C", have no @example. The test is also derandomized, so none of its draws is random. Suggested replacement: "The generated SEHI test has an explicit @example for each of the YAML file's draw setups (the hand case, the three OASDI/deduction setups, HI on with and without the deduction, and the half phase-in). Its 40 generated draws reach none of the three new setups, so those rest on the @examples."
  • The impact: line cites reforms.py:372. At this head the call owners_draw = create_owners_draw_reform(parameters, period) is at :371, and :372 is the AFA reform. :754 (the None filter) is right.
  • In the third new YAML case, the comment "the whole S-corp profit in line 5 would give 38,214.38" should say 38,214.39. My hand script gives 38,214.39, and the model under mutant (b) gives 38,214.387. The hand-off already noted this falls within the 0.02 margin. It's cosmetic.

r2 findings at this head

r2 Status Witness
P2-1 no test with HI on and the deduction off, or a partial phase-in with HI off; M6 survives Closed The three cases are at YAML :163, :186 and :210, with matching @examples at test_owners_draw.py:795-813. M6 is now killed by "With HI on, nondeductible draw SECA still keeps the draw out of line 5" (39,795.895 vs 39,464.37) and by test_generated_sehi_limit_counts_seca_taxed_draw_in_line_5.
P3-1 docs section splits the cap paragraph; / q shorthand Closed docs/contributed_reforms/owners_draw.md: the cap paragraph (:100-105) now closes "Gross draw, adjusted earnings, and cap priority", and "Self-employed health insurance limit" (:107-120) sits just before "Benefit credit and active NIIT interaction". Both sentences (:114-115, :136-137) now say "divided by the net-earnings factor (q by default)".
P3-2 stale CI line Closed The body cites run 41537180716 at this head, success, 35/35. I confirmed that below.

Checked and sound

  • The merge. git merge-tree --write-tree 8d96db39ac f8142b0223 gives tree e41006f9, the same tree as a6d18eb5. So the merge commit is the clean automatic merge, and nothing was hand-edited in it. git diff --stat f8142b0223 a6d18eb5 lists only this PR's five files. git diff --quiet 8d96db39ac bed691b7ff -- policyengine_us/reforms/ssa/owners_draw.py exits 0.

  • Hand values, computed from the parameter files before reading the cases' code path. Rates: SE OASDI 0.124, HI 0.029, employer share 0.5, percent deductible 0.5, so q = 0.9235. Schedule C net earnings are 36,940.

    Case Draw OASDI base Line 7 Line 5 draw term Limit Main
    HI on, deduction off, wages $300k 0 535.63 0 39,464.37 39,464.37
    HI on, wages $300k 0 1,405.63 64,970.22 39,464.37 38,594.37
    Share 0.5, HI off 30,000 4,685.91 32,485.11 37,414.14 35,314.09
    Hand case (reference) 60,000 6,545.91 64,970.22 37,505.61 33,454.09

    Under M6 the first row gives 39,795.89. With the whole S-corp profit, the third gives 38,214.39. Every value matches the YAML comments, apart from the 0.01 noted in P3-2.

  • The defect at main. I copied this head's YAML file into an origin/main (d4d391788d) worktree and ran it there with PYTHONPATH set to that worktree (and confirmed the import path): 5 failed, 5 passed. The failures are:

    • the hand case: 33,454.09;
    • the premiums case: 33,454.09;
    • partly within the cap: 36,364.37;
    • HI on with no room: 38,594.37;
    • the half phase-in: 35,314.09.

    Each is main's value as the YAML comments state it. The five that pass are the cases where the PR says main is already right.

  • Tests at head, one file per process:

    • the YAML file: 10 passed (196 s);
    • test_owners_draw.py: 38 passed (151 s);
    • test_source_conflicts.py: 103 passed.
  • Mutants. Each ran in a separate detached worktree at this head, in its own processes: the YAML file, then pytest -k "sehi or disabled". After each, git checkout restored the code and git status --porcelain was empty.

    Mutant (owners_draw.py:304-305) YAML (10) pytest (5) Killed by
    (a) M6 half_seca_deduction | include_hi 1 failed 1 failed "With HI on, nondeductible draw SECA …" (39,795.895); generated SEHI test
    (b) s_corp_income for the draw term 3 failed 1 failed above-cap HI off (39,795.895), partly within cap (38,614.605), half phase-in (38,214.387); generated SEHI test
    (c) gate on include_hi 5 failed 1 failed hand case and premiums (33,454.09), partly within cap (36,364.37), HI on nondeductible (39,795.895), half phase-in (35,314.09); generated SEHI test
    M8 (mine) draw term × _share(period, parameters) (phase-in applied twice) 1 failed 1 failed "A half phase-in …" (36,667.355); generated SEHI test
    M9 (mine) see P3-1 10 passed 5 passed (whole file 38 passed) only my witness case

    These match the hand-off's mutant table case for case. Afterwards I removed every worktree, and the main checkout's git status is clean.

  • The rule, in every setup the change reaches. _enabled is checked per period, and disabled years call baseline_sehi_limit.formula. The variable is per person, so filing status doesn't enter. The draw term is owners_draw_niit_exclusion, gated on half_seca_deduction. With HI on that is owners_draw_gross; with HI off it is owners_draw_oasdi_base / owners_draw_net_earnings_factor, and the factor follows the override when one is set. So it is the draw profit whose SECA is in line 7, under every combination of flags.

  • Docs against code. I read owners_draw_net_earnings_factor (owners_draw.py:116-134): it returns the override when nonzero, else 1 - employer_share × (OASDI + HI). That is the docs' q at :63-66, which counts HI even when HI on the draw is off. owners_draw_niit_exclusion (:237-244) divides by that variable. Both "divided by the net-earnings factor (q by default)" sentences are therefore accurate. draw_oasdi_base is the docs' own symbol (:82). The rest of the moved section matches the override: "the whole actual gross draw D with HI on", "line 5 omits the draw and the limit follows the baseline formula" with the deduction off, and "Disabled years use the baseline formula".

  • Readers. The only reader of self_employed_health_insurance_earned_income_limit is self_employed_health_insurance_ald_person. From there it reaches:

    • the SEHI ALD totals and dependents_self_employed_health_insurance_ald;
    • qbi/deduction_definition, dependent_taxable_ss_magi and self_employed_health_insurance_ald_excluded_premiums;
    • SC two-wage-earner subtractions, MS, OH, MO (AGI and premiums) and MT.

    Each uses the deduction just as on main. Under the reform each gets a larger deduction where premiums bind, which is the intended correction. No reader is made worse. The earned-income readers the body lists as "same kind" subtract the SE tax ALD and don't read this variable. They behave the same on main with the reform on, so they belong to the proposed follow-up.

  • Baseline can't move. create_structural_reforms_from_parameters (reforms.py:371) calls create_owners_draw_reform, which returns the reform only when some in_effect value is true (owners_draw.py:377-380). in_effect defaults to false, and reforms.py:754 filters out None. Every baseline shard and Household API Partners passed. The only population check needed is the hub's reform-on microsimulation: the limit should rise only for Schedule C filers with a SECA-taxed draw whose premiums bind.

  • Body lines. impact: and axiom: n/a: contributed reform are present. The methodology paragraph's 39,464.37 matches my run of the second new case. The mutant table matches my reruns.

  • Methodology choice: yes. Line 5 counts only the draw profit whose SECA is in line 7, rather than all of the S-corp profit, and only the phased-in share. That is one reading where the law offers no direct rule for a parallel draw base. The HI-on, no-room case is consistent with it: the limit then equals line 4 less Schedule C's own deductible tax.

  • Order with Add annual Social Security earnings histories and per-year reforms #10560. Add annual Social Security earnings histories and per-year reforms #10560 (head 2166886f48) merges with this head without conflict: git merge-tree exits 0. In the merged tree, the SEHI override still uses _enabled, which Add annual Social Security earnings histories and per-year reforms #10560 keeps. Add annual Social Security earnings histories and per-year reforms #10560's kernel computes niit_exclusion the same way: gross with include_hi, else oasdi_base / net_earnings_factor (tools/social_security/owners_draw.py:264-270). I ran the merged tree:

    • this YAML file: 10 passed;
    • the whole test_owners_draw.py: 38 passed.

    Once this lands, Add annual Social Security earnings histories and per-year reforms #10560 needs only a merge of main. Its doc hunk (the benefit-credit paragraph) lands just after the moved SEHI section and merges cleanly. Its test_installed_formulas_delegate_to_the_same_kernel_and_honor_inputs iterates only the owners_draw_* outputs, so this override doesn't need to delegate to the kernel.

  • Scope. Nothing under .github/ or policyengine_us/tests/policy/baseline/partners/. The files are the changelog fragment, the docs, owners_draw.py, the YAML file and test_owners_draw.py. The open-PR search on the changed file names finds Add annual Social Security earnings histories and per-year reforms #10560 (above) and the drafts Let farm, partnership and S corporation plans set the self-employed health insurance limit #10120 and Let farm and partnership plans take the self-employed plan deduction #10131. Those two edit the baseline limit formula, so whichever lands second must carry the change into the override's copy. test_generated_sehi_limit_counts_seca_taxed_draw_in_line_5's check against main is the guard for that (round 2's M7).

  • CI at this head. Pull request run 41537180716: completed, success, 35 of 35 jobs succeeded, none cancelled or skipped. Documentation run 41537180467: success. Its build succeeded, and "Deploy documentation to GitHub Pages" was skipped, as it is for every PR.

What I couldn't check

  • Sources. This session's network policy refuses irs.gov, govinfo.gov, uscode.house.gov, ecfr.gov and law.cornell.edu (proxy 403 or DNS refusal). I couldn't read Form 7206, its instructions or 26 U.S.C. 162(l), and I quote none of them. My hand rule follows the line structure in the baseline variable and the PR, which round 1 confirmed against the forms.
  • Population impact with the reform on: no microsimulation, as instructed.
  • Add annual Social Security earnings histories and per-year reforms #10560's own test_owners_draw_kernel.py at the merged tree; I ran only this PR's two test files there.
  • The hand-off's local timings. I reran every test it cites instead.

Generated by Claude Code

The round 3 review's witness: with HI on, line 5 holds the whole gross draw even when
13,060 of OASDI room remains. A mutant that counts only the OASDI-taxed part passed
every earlier test and fails this one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@MaxGhenis
MaxGhenis merged commit c3ae231 into main Nov 3, 2026
36 checks passed
@MaxGhenis

Copy link
Copy Markdown
Contributor Author

US + core hub merge audit, us#10571 at 8e10a070d9474f0df7090701d50065cd220101a7 (squash): Count the owners' draw in the SEHI limit's line 5 under the owners' draw reform.

  • Review: cloud review r3 (Opus 5.5, independent) approved at bed691b7ff with P3 notes only. It found every round 2 finding closed, recomputed the three new YAML cases by hand, and killed the three hand-off mutants and one of its own two. The other, M9, survived every test, so the hub added the review's witness YAML case and @example at 8e10a070d9; the YAML case failed under M9 in the review's run. The hub also revised the body's wording. The review's other two wording notes don't hold: reforms.py:372 holds the call, and exact arithmetic gives 38,214.38.
  • 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 reviewer's latest review requests changes;
    • the semantic-overlap guard;
    • head pinned.
  • Impact: none at baseline: create_owners_draw_reform returns the reform only when some gov.contrib.ssa.owners_draw.in_effect value is true, and that parameter defaults to false.
  • Order: this PR lands before Add annual Social Security earnings histories and per-year reforms #10560, which rewrites the same owners_draw.py and merges main afterwards.
  • Axiom: n/a: contributed reform

@MaxGhenis
MaxGhenis deleted the owners-draw-sehi-line5 branch November 3, 2026 05:12
MaxGhenis pushed a commit that referenced this pull request Nov 3, 2026
#10571, now merged from main, reworded owners_draw.md to say the OASDI
base is divided by the net-earnings factor (q by default), because an
explicit net_earnings_factor overrides q. The history page still said
draw_oasdi_base / q, and never defines q. Use the same wording there.
The shared kernel divides by the net_earnings_factor stage, which takes
the override.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis pushed a commit that referenced this pull request Nov 3, 2026
Main moved 59 commits past the branch. Three files changed on both sides
and merged without a conflict. owners_draw.py keeps #10571's
self_employed_health_insurance_earned_income_limit override in
reform.apply. That override reads owners_draw_niit_exclusion, which this
branch now computes through the shared kernel, and the kernel takes the
same disabled, HI-on and OASDI-only branches as main's formula. The
owners' draw docs keep #10571's line 5 section next to this branch's
annual-history text. The differential test keeps main's leaf-level
classification and adds gov.contrib.ssa.history to NEW_PARAMETER_NODES.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis pushed a commit that referenced this pull request Nov 3, 2026
#10571 overrode self_employed_health_insurance_earned_income_limit with a
copy of main's Schedule C-only formula plus the draw profits in line 5, so
with the reform on, the farm, partnership and S corporation plan businesses
this branch adds were ignored. The baseline formula is now a module-level
Form 7206 line 13 helper that takes extra line 5 profits, and the reform
calls it with the SECA-taxed draw profits. Three new reform cases cover a
named farm, the unnamed default, and a named Schedule C beside a larger
farm. The pure harness names the Schedule C business, which its oracle
assumes, and supplies S corporation wages of zero.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis pushed a commit that referenced this pull request Nov 3, 2026
#10571 added the SECA-taxed draw profits to the profits that the SEHI limit
splits Schedule 1 line 15 over, but the plan's earned income still split
line 15 over Schedule C, farm and partnership profits only, so under the
reform the Schedule C plan business took the draw's deductible SECA as well
(40,000 Schedule C with a 60,000 net-earnings draw: 33,454.09 instead of
37,505.61). Step 3 is now a module-level helper that takes extra line 5
profits, and the reform overrides self_employed_pension_earned_income to
pass the same draw term the SEHI override uses.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis added a commit that referenced this pull request Nov 5, 2026
…10560)

* Add annual earnings histories and conventional Social Security AIME

Add explicit complete-history inputs, annual cap/indexing ledger and claim-year freeze, reusing the supplied-record PIA/payment pipeline. Share legacy-compatible indexing and highest-years helpers and add conventional, SSA-example and legacy differential tests.

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

* Reuse owners draw arithmetic for annual history benefit credit

Extract one staged per-year kernel shared by reform Variables and history records, preserving intermediate overrides and lazy parameter reads. Add adjusted draw before each ordinary benefit cap and retain tax-room and NIIT-exclusion diagnostics separately. Freeze the pre-extraction arithmetic in differential and property tests.

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

* Compute annual mini PIAs and eligibility-cohort phase-in

Apply the shared eligibility-year formula to annual indexed earnings divided by 12, select the highest 40 mini PIAs and divide by 37. Blend with conventional PIA using a neutral-by-default 2032–2036 parameter path, retaining exact rational intermediates until final dime truncation.

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

* Add named annual donut tax and AIME-plus credit paths

Keep separate fixed-threshold OACT 2025 variants and a configurable neutral custom path. Tax and optionally credit only the covered stream above max(ordinary cap, threshold), index and select its own highest 35 years, and add the eligibility-cohort AIME-plus component after the mini blend.

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

* Expose annual earnings and PIA reconciliation ledgers

Expose exact annual earnings, E1, donut, indexing and mini-PIA diagnostics through the household report. Preserve complete JSON sources through export, document archived OACT rules and modeling conventions, and isolate new engine/differential files on existing Rest runners. Add future-cohort, source-isolation, reporting and full SSA payment examples.

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

* Verify exact reform composition and document annual history use

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

* Support early covered earnings in annual donut policy lookups

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

* Release owners draw accessors after annual evaluation

Break the recursive local-function cycle after each selected-stage or complete annual calculation so Person accessors do not retain entire simulations until cyclic collection. Preserve all arithmetic and lazy dependency behavior; add three GC-disabled weakref regressions.

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

* Select history AIME computation years by eligibility cohort

Cohorts first eligible before 1991 have fewer than 35 computation years
under 42 U.S.C. 415(b)(2), so a fixed 35/420 understated their AIME (a 1990
witness gave $895 instead of $912). The ordinary history AIME now selects
min(35, E - 1956) years and divides by that count times 12; AIME+ and the
mini-PIA keep their own rules. The ledger JSON also names the donut credit
coefficient and the year that selected it, the January diagnostic PIA's
period, and the computation-year count the ordinary flags follow.

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

* Document cohort computation years, conventions and source edition

State the cohort selection rule and its 1990 witness, the combined dime
rounding and cohort-selected donut coefficient conventions, Case B's dime
floor of SSA's formula result, and that the E2.13/E2.14/B3.12 dates come
from the 2025 Trustees Report edition. No parameter value changes.

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

* Merge main into ss-household-calculator-4a2

Main shipped the public calculator source selectors and the COLA means
test. Keep both import sets in engine.py, and take main's selector text in
the calculator guide; this branch's "modes remain reserved" text is now
false, so only the history-specific guidance survives.

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

* Classify gov.contrib.ssa.history as calculator-only

Main's namespace-classification check fails on any unlisted child of
gov.contrib.ssa. Only the household calculator's history route reads
this node (history_parameters_at via donut.py and mini_pia.py), so the
reference model drops it and the differential can still catch a legacy
formula that reads it by accident.

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

* Merge main into ss-household-calculator-4a2

Brings the branch up to date with main (18 commits behind) before the
1984 benchmark fix round.

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

* Start the annual history route at eligibility year 1984

42 U.S.C. 415(a)(4)(B)(i) keeps the December 1978 computation, when larger,
for workers with pre-1979 earnings who become eligible before 1984. The
history route computes only the wage-indexed 415(a)(1) formula, so it now
refuses E = 1983 at computation_years, compute_history, history_pia_details
and SuppliedRecord.benchmark (history source only; supplied AIME and PIA
keep their 1983 start). The boundary case moves to E = 1984 (28 years,
divisor 336), and the docs note that the route assumes no covered wages
before 1951, which the 415(a)(4)(B)(ii) subsection (d) guarantee counts.

Also names the 2025 Trustees Report edition beside two E2.13/E2.14 mentions.

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

* Merge main into ss-household-calculator-4a2

Main moved 59 commits past the branch. Three files changed on both sides
and merged without a conflict. owners_draw.py keeps #10571's
self_employed_health_insurance_earned_income_limit override in
reform.apply. That override reads owners_draw_niit_exclusion, which this
branch now computes through the shared kernel, and the kernel takes the
same disabled, HI-on and OASDI-only branches as main's formula. The
owners' draw docs keep #10571's line 5 section next to this branch's
annual-history text. The differential test keeps main's leaf-level
classification and adds gov.contrib.ssa.history to NEW_PARAMETER_NODES.

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

* Give the 415(a)(4)(B)(i) reason at every pre-1984 history benchmark

SuppliedRecord.benchmark ran the shared 1983 check before the history
route's own 1984 check, so a history record at E = 1982 got "Computation
benchmarks before 1983 are unsupported", which implies 1983 works. The
history check now runs first, so calculate_year and the engine give the
415(a)(4)(B)(i) reason at E <= 1983, as computation_years, compute_history
and history_pia_details already did. Supplied AIME, PIA and survivor
records keep the shared 1983 start.

The tests now pin both sides of this boundary at the main door: a
January 1, 1922 birth attains 62 on December 31, 1983 and is refused, and
a January 2 birth benchmarks at 1984, so changing the history check to
<= fails. They also check that a supplied PIA, not only a supplied AIME,
keeps 1983, and that annual records cannot begin before 1951, the limit
the docs rely on.

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

* State the 415(a)(4) and 415(d)(2) conditions in the history docs

The paragraph on why the history route starts at 1984 said "before 1984"
without (B)'s condition that the worker first becomes eligible after
1978, could be read as if (B)(ii) also stopped at 1984, and did not name
the subsection. It also left out that 415(d)(2)(A) limits subsection (d)
to a worker with a quarter of coverage before 1951, which is what makes
the no-pre-1951-wages assumption sufficient. It now also states the
route's other assumption: no disability-benefit eligibility before
January 1979, the case 415(a)(4)(A) takes outside paragraph (1). Own
retirement records have no prior-disability input. The wording follows
the delta review's reading of the statute; the build session could not
reach a copy of 42 U.S.C. 415 to recheck it.

Also rewraps the 100-column donut-coefficient line. Only whitespace
changes there, so the rendered page is the same.

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

* Merge main into the Social Security earnings history route

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis added a commit that referenced this pull request Nov 8, 2026
…ealth insurance limit (#10120)

* Limit the self-employed health insurance deduction to 401(c) earned income

26 U.S.C. 162(l)(2)(A) caps the deduction at earned income within the
meaning of section 401(c), which section 401(c)(2)(A)(v) and (vi) reduce
by the section 404 plan deduction and the section 164(f) deduction for
half of self-employment tax. Form 7206 (and Pub. 535 Worksheet 6-A before
2023) subtracts the share of Schedule 1 line 15 attributable to the plan
business and the line 16 plan deduction from that business's net profit.
The model used net profit alone.

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

* Test the self-employed health insurance earned income limit against Form 7206

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

* Add Form 7206 property tests for the self-employed health insurance deduction

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

* Update SSTB and Ohio expectations for the 401(c) earned income limit

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

* Widen the self-employed health insurance limit to farm, partnership and S corporation plans

Form 7206 allows the plan to be established under a Schedule F farm, a
partnership with K-1 box 14 code A earnings, or an S corporation in which
the person is a more-than-2% shareholder (162(l)(5), line 11), one form per
plan. Add self_employed_health_insurance_plan_business (default: the business
with the largest earned income) and s_corp_shareholder_medicare_wages.

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

* Extend the Form 7206 properties to farm, partnership and S corporation plans

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

* Add changelog fragment

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

* State the property 5 floor without depending on the plan contribution limit

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

* Clarify the default plan business and the S corporation premium gate

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

* Address review: name partnership and S corporation in YAML, document the default's limits

- Limit YAML Cases 18 and 19 name PARTNERSHIP and S_CORPORATION.
- The plan business input says when a partner's or shareholder's own policy
  counts, and that all Schedule C income is one business.
- Cite PDF pages 1-2 of the Form 7206 instructions.
- Drop the sum-of-businesses bound, which the differential property implies.

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

* Merge main into the plan business branch after #10109 landed

#10109 squash-merged with later commits this branch did not have. Keep this
branch's per-business limit and add #10109's documentation of the single
Schedule C business and the per-person line 15. In the limit YAML, keep
main's Cases 1-10 in place (Case 8 now names SCHEDULE_C, since an unnamed
business takes the largest) and append this branch's cases as 11-21. In the
ALD YAML, append this branch's four cases as 9-12. In the property test, take
main's independent Schedule SE reference for line 15 and its SSTB draws.
Delete #10109's changelog fragment, which main already released.

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

* Allocate line 15 by each business's share, as main does

The per-business rewrite computed profit x (1 - line 15 / line 5), which
adds two float32 roundings. For one-business filers it moved the limit from
main's value by up to $0.03 (92 of 400 Schedule C-only test filers). Each
business now subtracts line 15 x its own share of line 5, the same
operations main uses, so Schedule C-only limits match main bit for bit.

The limit also documents that each kind of business is one total per
person, and that CRP payments, the optional methods and the
personal-services test are not modeled. The two 162 references are one
source, so they become one entry.

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

* Say where entity-paid premiums go and that every business kind is pooled

The plan business input now says that a farm, a partnership and an S
corporation are each one total per person, like the Schedule C business,
and that premiums a partnership or S corporation pays or reimburses go in
health_insurance_premiums. The S corporation input says those premiums are
box 1 wages but not box 5 wages (26 U.S.C. 3121(a)(2)(B)), so they belong
in employment_income but not here, and that is_self_employed must be set
even when the shareholder has no other earned income.

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

* Make the S corporation case a valid return and check the reference limit

ALD Case 11 kept the reimbursed premiums out of box 1 wages, which the Form
7206 instructions require for the plan to count as established under the
S corporation, so its AGI of 0 couldn't occur. Box 1 wages are now 22,000
and AGI 12,000. The property test compared the deduction with the model's
own limit; it now uses the reference limit for each copy. Limit Cases 15
and 19 gain their hand computations.

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

* Merge remote-tracking branch 'origin/main' into pr-10120

* Carry the plan business choice into the owners' draw reform's SEHI limit

#10571 overrode self_employed_health_insurance_earned_income_limit with a
copy of main's Schedule C-only formula plus the draw profits in line 5, so
with the reform on, the farm, partnership and S corporation plan businesses
this branch adds were ignored. The baseline formula is now a module-level
Form 7206 line 13 helper that takes extra line 5 profits, and the reform
calls it with the SECA-taxed draw profits. Three new reform cases cover a
named farm, the unnamed default, and a named Schedule C beside a larger
farm. The pure harness names the Schedule C business, which its oracle
assumes, and supplies S corporation wages of zero.

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

* Name the smaller-plan-business case in the plan business input's docs

The default takes the business with the largest earned income, so it
also overstates when the plan sits under a smaller business (issue
#10528's Case 3). The documentation named only the partnership or S
corporation case; it now tells users to name the business in both.

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

* Merge main into sehi-plan-business-base

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

* Test long-term care premiums under every plan business

Since #10265, the deduction is the smaller of Form 7206 line 3 (the
medical premiums plus eligible long-term care premiums) and line 13.
The property test still capped it at the medical premiums, and no test
put long-term care premiums under a farm, partnership or S corporation
plan.

- ALD YAML Cases 13 to 16 add long-term care premiums under an S
  corporation, a farm, a partnership and the default business. Each
  checks the long-term care part and Schedule A as well as the
  deduction.
- The property test draws long-term care premiums, checks the
  deduction against line 3 and adds property 6: the long-term care part
  and Schedule A.
- The plan business, S corporation wage and long-term care premium
  inputs say that long-term care premiums a partnership or S
  corporation pays or reimburses go in
  long_term_health_insurance_premiums, on the insured person.
- The reform case with S corporation profits but no box 5 wages now
  says so in its name.

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

* Say the model taxes reimbursed long-term care premiums as payroll wages

The S corporation wage input's documentation now says the model's payroll
tax wages start from employment_income, so they include the long-term care
premiums it routes there. Three test comments name the year of the $850
limit, the premiums property 6 counts, and both zero lines in the owners'
draw case.

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

* Tell partners where guaranteed payments go and cite Notice 2008-1

The plan business input now says a partner also enters premium
guaranteed payments in partnership_income, and in
partnership_self_employment_net_earnings when the partnership reports
them in K-1 box 14 code A, matching the S corporation input's
employment_income sentence. Both inputs cite Notice 2008-1 for when a
plan is established by the S corporation and for the box 1 vs box 5
treatment, and the plan business input cites the Form 1065
instructions' line 4c note that guaranteed payments are net earnings
from self-employment. Documentation and references only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.

2 participants