Skip to content

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

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

MaxGhenis wants to merge 4 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, 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).

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 at 5ae529281 (macOS, Python 3.14, pytest -n 4): 234 passed across the stage tests, the properties, and the spec-engine loader, field-usage, inventory, schema, bundle and coverage-tool 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. tools/ci_test_plan.py verify 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: pending. Real microsimulations on the certified default and the widened candidate, under PolicyEngine-US 2.2.1 and the #10120 head (47bba5e893, 2.38.24), are queued under the shared heavy lock. They report the deduction (returns and amount) against SOI Pub 1304 Table 1.4 (TY2023: 3,595,764 returns / $31.23B, PolicyEngine/chronicle#105), federal and state income tax, and the release gate's neutralization probe. The probe's reason text in the coverage manifest is rewritten when they land.

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>

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