Skip to content

Medicaid MAGI household composition from co-resident parent ids (fixes #9404) - #9406

Merged
MaxGhenis merged 28 commits into
mainfrom
medicaid-magi-parent-ids
Oct 7, 2026
Merged

MaxGhenis merged 28 commits into
mainfrom
medicaid-magi-parent-ids

Conversation

@MaxGhenis

@MaxGhenis MaxGhenis commented Sep 7, 2026 •

Copy link
Copy Markdown
Contributor

Fixes policyengine-us#9404.

Use optional parent_1_id and parent_2_id inputs to identify co-resident parents and siblings when constructing Medicaid MAGI households. Resolve a dependent's effective claiming tax unit first, then add the dependent's own co-resident spouse once. Linked filers also count their co-resident spouses reciprocally, including separately filing spouses without a cohabiting flag. A spouse added to a dependent's household does not thereby enter the claimant's household.

The Ohio 2026 regression is a stylized reconstruction of Microcosm Build P household 1009324, not the production record: the 56-year-old's income is set to the $94,504 residual that reproduces the previous $141,848 pooled income, and child ages are placeholders. With parent IDs, four children of the 30-year-old have size 5 and income $23,334, and the 27-year-old's child has size 2 and income $24,010. Those five children qualify for Medicaid; the 17- and 16-year-olds retain size 3 and income $94,504 and remain ineligible. Without parent IDs, the regression retains size 10 and income $141,848 for all seven children. These are fixture results, not a national impact estimate.

Input contract and compatibility

The compatibility guarantee is exact existing behavior when all parent IDs are zero throughout the simulation. It does not guarantee that an isolated zero-ID household stays unchanged inside every mixed simulation. Zero means unknown. Nonzero person_id and parent IDs are signed int32 values; person IDs must be unique across the simulation whenever tax units span households or known claiming-unit IDs are supplied. Household-local reuse is supported only for self-contained household and tax-unit graphs. Duplicate-ID fixtures pin deterministic fallback behavior; duplicate IDs cannot establish legal identity.

An ID naming a co-resident resolves to that person. A named parent must be the claiming tax unit's head or spouse, rather than another dependent, for parental claimant recognition. The linked both-parent exception requires the same actually filing parental unit for the joint-return exclusion and correct membership in the identified claiming unit. Raw IDs, including absent-parent links, remain intact.

When exactly one distinct named parent is resident, Medicaid may also recognize that parent's established resident spouse as a stepparent. Evidence is bounded to joint head/spouse roles or a two-person marital unit with a spouse flag. The same effective relationship feeds claimant recognition, the both-parent exception, and non-filer parent/child membership. Two ID slots and this bounded inference do not represent every larger parent or step-relative graph. cohabitating_spouses describes the tax-unit head's separately filing spouse; attaching the flag to an unrelated claimant's unit can overcount.

The companion data export is microcosm#884; its production ID/link coverage still needs verification. The paper disclosure is obbba-paper#2.

Changes in this fix round

Recovered the six approved commits through 34c6562e2, retained the later fixes already published at 62aee62351, and merged current main dd9cb3f683 through merge commit b6ad6626ba. The clean merge tree was 8351b88; the new main changes were release-only and did not overlap model files. No history was rewritten.

  • Added reciprocal spouse membership, including the Ohio missing-flag case and California pregnant-spouse counterpart, with deduplication across same-unit membership, external claims, and the existing flag.
  • Added shared, bounded resident-stepparent resolution and regression cases for claimant recognition, separately filing parents, non-filer membership, and unsupported inference shapes.
  • Tightened ID documentation, made the co-resident duplicate-ID override independent of member order, and added second-slot collision cases.
  • Added grandparent/teen-parent/baby, claiming-head-or-spouse, and same-filing-parental-unit tests.
  • Fixed linked flagged-spouse income propagation to adult dependents and cancelled flagged spouse contributions already included through tax-unit membership or known external claims, consistently across size, income and California pregnancies. Three flagged-spouse regressions failed before these fixes.
  • Corrected the changelog to state co-resident resolution, the linked both-parent exception, and the simulation-wide all-zero guarantee.
  • Reject named parent-child relationships as spouse evidence during bounded stepparent inference. The new grandparent/teen18/baby case reproduced a baby household size of 3 where the legal composition requires baby+teen, size 2; the final composition file passes with baby size 2 and income $0.
  • Commit a4ca85f6fb gives cross-household married-dependent property scenarios globally unique person IDs and matching parent links; all 11 property tests passed with expected outputs unchanged.

Validation

Recovered head 62aee62351 passed 34/34 CI checks, including the partner contracts, in run 37563630113. Current main dd9cb3f683 also passed Household API Partners. These are recorded CI results, not local suite runs. No partner expected outputs were edited. Saved targeted local logs at the recovered head show 6 core tests and 11 property tests passed.

Earlier CI at 38880d4aa0 passed the three parent-ID YAML files (11 + 43 + 5 cases) but failed the spouse property for a minor whose inferred tax unit had no head: s13_spouse produced $0 versus the generator's expected $13,979. The explicit head inputs in 7d4ad6901a fixed that setup error; the recovered property file subsequently passed all 11 tests.

  • Approved 34c6562e2 reproduced 5 failures/6 passes across the 11 r8 fix-round cases in an independent archived source tree. The four new recovery-audit cases also failed before the final edits (one ancestor-inference case and three flagged-spouse cases); logs are retained.
  • At 409e65e8b2, all 63 YAML cases (47 composition + 11 Medicaid parent-ID + 5 demographic) and 17 Python tests (11 properties + 6 core) passed. Composition includes all 11 r8 fix-round cases and the four new recovery regressions. These were sequential single-file runs; no suites or microsimulations were run locally.
  • Independent clean-main dd9cb3f683 versus 409e65e8b2: 48 synthetic API cases (24 worlds), 13 variables, 624 arrays identical by dtype/shape/bytes, zero errors and zero differences. Omitted and explicit-zero inputs also matched within each source. Both model processes ran sequentially with the same Core environment and verified source paths; this is bounded compatibility coverage, not production-data parity.
  • make format passed after the final membership edits: 6,656 files unchanged and Ruff lint passed.
  • Observed 2026-10-07T11:12:11.906106+00:00: 7 successful, 4 running and 24 queued checks, no failures; run 37609932407. Final-head partner contracts are queued; broad suites remain incomplete.

Historical r8 checks against pristine ca1805cf0 found zero differences across 22,581 arrays (plus an explicit-zero repeat and 1,440 random-world arrays), with 50 matching errors. The current-main comparison above independently verifies this round and replaces reliance on the old class-restoring AST guard.

Before/after fixtures, logs, differential results and replay commands are retained with the fix-round report and evidence.

Impact and integration

Default-dataset microsimulation (hub runner): merge base against this head 409e65e8b2, on populace_us_2024.h5@populace-us-2024-spm-20260909, for 2024 and 2026. It ran under the shared heavy lock, one run per side, compared record by record.

Exactly zero change. is_medicaid_eligible, medicaid, medicaid_household_size, medicaid_household_income, is_chip_eligible, chip, aca_ptc and household_net_income are identical on every record in both years.

This is the required all-zero-ID check on real data. The default dataset has no parent_1_id or parent_2_id columns (see below), so every parent ID is zero and the PR reproduces main exactly. It does not measure the feature's effect: that needs a dataset with populated parent links. The other outputs listed below (is_parent, the exception and routing flags, medicaid_income_level, CA pregnancies and premium_tax_credit) were not separately recorded in this run. With all IDs zero they follow the same unchanged inputs.

Read-only metadata and a narrow person_id field inspection of the cached default populace-us-2024-spm-20260909 build (snapshot 9a814a3b3b53c0ecd6e1737b6ec862c31300ef6f) found 166,321 globally unique nonzero person IDs, all within int32 range. Both parent_1_id and parent_2_id columns are absent. Raw CPS PEPAR1/PEPAR2 fields exist, but no transformation into model links was run or verified. This is schema evidence, not a simulation or a weighted impact estimate. A populated-link export or verified transformation is needed to measure feature impact.

Stratify linked and zero-ID populations, children, teen parents, married dependents, multigenerational households, and state. Repeat with all parent IDs zero and require exact zero changes. National net effects and weighted counts remain unknown. No new performance benchmark was run; the historical r8 partial-model benchmark had 199,713 synthetic people and seven variables, taking 0.163 seconds with zero IDs and 1.49 seconds with links.

The separate medicaid-magi-joint-floor work overlaps medicaid_household_income.py, medicaid_household_income_member.py, medicaid_magi_person.py, and medicaid_person_is_required_to_file.py. Its branch was not imported. Integration must retain this PR's claimant/spouse membership and aggregate signed member income before applying the final household zero floor.

Methodology choices for Max

  • Stepparents are parents under 42 CFR 435.603. Two-slot completeness and the bounded spouse-inference scope are data-model choices, not alternative legal definitions.
  • The existing exclusion of a non-required-filer dependent spouse's income remains preserved. Whether the taxpayer-household exclusion under (d)(2)(ii) carries into the dependent's own spouse-augmented household remains for Max to decide.
  • California's full-time-student-under-21 treatment is established policy in ACWDL 20-10, not an optional policy choice for this build.
  • Cross-household ID collisions do not contain enough information to recover the intended identity through legal interpretation.

Final head: 409e65e8b2.

axiom: needed — rulespec-us parity review is pending; no public issue was filed.

@codecov

codecov Bot commented Sep 7, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (909176a) to head (eaf6cc4).
⚠️ Report is 64 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##              main     #9406    +/-   ##
==========================================
  Coverage   100.00%   100.00%            
==========================================
  Files            4        12     +8     
  Lines           76       278   +202     
  Branches         2         6     +4     
==========================================
+ Hits            76       278   +202     
Flag Coverage Δ
unittests 100.00% <100.00%> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

MaxGhenis and others added 3 commits September 25, 2026 19:37
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…olds

Three invariants over seeded random households, so a failure reproduces:
explicit zero parent ids give the same Medicaid and demographic outputs as
omitted ids; when own_children_in_household counts exactly the people
whose ids name a person, is_parent from ids agrees with the count path;
and is_parent/is_mother do not depend on member order.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis and others added 9 commits September 26, 2026 12:23
Rework per the independent review of #9406 (REQUEST_CHANGES, six findings).

- is_parent is now per person: own_children_in_household > 0 OR a
  co-resident person's parent id names them. Without ids it is main's
  expression; with consistent links both sources agree; one family's links
  can no longer erase another family's count-only parent (finding 2).
- Households with no nonzero parent id keep main's family-sum expressions
  verbatim. Households with ids use medicaid_non_filer_member_sum, one
  pairwise 42 CFR 435.603(f)(3) predicate over co-residents: self, spouse,
  own child-age children (reverse links, for applicants of any age),
  and for child-age applicants their parents and child-age siblings.
  Each member is counted once (findings 1 and 4).
- Main's family proxy survives only among unlinked people: child-age
  members without ids and members whose own_children_in_household exceeds
  the links naming them. The same rule drives the zero-id fallbacks of the
  claimed-by-parent test and the both-parents exception (finding 2).
- medicaid_household_size, medicaid_household_income and California's
  ca_medicaid_household_pregnancies sum over that same membership, so an
  excluded aunt's pregnancy no longer enlarges a niece's household
  (finding 3).
- Sums accumulate in float64; storage rounds (finding 5).
- Parent ids resolve among tax-unit members for the claimed-by-parent test
  (identity) and among household members where the rule requires living
  together (non-filer membership, both-parents exception) (finding 6).
- parent_1_id/parent_2_id document step and adoptive parents, 0 = unknown,
  nonzero ids, and int32 comparison.

Test expectation changes, all from the rules above:
- test_parent_links.py: the linked adult's household drops the unrelated
  child (size 3 -> 2, income 10,300.875 -> 10,100.375). The unrelated
  child has no ids and no family member reports a child the links omit, so
  its household is itself (size 3 -> 1, income -> 200.5). Previously the
  adult branch counted every child-age family member regardless of links.
- Demographic parent_ids.yaml and the absent-parent Medicaid case: an adult
  whose own_children_in_household is positive stays a parent even when no
  child's id names them. Two resolution-only cases now set that count to 0
  so they still isolate id resolution.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The zero-id differential found that, for 1986-1987 emergency Medicaid
cases where both main and this branch raise ParameterNotFoundError, the
branch raised a different error because it read filing_status before
main's own terms. Evaluate main's terms first and compute the link-based
condition only when some person has a parent id, so simulations without
ids make exactly main's calls.

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

- parent_ids_composition.yaml: teen parent (G40/T17/B1), mixed families
  (A35/C10 + B40/D8, as separate and as one family entity), another
  family's linked parent and the both-parents exception, California
  pregnancies (G60/M35/A30/C10/B2), adult sibling (G60/A25/B15), claimant
  in another household (P55/C26), stepfamily, married 18-year-olds, and
  the age-19 boundary for siblings and children. Expected values follow
  42 CFR 435.603(f)(3) and ACWDL 20-10; 8 of the 10 fail on 08eaff9.
- test_parent_links.py: float64 accumulation (2**24 + 1 + 1) in both
  member orders.
- test_parent_links_properties.py: on seeded random households with teen
  parents, absent parents, spouses and pregnancies, the linked composition
  equals the regulation's on consistent data and a scalar statement of the
  documented rule on data with dropped ids, extra counts and split
  families; size >= 1; CA pregnancies and income sum over the same
  members; outputs are invariant to member order; is_parent is exactly
  the union of the child count and the links.
- Changelog fragment describes the reworked behavior.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An independent r2 review confirmed all six r1 findings fixed and found:

- Blocker: a married dependent lost a co-resident spouse who files
  separately, because the linked spouse test required a tax-unit role.
  A spouse is now a co-resident partner in a two-person marital unit,
  whatever either partner's tax role, or the other head or spouse of a
  joint return (42 CFR 435.603(f)(3)(i), (f)(4)). People linked as parent
  and child are never spouses, since PE puts everyone in one marital unit
  when a situation omits marital units.
- A link in one household made its parent the presumed parent of a
  zero-id dependent in another household through a shared tax unit. The
  claimed-by-parent and both-parents fallbacks now use
  reports_unlinked_children (own_children_in_household exceeds the ids
  naming the person), which equals the original is_parent without links.
- Inherited gap: a child claimed on a parent's return in another known tax
  unit never got the both-parents exception. With parent ids, a known
  external claiming unit headed by a co-resident parent now counts as
  being claimed by one parent (42 CFR 435.603(f)(2)(ii)).
- The helper docstring no longer claims links never change another
  family's composition; switching a household to the linked rule can
  remove another family's legacy over-count.

The exception formulas evaluate main's terms in main's order, and the
composition file gains a regression per finding; each fails on 5934620.

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

An independent r3 review found:

- Blocker (from the r2 fix): with every parent id zero, the claimed-by-
  parent and both-parents fallbacks read own_children_in_household instead
  of is_parent, so an explicit is_parent input no longer matched main
  (C10 with unmarried P40/Q39 marked is_parent: main 3/$50,000, r2 head
  2/$20,000). The fallbacks and the composition proxy now use
  unlinked_parent: is_parent (formula or input) and either no id names the
  person or own_children_in_household exceeds the ids that do. Without
  links this is is_parent exactly; with links a parent whose children are
  all named is never another child's presumed parent, which keeps the r2
  cross-household fix.
- PE puts everyone in one marital unit when a situation omits marital
  units, so two adult siblings with ids became spouses. People sharing a
  parent id are no longer spouses, as linked parent-child pairs already
  were not.

Each new regression fails on 2e92217.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An independent r4 review confirmed the r1-r3 fixes and found that PE's
synthesized marital unit (one unit containing everyone when a situation
omits marital units) still paired two unrelated roommates as spouses. A
married couple living together is one family, so the linked spouse test
now also requires the marital-unit partners to share a family entity;
joint-return spouses are unchanged. The married dependent from r2 keeps
their spouse.

Also pins the intended cross-household behavior r4 flagged: a filer whose
only reported child is linked is not the presumed parent of a zero-id
dependent in another household of the same tax unit. So the no-ids
guarantee is for simulations with every parent id zero; a household
without ids keeps main's composition expressions but its tax-unit proxies
can see links held by a co-member elsewhere.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An independent r5 review found that spouses who are also step siblings
(their parents later married each other) lost each other: the r3 guard
against PE's synthesized marital unit rejected any pair sharing a parent
id, even with a cohabiting-spouses flag. A joint return or a cohabiting-
spouses flag on either partner's tax unit now shows the marriage; only a
bare two-person marital unit still needs a shared family and no shared
parent id. Linked parent-child pairs are never spouses.

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

The r6 review noted the changelog still promised that any household
without parent ids keeps its outputs, which the pinned cross-household
regression contradicts by design.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A mutation check over the membership rule showed three branches that no
test isolated: every joint couple also had an explicit two-person
marital unit, every marital-unit spouse also had a joint return or a
cohabiting flag, and no linked parent and child shared a synthesized
marital unit. Add one regulation-derived case for each: a joint return
with omitted marital units, a married dependent whose non-filing spouse
has no flag, and a parent with a 20-year-old child in a default marital
unit. Each case fails when its branch is removed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis and others added 7 commits September 27, 2026 02:39
Link-aware claim recognition correctly routes a married adult child whose
id names the parent claiming them to the tax-household rules, but that
branch never added the dependent's own spouse. P40 alone claims T19; T lives
with spouse S18, who earns $30,000 and files separately. Main routes T to
the non-filer rules by mistake and gets 2/$30,000; the rework gave 2/$0; 42
CFR 435.603(f)(2) and (f)(4) give the parent's tax household plus the
spouse, 3/$30,000.

In households with parent links, a tax dependent on the tax-household
branch now counts, once, the co-resident spouse the non-filer spouse rule
finds, unless the claiming tax household already holds them (same return,
or claimed into it from another unit). Size, income and California
pregnancies use the same membership. The spouse rule moves into
_spouse_rule so both branches share it. Households without ids keep main's
expressions through where(), so the no-ids path is unchanged.

Five regressions: the review's case, a California pregnant-spouse variant,
a teen claimed by the one parent she lives with, a known claiming unit
elsewhere, and a spouse on the same return who must count once. The first
four fail on eaf6cc4; the fifth guards against double counting.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Found by the new married-dependent property test. A minor alone in a tax
unit has no head, so PE makes them that unit's dependent. When
medicaid_claiming_tax_unit_id names a parent's unit elsewhere (main's
known-claimant fixtures use exactly this shape), the claimed-by-parent test
resolved the child's ids only in the child's own unit, found no parent, and
applied the 42 CFR 435.603(f)(2)(i) exception. A child of 10 living apart
from the claiming parent got 1/$0 instead of the parent's household, 2/$30,000,
which main gives.

With ids, a known claiming tax unit other than the dependent's own is the
claimant: the ids resolve among its members (person ids are distinct within
a tax unit), and its head or spouse must be named. A grandparent's unit
still is not a parent's. The no-ids path is unchanged.

Adds the two regressions and a seeded property test: parents claiming a
child whose id names them, on their return or as a known claiming unit, with
the child's spouse unmarried, flagged, in one family, on the same return,
claimed into the parent's unit or living elsewhere. It checks size, income
and California pregnancies against set-based oracles for the parent and the
child, and invariance to member order.

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

The changelog keeps the current guarantee: simulations in which every
parent id is zero keep their outputs, and a household without ids keeps its
composition formulas though a shared tax unit or family can change who is
presumed a parent.

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

Tests for branches the new code adds that no test reached yet:

- a joint claiming return whose spouse, not its head, is the parent the
  child's ids name;
- a head whose known claiming unit is their own: the unit's
  cohabiting-spouses flag already counts the spouse, so the tax-dependent
  spouse rule must not add them again;
- the married-dependent scenarios without ids give the same values alone
  and next to their linked copies, so the spouse rule stays inside linked
  households.

Also drops a redundant presence check: a claiming unit with nobody in a
role yields id 0, which no nonzero parent id matches.

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

The mutation check found that the married-dependent property test never
exercised "exactly once" for a spouse on the parent's return or claimed into
it: those spouses sat in a family of their own with no flag, so the spouse
rule never saw the marriage and nothing could be double counted. They now
share the child's family, as the same-family placement does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… 4, 6)

r7 finding 2: ids are only distinct within a household and within a tax
unit, so a claimant in another household can share the person_id of the
co-resident parent a child's id names. The claimed-by-parent test then took
the claimant for the parent. M35 (id 1) lives with C10 (parent_1_id 1); a
grandparent (also id 1) elsewhere claims C, through a known claiming unit
or on the grandparent's own return. Main and the r7 head give C the
grandparent's household, 2/$30,000; (f)(2)(i) gives the non-filer household
of C and M, 2/$20,000.

An id that names a co-resident now names that person, in both resolutions
the claimed-by-parent test uses: tax_unit_parent_indices drops a tax-unit
match that is not the co-resident the id names, and a known claiming unit's
head or spouse matches an id only if the id names no co-resident or names
that head or spouse. A repeated second id is the first parent again. The
no-ids path is unchanged (both helpers return before reading any
variable).

r7 finding 4: the same-return spouse case's comment now says its income
follows main's tax-branch reading of (d)(2)(ii), which a literal reading
would contest. r7 finding 6: the changelog limits the tax-route spouse to
dependents whose household is the claiming taxpayer's, and states the
co-resident rule. The id documentation states it too.

Regressions: both collision cases (they fail on f5151b4) and a unit test
for a repeated id naming a co-resident.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis and others added 2 commits October 6, 2026 10:42
…indings 1-3, 5, 6)

- Infer a lone named co-resident parent's established spouse (joint return,
  or two-person marital unit with the cohabiting-spouses flag) as a step
  parent for claimant recognition, the both-parents exception and non-filer
  membership, including step siblings (42 CFR 435.603(b)).
- Add a head's or spouse's co-resident spouse outside the unit to the unit's
  tax household in linked households, deduplicated against same-unit
  membership, claims into the unit and the cohabiting-spouses flag
  (435.603(f)(1), (f)(2), (f)(4)).
- An id matching two members of one group resolves to neither, and a
  co-resident resolution no longer depends on tax unit member order.
- Document the simulation-wide unique id contract for cross-household tax
  units and known claiming units.
- Add the r8 YAML regressions.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…id order; reword the changelog (r8 findings 5-7)

- test_named_parent_must_be_claiming_head_or_spouse: a named parent who is
  another dependent on the claiming return, or files elsewhere, is not the
  claiming taxpayer (42 CFR 435.603(f)(2)(i)).
- test_both_parent_joint_return_requires_same_filing_unit: (f)(2)(ii) holds
  unless the two co-resident parents head one filed joint return.
- Extend the married-dependent property to the spouse's own household with
  and without the cohabiting-spouses flag, and the random-household oracle to
  step parents (435.603(b)).
- test_duplicate_ids_resolve_the_same_way_in_every_member_order.
- Changelog: co-resident ids resolve to that person, the linked both-parent
  exception, the id contract and the whole-simulation zero-id guarantee.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis added a commit that referenced this pull request Oct 6, 2026
…ive dependents the claimant's separate spouse

Two findings from review r1:

- A non-filer household's parents and siblings are read from the whole family,
  which can include people outside the household, such as a grandparent who
  is also a parent there. With signed amounts, a grandmother's $5,000 loss
  cut her granddaughter's household income from $12,000 to $7,000, though
  42 CFR 435.603(f)(3)(iii) leaves her out. Those relatives' amounts are now
  added only when positive: each child's, and each couple's parents' netted
  together, so married parents' losses still offset each other. The
  individual's and spouse's amounts stay signed.
  #9406's co-resident parent IDs fix the
  membership itself.
- A dependent's tax household is the claimant's (435.603(f)(2)), which
  includes the claimant's cohabiting spouse who files separately (f)(4).
  Household size already counted that spouse; household income now does
  too.

Also corrects two comments to the 2025 standard deduction, $15,750.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis added a commit that referenced this pull request Oct 6, 2026
…ive dependents the claimant's separate spouse

Two findings from review r1:

- A non-filer household's parents and siblings are read from the whole family,
  which can include people outside the household, such as a grandparent who
  is also a parent there. With signed amounts, a grandmother's $5,000 loss
  cut her granddaughter's household income from $12,000 to $7,000, though
  42 CFR 435.603(f)(3)(iii) leaves her out. Those relatives' amounts are now
  added only when positive: each child's, and each couple's parents' netted
  together, so married parents' losses still offset each other. The
  individual's and spouse's amounts stay signed.
  #9406's co-resident parent IDs fix the
  membership itself.
- A dependent's tax household is the claimant's (435.603(f)(2)), which
  includes the claimant's cohabiting spouse who files separately (f)(4).
  Household size already counted that spouse; household income now does
  too.

Also corrects two comments to the 2025 standard deduction, $15,750.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis added a commit that referenced this pull request Oct 6, 2026
…ive dependents the claimant's separate spouse

Two findings from review r1:

- A non-filer household's parents and siblings are read from the whole family,
  which can include people outside the household, such as a grandparent who
  is also a parent there. With signed amounts, a grandmother's $5,000 loss
  cut her granddaughter's household income from $12,000 to $7,000, though
  42 CFR 435.603(f)(3)(iii) leaves her out. Those relatives' amounts are now
  added only when positive: each child's, and each couple's parents' netted
  together, so married parents' losses still offset each other. The
  individual's and spouse's amounts stay signed.
  #9406's co-resident parent IDs fix the
  membership itself.
- A dependent's tax household is the claimant's (435.603(f)(2)), which
  includes the claimant's cohabiting spouse who files separately (f)(4).
  Household size already counted that spouse; household income now does
  too.

Also corrects two comments to the 2025 standard deduction, $15,750.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis added a commit that referenced this pull request Oct 6, 2026
…ive dependents the claimant's separate spouse

Two findings from review r1:

- A non-filer household's parents and siblings are read from the whole family,
  which can include people outside the household, such as a grandparent who
  is also a parent there. With signed amounts, a grandmother's $5,000 loss
  cut her granddaughter's household income from $12,000 to $7,000, though
  42 CFR 435.603(f)(3)(iii) leaves her out. Those relatives' amounts are now
  added only when positive: each child's, and each couple's parents' netted
  together, so married parents' losses still offset each other. The
  individual's and spouse's amounts stay signed.
  #9406's co-resident parent IDs fix the
  membership itself.
- A dependent's tax household is the claimant's (435.603(f)(2)), which
  includes the claimant's cohabiting spouse who files separately (f)(4).
  Household size already counted that spouse; household income now does
  too.

Also corrects two comments to the 2025 standard deduction, $15,750.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis and others added 4 commits October 6, 2026 20:55
CI on 38880d4 failed test_tax_dependent_counts_a_co_resident_spouse_exactly_once
at s13_spouse: a 16-year-old spouse alone on a filing return. PE infers only an
adult (age 18 or over) as a tax unit head, so the generator's spouse headed no
return and became a non-filing dependent with zero MAGI, while the property
assumes a taxpayer filing alone. The generator now states every head: the
parent, a child on a return of their own only as an adult (as inferred), and
a spouse off the parent's return at any age. Only minor spouses on their own
returns change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis added a commit that referenced this pull request Oct 7, 2026
…ive dependents the claimant's separate spouse

Two findings from review r1:

- A non-filer household's parents and siblings are read from the whole family,
  which can include people outside the household, such as a grandparent who
  is also a parent there. With signed amounts, a grandmother's $5,000 loss
  cut her granddaughter's household income from $12,000 to $7,000, though
  42 CFR 435.603(f)(3)(iii) leaves her out. Those relatives' amounts are now
  added only when positive: each child's, and each couple's parents' netted
  together, so married parents' losses still offset each other. The
  individual's and spouse's amounts stay signed.
  #9406's co-resident parent IDs fix the
  membership itself.
- A dependent's tax household is the claimant's (435.603(f)(2)), which
  includes the claimant's cohabiting spouse who files separately (f)(4).
  Household size already counted that spouse; household income now does
  too.

Also corrects two comments to the 2025 standard deduction, $15,750.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis added a commit to MaxGhenis/policyengine-us that referenced this pull request Oct 7, 2026
@MaxGhenis
MaxGhenis marked this pull request as ready for review October 7, 2026 11:15
@MaxGhenis
MaxGhenis merged commit f33a4b3 into main Oct 7, 2026
35 checks passed
@MaxGhenis

Copy link
Copy Markdown
Contributor Author

US + core hub merge audit, us#9406 at 409e65e8b29feb87b8fc7cf245b35be28bb6b152 (squash)

  • Review: independent review (Subfleet) APPROVE at 409e65e, METHODOLOGY_CHOICE no, with one P3: a stale legal-uncertainty comment in a test, left for a follow-up.
    • It checks the r8 findings and the spec's tests: the reciprocal spouse, the CA pregnant-spouse counterpart, grandparent/teen-parent/baby, the established stepparent (child size 3, income $60,000), second-slot collisions and the ID contract.
  • Gates checked live at merge:
    • gh pr checks exits 0;
    • MERGEABLE;
    • not a draft;
    • no CHANGES_REQUESTED review;
    • head pinned with --match-head-commit.
  • Impact: a real microsim on the default dataset for 2024 and 2026. Results are exactly zero on is_medicaid_eligible, medicaid, medicaid_household_size, medicaid_household_income, is_chip_eligible, chip, aca_ptc and household_net_income.
    • The dataset has no parent-ID columns, so this is the required all-zero-ID check on real data.
    • Measuring the feature's own effect needs a dataset with populated links.
  • Coordination: the Medicaid joint-floor PR (Net household members' Medicaid MAGI before the zero floor, and give each spouse their own deductions #9860) overlaps these files. The agreed order is signed member income first, then the household zero floor.
  • --admin: used only because a required approving review is the sole block.

@MaxGhenis
MaxGhenis deleted the medicaid-magi-parent-ids branch October 7, 2026 12:37
MaxGhenis added a commit that referenced this pull request Oct 14, 2026
…each spouse their own deductions (#9860)

* Give each spouse their own above-the-line deductions in per-person AGI

adjusted_gross_income_person split every above-the-line deduction other than
the three self-employment ones 50/50 between head and spouse on a joint
return. Each person now takes their own deductions through
above_the_line_deductions_person; only tax-unit-level inputs (HSA, tuition
and fees, domestic production, possession income) are divided equally.
Adds alimony_expense_ald_person, loss_ald_person and
limited_capital_loss_person, moves Medicaid's per-person AGI and Montana's
Social Security worksheet onto the same attribution, and applies Montana's
capital loss rule for spouses filing separately (MCA 15-30-2110(6)).

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

* Split loss_ald into limited_business_loss and the capital loss deduction

loss_ald_person now divides the Section 461(l)-limited business loss by each
spouse's business losses and the rest of loss_ald by their capital losses, so
it stays right when loss_ald gains capital components (#9788). Adds the
filer_share helper used by the per-person deduction variables.

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

* Leave Medicaid AGI to a separate fix

Attributing each spouse's deductions in Medicaid AGI without also netting the
couple's MAGI before medicaid_magi_person's zero floor moved Medicaid
eligibility in both directions; the two belong in one Medicaid change.

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

* WIP: drop the Montana capital loss override (ARM 42.15.206(3)(a)); review r1 fixes; strengthen property test

The property test's new independent 461(l) check still has a test-side
indexing bug: the model gives the right limited_business_loss (256,000 for a
2026 single filer with a 1.5M loss).

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

* Restore main's Medicaid AGI tests after the rebase

* Leave filer amounts recorded on a dependent off the dependent's own deductions

above_the_line_deductions_person gave a tax unit dependent every person-level
deduction recorded on them, including employer adoption assistance and
education savings bond interest, which gov.irs.ald.filer_amounts_recorded_on_dependents
marks as the filer's. Those now count only for the head and spouse, whose
parts already take them up through above_the_line_deductions. The dependent's
part matters once Medicaid AGI reads it for a dependent's own return.

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

* Divide a filer amount recorded on a dependent equally between head and spouse

Education savings bond interest and employer adoption assistance recorded on
a dependent are the filer's (gov.irs.ald.filer_amounts_recorded_on_dependents),
but which spouse's is not recorded. They reached the head's and spouse's parts
through the reconciliation to above_the_line_deductions, in proportion to
each spouse's other deductions, so the spouse's IRA deduction changed the
head's share. They are now divided equally, as the other amounts recorded
only for the return are.

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

* Fix the spouse deduction invariant test's harness and numpy references

- Units that set a tax-unit deduction directly run in their own simulation.
  An input given for one tax unit is an input for every unit in a
  simulation, so one unit's loss_ald set to 0 zeroed every other unit's.
- The Section 461(l) reference keeps each business source's gain and loss
  separate (aggregate income against aggregate deductions), as the model
  does, rather than netting a person's sources first.
- The capital loss reference nets losses against capital gains and
  distributions before the 1211(b) limit, as main does since #9788.
- The alimony identity skips units that set alimony_expense_ald directly:
  alimony_expense_ald_person stays each payer's own alimony there.
- The reference gives a dependent's filer amounts to the head and spouse
  equally.

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

* Give the determinism test's stand-in ALD parameters the filer amount list

above_the_line_deductions_person now reads gov.irs.ald.filer_amounts_recorded_on_dependents.

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

* Floor Michigan and Pennsylvania withholding at zero for a negative spouse AGI

With each spouse's own deductions, a spouse with a business loss or an IRA
deduction above their income has negative AGI, and these two withholding
estimates multiplied it by the rate. Every other state's withholding formula
already floors its base at zero.

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

* Prorate Virginia additions by each spouse's positive federal AGI

va_agi_person divides tax-unit additions by each spouse's share of federal
AGI. A spouse with negative AGI got a negative share and the other spouse
more than all of it. A spouse with negative AGI now gets none, as
#9878 does for Delaware's itemized deductions.

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

* Skip only directly set alimony in the spouse identity check

Directly set loss_ald still reconciles through loss_ald_person, so its identity is checked.

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

* Keep Montana's losses divided equally between spouses until its own rules are modeled

Montana spouses who filed separately on one form through 2023 allocate
capital losses by one of two elections (ARM 42.15.206(3)) and recompute the
Section 461(l) limit with the single filer's threshold. Neither is modeled.
Giving each spouse their own federal losses instead of half moved Montana
away from both. With one spouse's $1,000 gain against the other's $3,000
loss, the loss spouse's AGI was $7,000, though separate tracking caps the
loss at $1,500 ($8,500). A $600,000 business loss on one spouse used the
joint limit, lowering tax by $45.65 against main.

mt_loss_ald_reallocation adds back to each spouse's federal AGI the
difference between their own loss_ald_person and an equal share of the
return's loss_ald, so Montana divides losses as before. The IRA and other
deductions still go to the spouse who has them (former MCA 15-30-2110(8);
ARM 42.15.206(1)).

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

* Base Montana's joint standard deduction and medical floor on the joint return's AGI

mt_standard_deduction_joint and mt_medical_expense_deduction_joint added each
spouse's mt_agi_indiv, which is floored at zero. With each spouse's own
deductions, a spousal IRA above that spouse's income was lost to the floor:
head wages $30,000 and a $6,000 spousal IRA gave a joint deduction of $6,000
(20% of $30,000) instead of $4,920 (the floor, 20% of the joint $24,000
being less). Both now read mt_agi_joint, the joint column's Montana AGI
through 2023. Their tests take mt_agi_joint as the input. Adds the spousal
IRA case, a single filer's loss and a case with student loan interest and an
IRA in each spouse's own column.

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

* Use the joint Montana AGI for the medical floor only through 2023

Review r5: the medical floor had no year gate, so reading mt_agi_joint also
changed 2024 and later, when mt_agi_indiv is zero for everyone and main's
floor was zero. From 2024 Montana starts from federal itemized deductions,
whose floor is a share of federal AGI, a separate fix. The joint AGI now
applies while married filing separately on the same return is allowed
(through 2023); 2024 on is unchanged from main.

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

* Note that Montana's 2024+ medical floor is zero, as on main

Comment only (review r6).

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

* Limit DC separate-filing capital losses per spouse; stop Montana spouse allocation from 2024

Per Max's d1014 ruling:
- DC: spouses filing separately on the same return each deduct their own
  capital losses up to their own capital gains plus the $1,500 married filing
  separately limit (D-40 instructions, line c), via
  dc_separate_capital_loss_adjustment. It applies only to the separate column;
  dc_taxable_income_joint keeps the federal joint deduction.
- Montana: mt_loss_ald_reallocation and mt_applicable_ald_deductions apply only
  while spouses may file separately on the same form (through 2023).

Adds YAML cases and DC/Montana invariants to the spouse property test.

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

* Net household members' Medicaid MAGI before the zero floor, and attribute each spouse's own deductions

medicaid_magi_person floored each person's MAGI at zero, and
medicaid_household_income added the floored amounts, so a spouse's business
loss never offset the other spouse's wages on their joint return. Household
income is the sum of every member's MAGI-based income (42 CFR 435.603(d)(1)),
and the 36B methodology the regulation adopts (435.603(e)) combines the
taxpayer's and dependents' modified AGIs "even if one or both of them are
negative" before entering a negative total as zero (Form 8962, line 3).
medicaid_magi_person is now unfloored and medicaid_household_income floors
only the household's total.

medicaid_adjusted_gross_income_person now subtracts
above_the_line_deductions_person, so each spouse's own IRA deduction,
student loan interest, alimony and business and capital losses lower only
their own amount, and a dependent's own deductions, alimony paid included,
lower the dependent's own AGI. Tax-unit-only MAGI additions are divided
equally between the head and spouse with filer_share.

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

* Count only gross income other than wages and self-employment as unearned in the Medicaid filing test

medicaid_person_is_required_to_file took unearned income as gross income less
earned_income. Gross income counts each source's positive amount, so a
self-employment loss made other income look unearned: a dependent with a
5,000 loss and 100 of interest had 5,100 of unearned income and was required
to file. With household members' MAGI no longer floored, that would count the
dependent's negative income. Unearned income is now gross income less the
positive wage and self-employment sources it includes (Pub. 501, Table 2),
which also stops 401(k) deferrals, left out of box 1 wages, from hiding
unearned income.

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

* Add property tests for Medicaid household income with members' losses

Hypothesis draws batches of joint filers with dependent children, non-filing
couples, cohabiting spouses filing separately and single filers, with losses,
own deductions and children who may be required to file. For every
household: joint filers' household income is max(0, J + D), with J the
couple's MAGI figured from the tax unit's AGI; the spouses' MAGIs add up to
J; spouses outside a joint return are netted before the floor; income is
never negative and is zero exactly when the unfloored sum is not positive;
and three metamorphic relations hold (the spouse's own deductions never
change the head's, swapping spouses changes nothing, and lowering the
spouse's self-employment income never raises household income).

20 examples plus a seeded population of 200 households: 44 s wall and
1.17 GB peak locally (macOS, loaded host).

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

* Keep family-wide relatives' losses out of non-filer households, and give dependents the claimant's separate spouse

Two findings from review r1:

- A non-filer household's parents and siblings are read from the whole family,
  which can include people outside the household, such as a grandparent who
  is also a parent there. With signed amounts, a grandmother's $5,000 loss
  cut her granddaughter's household income from $12,000 to $7,000, though
  42 CFR 435.603(f)(3)(iii) leaves her out. Those relatives' amounts are now
  added only when positive: each child's, and each couple's parents' netted
  together, so married parents' losses still offset each other. The
  individual's and spouse's amounts stay signed.
  #9406's co-resident parent IDs fix the
  membership itself.
- A dependent's tax household is the claimant's (435.603(f)(2)), which
  includes the claimant's cohabiting spouse who files separately (f)(4).
  Household size already counted that spouse; household income now does
  too.

Also corrects two comments to the 2025 standard deduction, $15,750.

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

* Document the non-filer limits and test married non-filer parents

Review r2 notes:
- A non-filer child's own amount and their siblings' enter with the
  family's children, only when positive, and unmarried co-resident parents
  are each counted only when positive. The documentation now says so,
  rather than "always added signed".
- Medicaid AGI documentation says a dependent's own business and capital
  losses are not modeled.
- New case: married non-filer parents, $12,000 of wages and a $5,000
  business loss, give their child's household $7,000.

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

* Base DC's separate-filing capital adjustment on the capital part of loss_ald

Review r7: dc_separate_capital_loss_adjustment removed the computed capital
loss deduction even when loss_ald was set directly, so it took out an amount
federal AGI did not contain. It now removes the capital part of each spouse's
loss_ald_person (loss_ald less the business loss, by own capital losses). Adds
YAML cases with loss_ald set to $0 and $1,000, and the property test checks
every unit against that capital part, keeping the nonnegative couple sum for
computed units only.

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

* Take DC's self-employment loss add-back from the business part of loss_ald

Review r8: with loss_ald set directly and both business and capital losses,
dc_self_employment_loss_addition isolated the business loss by subtracting
the computed capital deduction, while loss_ald_person (and so federal AGI and
the DC capital adjustment) takes the business part first. Use
min(limited_business_loss, loss_ald), as loss_ald_person does; the amount is
unchanged when loss_ald is computed. Adds computed and direct mixed-loss YAML
cases.

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

* Date linked Medicaid household property IDs to the tested year

* Reconcile HSA attribution coverage with main's person input

* Describe how a joint business loss is divided in Medicaid person AGI

The documentation said a spouse's business loss lowers only that spouse's
amount. loss_ald_person divides it in proportion to each spouse's own net
business losses, and equally when neither has one. Documentation only.

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

* Point the Medicaid person AGI documentation to loss_ald_person

Restating the allocation left out the equal share of a Form 4797 loss.
Refer to loss_ald_person instead. Documentation only.

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

---------

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.

Medicaid MAGI household: 'living with both parents' proxy pools grandparent income in multigenerational households

1 participant