Skip to content

Age-ordered tax-unit roles make an unmarried adult child the spouse (enhanced CPS 1.78.2): joint filing, lost EITC child, zero CDCC #9868

Open

Description

@PavelMakarchuk

Summary

is_tax_unit_spouse (policyengine-us 1.811.2) makes the oldest non-head adult of a tax unit its spouse:

eligible = adult & ~head & ~is_separated
return person.get_rank(tax_unit, -age, eligible) == 0

It does not look at marital units. On the enhanced CPS (1.78.2), which ships marital units but no tax-unit roles, a single parent's adult child (18 or older, is_child false) who is in the parent's tax unit becomes the "spouse": the unit files JOINT, the child stops being a dependent or qualifying child, and the CDCC's lower-earner limit reads the child's earnings. In the 2025 simulation 1,200 of 20,246 tax-unit spouses are not in the head's marital unit; their median age is 21 (310 are 18), the median age gap to the head is 28 years, and 99.5% of the heads have their own children in the household.

A joint return requires a married couple (26 USC 6013); an 18-year-old child of the filer is a qualifying child for the EITC (26 USC 152(c)(3)(A)); a single parent's CDCC expenses are capped at the parent's own earnings (26 USC 21(d)(1)(A)).

PR #9617 fixes roles for datasets that supply them; the enhanced CPS does not, and its marital units already identify the couples.

Repro (policyengine-us 1.811.2, 2025)

from policyengine_us import Simulation
s = Simulation(situation={
    "people": {"mom": {"age": {2025: 45}, "employment_income": {2025: 20_000}}, "son": {"age": {2025: 18}}},
    "tax_units": {"t": {"members": ["mom", "son"]}},
    "marital_units": {"m1": {"members": ["mom"]}, "m2": {"members": ["son"]}},
    "spm_units": {"s": {"members": ["mom", "son"]}}, "families": {"f": {"members": ["mom", "son"]}},
    "households": {"h": {"members": ["mom", "son"], "state_name": {2025: "TX"}}},
})
print(s.calculate("is_tax_unit_spouse", 2025), s.calculate("filing_status", 2025),
      s.calculate("eitc_child_count", 2025), s.calculate("eitc", 2025))
  • Observed: son is the spouse ([False, True]), filing status JOINT, eitc_child_count 0, eitc $475.35 (childless joint: 649 - 7.65% x (20,000 - 17,730)).
  • Expected: head of household with one qualifying child: eitc $4,328.
  • CDCC: the same mother with $60,000 of wages, a 5-year-old and $5,000 of child care gets min_head_spouse_earned 0 and cdcc 0; expected 20% x $3,000 = $600 (if the son is a student, PE deems him $250 a month instead).

Impact (PRD comparison, 2025 aligned view, unweighted CPS tax units)

eitc_pe_adult_child_as_spouse: 107 units, $209,709, 30.8% of the EITC lane's gap. cdcc_pe_adult_child_as_spouse: 31 units, $15,649. The same units are joint filers in every PE tax computation.

Expected fix

When marital units are present, pick the spouse only among adults in the head's marital unit (or fall back to age order only when the dataset has no marital units).

Activity

  1. PavelMakarchuk commented on Oct 6, 2026

    @PavelMakarchuk
    CollaboratorAuthor

    Related work: #9617 (open) lets a dataset supply each person's tax-unit role (tax_unit_role_input) and the unit's filing status, and #9630 (merged) keeps input dependents out of head/spouse inference. Neither changes the age-ordered fallback, so a dataset without roles (such as enhanced CPS 1.78.2) still makes the second-oldest adult the spouse even when that person is not married to the head. A possible fallback fix: count a person as the spouse only when they share the head's marital unit. It touches is_tax_unit_spouse.py, which #9617 also changes, so it is probably best done in or after #9617 rather than in a parallel PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions