PolicyEngine / PolicyEngine/policyengine-us
SNAP excludes a lone minor household head's earnings — 7 CFR 273.9(c)(7) parental-control condition not modeled
- Dominant language
- Python
- Stars
- 162
- Forks
- 212
- Avg merge
- 3d 3h
- Merged PRs (30d)
- 97
Description
## Summary
`snap_excluded_child_earner` excludes the employment income of any under-18 K-12 student, including a minor who is their **own** household head living alone. The regulatory exclusion applies only to a child **under the parental control of another household member**; a lone minor's wages are countable.
7 CFR 273.1(b)(1)(iii) (household composition) conditions child inclusion on living "with and \[being\] under the parental control of a household member other than his or her parent," and 273.9(c)'s minor-earnings exclusions are likewise tied to being "under the parental control of another \[adult\] member" (see also the WIA/WIOA clause in 273.9(c), which applies "to household members under 19 years of age who are under the parental control of another adult member"). A one-person household headed by a 15–17-year-old has no such other member, so the student-earnings exclusion cannot apply.
## Reproduction (verified on 1.767.3 and 1.752.2, core 3.30.3/3.28.0)
One person, age 17, `employment_income` = $217,027.52, own household (AL, 2026):
- `is_in_k12_school` imputes/defaults to True
- `snap_excluded_child_earner` = **True**
- `snap_earned_income` = **$0**, gross/net income tests pass
- `is_snap_eligible` = **True**, `snap` = **$298/yr**
Age-18 control with identical income: earnings counted, correctly ineligible. Same result for age 15 (NC). Full script attached below (adapted from our oracle-comparison harness; found via Axiom↔PolicyEngine SNAP conformance sweeps on Enhanced CPS, where a synthetic clone mass point at $217,027.52 produces 12 such records across AL/MA/NC/SC/TN).
repro.py
```python
"""Reproduce the SNAP lone-minor income bug in PolicyEngine US 1.767.3."""
from importlib.metadata import version
from policyengine_us import Simulation
YEAR = "2026"
MONTH = "2026-01"
ANNUAL_EARNINGS = 217_027.52
def make_simulation(
age: int, state: str, in_k12: bool | None = None
) -> Simulation:
person = {
"age": {YEAR: age},
"employment_income": {YEAR: ANNUAL_EARNINGS},
"is_household_head": True,
}
if in_k12 is not None:
person["is_in_k12_school"] = {YEAR: in_k12}
return Simulation(
situation={
"people": {"person": person},
"families": {"family": {"members": ["person"]}},
"marital_units": {"marital_unit": {"members": ["person"]}},
"spm_units": {"spm_unit": {"members": ["person"]}},
"tax_units": {"tax_unit": {"members": ["person"]}},
"households": {
"household": {
"members": ["person"],
"state_code": {YEAR: state},
}
},
}
)
def scalar(simulation: Simulation, variable: str, period: str):
return simulation.calculate(variable, period)[0].item()
def show(
label: str, age: int, state: str, in_k12: bool | None = None
) -> None:
simulation = make_simulation(age, state, in_k12)
variables = {
"employment_income_annual": ("employment_income", YEAR),
"is_in_k12_school": ("is_in_k12_school", YEAR),
"monthly_age": ("monthly_age", MONTH),
"is_household_head": ("is_household_head", YEAR),
"is_tax_unit_head": ("is_tax_unit_head", YEAR),
"is_tax_unit_dependent": ("is_tax_unit_dependent", YEAR),
"spm_unit_size": ("spm_unit_size", YEAR),
"snap_unit_size": ("snap_unit_size", MONTH),
"snap_excluded_child_earner": (
"snap_excluded_child_earner",
MONTH,
),
"snap_countable_earner": ("snap_countable_earner", MONTH),
"snap_earned_income_person": ("snap_earned_income_person", MONTH),
"snap_earned_income": ("snap_earned_income", MONTH),
"snap_gross_income": ("snap_gross_income", MONTH),
"meets_snap_gross_income_test": (
"meets_snap_gross_income_test",
MONTH,
),
"snap_net_income": ("snap_net_income", MONTH),
"meets_snap_net_income_test": (
"meets_snap_net_income_test",
MONTH,
),
"is_snap_eligible": ("is_snap_eligible", MONTH),
"snap": ("snap", MONTH),
}
print(f"\n{label}")
for label_name, (variable, period) in variables.items():
print(f"{label_name}={scalar(simulation, variable, period)}")
print(f"policyengine-us={version('policyengine-us')}")
print(f"policyengine-core={version('policyengine-core')}")
print(f"spm-calculator={version('spm-calculator')}")
show("AL, age 17, K-12 imputed", 17, "AL")
show("NC, age 15, K-12 imputed", 15, "NC")
show("AL, age 17, K-12 explicitly false", 17, "AL", False)
show("AL, age 18, K-12 imputed", 18, "AL")
```
## Expected
The child-earner exclusion should require the child to be under the parental control of **another** SNAP household member (not the child themself as head of a one-person unit), per 7 CFR 273.1(b)(1)(iii) and 273.9(c).
## Impact
Any lone-minor unit with earnings is scored eligible regardless of income. On Enhanced CPS 2024 this affects 12 records concentrated at a clone mass point.
Contributor guide
Research direction
Start by tracing the snap_excluded_child_earner calculation and its relationship to snap_countable_earner and snap_earned_income_person. Run the supplied reproduction for a one-person 15–17-year-old household and compare it with the age-18 case. Done means a lone minor's earnings are countable while the child-earner exclusion still applies when another adult household member has parental control.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- backend
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 68/100