Skip to content

Attribute self-employed health premiums to farm and partnership businesses - #1208

Draft
MaxGhenis wants to merge 7 commits into
mainfrom
us-sehi-farm-partnership-attribution
Draft

MaxGhenis wants to merge 7 commits into
mainfrom
us-sehi-farm-partnership-attribution

Conversation

@MaxGhenis

@MaxGhenis MaxGhenis commented Oct 11, 2026 •

Copy link
Copy Markdown
Contributor

Draft. Depends on PolicyEngine/policyengine-us#10120 (open draft, head b03f96f65f, stacked on PolicyEngine/policyengine-us#10109). Hold the merge until #10120 merges. Microcosm pins PolicyEngine-US 2.2.1, which caps the deduction at Schedule C income, so the federal effect also needs the engine pin to move past #10120.

Problem

The other_health_insurance_premiums stage's attribution operation (#451 item 2) sets is_self_employed and copies the reported non-Part-B premium onto health_insurance_premiums only for people with strictly positive Schedule C income. The law allows more:

  • 26 U.S.C. 162(l)(2)(A) limits the deduction to earned income "derived by the taxpayer from the trade or business with respect to which the plan providing the medical care coverage is established".
  • Form 7206 instructions (2023), p. 1 list who qualifies: a net profit on Schedule C or Schedule F, a partner with net earnings on Schedule K-1 box 14 code A, the Schedule SE optional methods, and wages of a more-than-2% S corporation shareholder.
  • Let farm, partnership and S corporation plans set the self-employed health insurance limit policyengine-us#10120 computes the Form 7206 limit for each business (Schedule C = total_self_employment_income, Schedule F = farm_operations_income, partnership = partnership_self_employment_net_earnings) and, with no named plan business, takes the largest.

On the certified default (populace_us_2024, sha 6496cc43…), 2.01M people with nonzero farm income and 2.06M with nonzero partnership earnings are not flagged self-employed, so #10120 cannot reach them.

Change

A person is self-employed for this stage when any one plan business has strictly positive net earnings, each on its own:

Business Columns Rule
Schedule C self_employment_income_before_lsr + sstb_self_employment_income_before_lsr sum > 0 (one business, as in #10120)
Schedule F farm_operations_income > 0
Partnership (K-1 box 14 code A) partnership_self_employment_net_earnings > 0
  • A loss in one business never cancels another's profit, because one plan sits under one business.
  • The Medicare-proxy exclusion (age 65+ or any SSDI) and the measured-ESI exclusion are unchanged and apply to the new paths.
  • The manifest parameter self_employment_income_sources (list) becomes plan_business_income_sources (keyed by business). source_stages.json, the regenerated sources.yaml and the sources schema move with it.
  • The signal gate recomputes the same rule from shipped columns and reports positive_plan_business_rows per business.
  • Not covered: the Schedule SE optional methods and S corporation shareholders, which no measured input carries (#10120's s_corp_shareholder_medicare_wages has no data).
  • The partnership path is an upper bound. A partner's own policy counts only when the partnership pays or reimburses the premiums as guaranteed payments (Form 7206 instructions, p. 1), and the data do not record that. This reading is logged in the US hub's statute-rulings file as us:sehi-plan-business-data-attribution.

Invariants

These hold for every input and are tested with Hypothesis in tests/engine_free/us/test_us_se_health_attribution_properties.py (150 examples each):

  1. Reference. Premium and flag equal an independent row-wise reference written in plain Python; the order of businesses does not matter; the source frame is not mutated.
  2. No invented mass. Each attributed premium is 0 or exactly the reported premium, the total never exceeds the reported total, and a positive premium implies the flag.
  3. Differential against the old rule. Restricted to Schedule C, the operation equals the pre-change formula exactly. The widened rule only adds people: everyone the old rule attributed keeps the same premium and flag, every added person has a profitable farm or partnership, and people without one are unchanged.
  4. Losses never cancel. Replacing any unprofitable business with any other non-positive amount (for Schedule C, any other split netting to a loss or zero) leaves the output unchanged.
  5. Differential against the gate. The signal gate's own recomputation of the identity finds no divergence on the operation's output, and its per-business counts match.

Tests

  • test_us_other_health_insurance.py: farm-only and partnership-only carriers; a Schedule C loss beside a farm profit and a farm loss beside partnership earnings; both exclusions on the new paths; missing and nonfinite farm and partnership sources fail closed; the retired Schedule C-only parameter and a narrowed mapping are refused; a farm person and a partner carry positive premiums through the stage and the gate; the gate fails when a farm person's flag or premium is tampered with or a source column is missing.
  • Local runs after merging main (584 commits), macOS, Python 3.14, pytest -n 4:
    • at c196ec21c: 645 passed across the stage tests, the properties and every spec-engine, bundle, coverage-tool and release-input-coverage file (exit 0);
    • at 56139c35b: 109 passed on the re-pinned spec files (exit 0).
    • tools/spec_engine_coverage.py --check exit 0 (42,186/42,186 fields, 41/41 inventory checks); tools/generate_us_bundle_from_constants.py --check exit 0.

Measurement

Stored data, no engine (data_only.py on the certified default; weighted people):

Schedule C-only rule Widened rule Change
is_self_employed 36.75M 39.27M +2.52M
People with an attributed premium 3.744M 3.880M +0.136M
Attributed premiums $18.35B $19.73B +$1.38B
Tax units with an attributed premium 3.69M 3.82M +0.14M
  • The Schedule C-only rule reproduces the shipped health_insurance_premiums and is_self_employed columns exactly, so the candidate differs from the certified file only by this change.
  • Of the 2.52M newly flagged people, 0.98M qualify through a farm only, 1.51M through a partnership only, and 0.02M through both; 0.07M have a Schedule C loss.
  • Few of them gain a premium: 38.9% are in the Medicare proxy, a further 39.0% have measured ESI, and 51.2% report any premium.
  • Medicare-proxy share by profitable business: Schedule C 19.1%, farm 13.2%, partnership 43.5%.
  • 0.110M of the 0.136M added carriers are PUF-support people, whose reported premium is the joint QRF's prediction. Its predictors include Schedule C income but neither farm nor partnership earnings.

Engine runs: PolicyEngine-US at #10120's head (b03f96f65f, 2.38.24, core 3.33.12). These are real microsimulations of the certified default and the widened candidate (build_candidate.py: the branch's own operation applied to the certified file, with only the two attribution columns changed). Each file is scored in 8 household batches, one fresh process per batch, and the weighted totals are summed. Every tax unit, SPM unit, family and marital unit nests in one household, so the batches add up exactly. A separately written full-file run in #1233 reproduces these figures: $15,980,982,740 on 3,730,316.1 returns, with the same per-business counts.

On fixed certified weights, TY2024 Schedule C-only (certified file) Widened Change SOI Pub 1304 Table 1.4, TY2023
Returns with the deduction 3,594,073 3,730,316 +136,243 3,595,764
Deduction $14.74B $15.98B +$1.24B $31.23B
… qualified by farm alone 0 66,444 people
… qualified by partnership alone 0 69,803 people
Federal income tax −$137.8M
State income tax −$57.9M (GA −$28.5M, MN −$8.7M, MD −$8.5M, LA −$7.1M, CA −$1.4M, others under $1.1M)
Neutralization probe (health_insurance_premiums), baseline minus reform on income_tax −$2.05B −$2.18B
  • Read these as fixed-weight effects. The release calibration targets JCT's CY2024 revenue loss from this deduction (us/fiscal_target_references.json), not the SOI return count, so a rebuild re-solves the weights. The certified file's 3,594,073 returns sit within 0.05% of SOI's count, but that is not a fitted target.
  • The amount stays at about half of SOI ($15.98B against $31.23B): the widening closes little of that gap.
  • Engine differential. On the widened file, nobody whose #10120 limit is positive is left unflagged; on the certified file 2.52M such people were. A further 960,094 people are flagged on both files while their limit is zero or less, because the limit nets out half of SE tax and plan deductions.

PolicyEngine-US 2.2.1 (the pin): pending. These runs were held by a host reservation until about 20:45Z on 2026-10-21. They score both files in 32 household batches.

What changes before #10120. Under 2.2.1 the federal deduction is min(max(total_self_employment_income, 0), premiums), so a farm-only or partnership-only person's deduction stays $0. Five state and local formulas read the bare premium input (MO qualified-premium subtraction, MI household resources, OH medical expenses, Orange County general relief, MO child-care subsidy), so the added $1.38B of premiums moves those today. is_self_employed has one reader in both engine versions: the defined_for gate of self_employed_health_insurance_premiums.

Identities re-pinned

The stage module is a seed-protocol kernel module and source_stages.json is digested into the spec bundle, so these moved and were recomputed: the frozen source-stage hash, US spec_sha256, seed protocol, seed map, source manifest, resource semantics, full checkpoint, the pointer inventory and field counts (+2 authored fields), the minimal-loader golden, and docs/evidence/spec-engine/us-f0-coverage.json.

Review

Round 1 (independent Opus 5.5 lane, subfleet run --task review --tier hard; the Codex lanes were closed): APPROVE, no blocking defects. It confirmed the gate matches #10120's limit, that no later stage rewrites the farm or partnership columns before the export gate, and that no registry declares this stage's reads. Acted on:

  • the schema now requires unique, non-empty column names;
  • the loss property covers Schedule C and the integration test carries positive farm and partnership premiums;
  • the notes state the live state and local effect, the PUF-support imputation caveat, and that treating Schedule C as one business follows #10120's modelling choice.

Its guess that the Medicare proxy bites hardest on farmers did not hold in the data (13.2% for farm, 43.5% for partnership). A second round runs on the final head.

Follow-ups (filed as separate tasks)

  • The raw-PUF fallback for partnership_self_employment_net_earnings in puf_support.py adds two loss fields, unlike the archived derivation. The certified build reads the processed PUF column, not that fallback.
  • The Form 7206 instructions let voluntarily paid Medicare premiums figure the deduction; the Medicare proxy withholds them. This predates the PR and needs a methodology decision.

No dataset release: Microcosm data releases are Max's call.

axiom: n/a: microdata attribution change; the engine-side provision is PolicyEngine/policyengine-us#10120, whose Axiom line is TheAxiomFoundation/rulespec-us#1676 queued

🤖 Generated with Claude Code

MaxGhenis and others added 4 commits October 11, 2026 17:20
The other_health_insurance_premiums stage's attribution operation set
is_self_employed and copied the reported premium onto
health_insurance_premiums only for people with positive Schedule C
income. 26 USC 162(l)(2)(A) and the Form 7206 instructions also let the
plan be established under a Schedule F farm or a partnership (Schedule
K-1 box 14 code A), and PolicyEngine/policyengine-us#10120 computes the
earned-income limit per business. The operation now opens wherever any
one of Schedule C (both inputs summed), farm_operations_income or
partnership_self_employment_net_earnings is strictly positive, so a loss
in one business never cancels another's profit. The Medicare-proxy and
measured-ESI exclusions are unchanged.

The manifest parameter becomes plan_business_income_sources keyed by
business; source_stages.json, sources.yaml, the sources schema and the
frozen source-stage hash move with it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The stage module is a seed-protocol kernel module and source_stages.json
is digested into the spec bundle, so the seed protocol, seed map, source
manifest, resource semantics, full checkpoint, pointer inventory, field
counts and US spec_sha256 move; us-f0-coverage.json is regenerated.

Adds example tests for farm-only and partnership-only carriers, a loss
beside another business's profit, the Medicare-proxy and ESI exclusions
on the new paths, fail-closed sources and manifest drift, plus Hypothesis
properties: a row-wise reference, no invented premium mass, the Schedule
C-only configuration equals the old rule and widening only adds people,
losses never cancel another business, and the signal gate's recomputed
identity agrees with the attribution. The coverage-manifest column notes
describe the widened rule.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The plan-business object adds two authored legacy-behavior fields, and the
sources schema change moves the minimal-loader golden vector.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- sources schema: plan-business column lists are unique, non-empty strings.
- Stage notes and module comment: the Form 7206 instructions name five
  qualifying situations, two with no measured input; Schedule C as one
  business follows policyengine-us#10120's modelling choice; PUF-support
  premiums are imputed without farm or partnership predictors; the state
  and local formulas reading the bare premium input move under 2.2.1
  already; the Medicare proxy withholds premiums Form 7206 would allow.
- Property: an unprofitable Schedule C can be swapped for any other
  non-positive split without changing the attribution, so a Schedule C
  loss never cancels a farm or partnership profit.
- Integration: a farm person and a partner carry positive attributed
  premiums through the stage and the signal gate.
- Re-pin the seed protocol, seed map, source manifest, resource
  semantics, full checkpoint, spec_sha256, frozen source-stage hash and
  loader golden for the final module and schema bytes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis and others added 3 commits October 21, 2026 11:08
…hip-attribution

# Conflicts:
#	docs/evidence/spec-engine/us-f0-coverage.json
#	packages/microcosm-build/src/microcosm/build/spec_engine/inventory_coverage.py
#	packages/microcosm-build/tests/engine_free/shared/test_spec_engine_loader.py
#	packages/microcosm-build/tests/engine_workflow/us/test_us_multispine_pool_tool.py
Main's 584 commits moved the US spec digest, seed protocol, seed map,
source manifest, resource semantics and full checkpoint, and the
minimal-loader golden; recomputed on the merged tree with the same
tools (coverage and bundle checks exit 0).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Form 7206 instructions (2023, p. 1) count a partner's own policy as
established under the partnership only when the partnership pays or
reimburses the premiums and reports them as guaranteed payments;
policyengine-us#10120's input documentation now says the same. The data
do not observe that, so the stage notes call the partnership path an
upper bound. Re-pins the source-stage hash and the digests it feeds.

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

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.

1 participant