Skip to content

Count South Carolina TANF unearned income at gross amounts and leave SSI recipients out of the benefit group - #10184

Open
MaxGhenis wants to merge 9 commits into
mainfrom
sc-tanf-unearned-gross
Open

MaxGhenis wants to merge 9 commits into
mainfrom
sc-tanf-unearned-gross

Conversation

@MaxGhenis

@MaxGhenis MaxGhenis commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

Summary

gov.states.sc.tanf.income.unearned was copied from the IRS gross income sources (gov.irs.gross_income.sources minus its earned items). So South Carolina TANF counted only the federally taxable part of Social Security (taxable_social_security, usually $0 for TANF families), unemployment compensation (2020: up to $10,200 excluded under IRC 85(c)), pensions, interest and retirement account withdrawals. It also counted discharged debt. It left out child support, alimony, VA benefits, workers' compensation and other cash income the TANF Policy Manual counts.

This PR rebuilds the list from the manual's Section 7.7 chart at gross amounts and applies the chart's two rules that touch unearned income:

  • the $400-a-year interest and dividend exclusion;
  • the SSI recipient exclusion, which also takes SSI recipients out of the benefit group size behind the need standard.

What the manual says

SC DSS TANF Policy Manual, Volume 67 (effective August 1, 2026). Chapter 7 is word for word the same as Volume 65, which the repo cited before, apart from a one-page shift. PDF page numbers:

  • §7.1, p.102: "Income is money received by a BG member (s) from any source." Unearned income is "Any income not defined as earned income"; in-kind gains are excluded.
  • §7.7 chart, pp.107-120:
    • SSA benefits: "Count gross amount. This includes Medicare premium" (p.116).
    • Counted (Y): unemployment compensation (p.118), pension (p.115), retirement pay including railroad retirement (p.116), VA (p.119), workers' compensation (p.120), income maintenance and disability insurance (p.113), alimony, cash contributions, child support and capital gains (other) (p.108), and rent (p.116).
    • Farm income is earned self-employment income (p.112).
    • Interest, dividends and royalties: "Up to $400 per BG annually is excluded. Income above $400 must be averaged over the period of the remaining 12 months" (p.113).
    • SSI benefit: "Exclude the SSI recipient, his/her income, resources and SSI payments in determining benefits for other BG members" (p.117).
  • §3.6, p.56: the same SSI exclusion, and "If an SSI child is the only child in the home, include the parent(s) as the only BG member."
  • §8.15, p.133: the need standard is 50% of the poverty guideline for the number in the benefit group, revised each October 1.

Changes

Item Before After Manual
Social Security taxable_social_security social_security (gross) p.116
Unemployment compensation taxable_unemployment_compensation total_unemployment_compensation (reported UC, or modeled state UI when none is reported, as in the other TANF lists since #9558) p.118
Pensions taxable_pension_income pension_income (taxable + tax-exempt) p.115
Retirement account withdrawals taxable_retirement_distributions retirement_distributions (gross) §7.1
Interest taxable_interest_income interest_income (incl. tax-exempt), floored at zero, after the $400 exclusion p.113
Dividends dividend_income dividend_income, floored at zero, after the $400 exclusion p.113
Discharged debt debt_relief not counted (not money received) p.102
Farm operations income unearned earned (gets the earned income disregards) p.112
Added child_support_received, alimony_income, veterans_benefits, workers_compensation, disability_benefits, survivor_benefits, railroad_benefits, financial_assistance (cash contributions), gi_cash_assistance and estate_income (settlements stay resources, p.92) pp.108-120, p.102
Kept illicit_income p.102
Net amounts that can be losses summed with their signs, so a loss offset other income, including another member's capital_gains, rental_income, farm_rent_income, partnership_s_corp_income, estate_income and miscellaneous_income move to unearned/positive_sources.yaml. Each counts per person only when positive: a net loss is not money received, and §8.16 has no step that deducts one pp.102, 108, 116, 134
SSI recipients counted in size, income and resources sc_tanf_ssi_recipient (SSI > 0 or receives_ssi). Their earned and unearned income is excluded; they are left out of the new sc_tanf_benefit_group_size; and the new sc_tanf_countable_resources subtracts their own bank, stock and bond assets from spm_unit_cash_assets, as Missouri's TANF does (an aggregate-only asset input is counted as given) pp.56, 117
Need standard tanf_fpg × 0.5 (whole SPM unit), computed in three places new sc_tanf_need_standard: 50% of the guideline for the benefit group size, used by the payment, the need test and the gross income limit p.133

The list moved from income/unearned.yaml to income/unearned/sources.yaml, beside income/unearned/interest_and_dividends/{sources,exclusion}.yaml. Only sc_tanf_gross_unearned_income reads it.

Judgment calls and known limits

  • Retirement account withdrawals stay counted, now at the gross amount. IRA and Keogh balances are resources (§6.6, p.93) and deferred compensation is excluded "until the employee withdraws funds" (p.92). The manual doesn't say whether periodic withdrawals are income. This PR keeps main's choice to count them and applies the gross-amount reading.
  • Royalties are counted but not excluded. The model has no royalty input; rental_income holds rent and royalties together, and rent is counted in full (p.116).
  • military_retirement_pay is not added. The model treats it as a memo amount that is also entered in pension income: federal gross income never adds it, yet Connecticut and Iowa subtract it from AGI, and SC's retirement deduction nets it against the cap. Adding it would double-count.
  • Partnership/S-corp, miscellaneous and illicit income stay unearned, as before. Active business income would be earned, but the inputs don't say which it is.
  • Netting within one input stands. Net rent after costs, short- against long-term gains in capital_gains, and gains against losses within one person's estate_income all still net, because the inputs carry only the net amount.
  • Not in this PR. Follow-ups are split out as tasks:
    • the dependent child earned income exclusion (§7.5) and the deduction for child support paid outside the home (§§8.11, 8.16);
    • dropping cents from the need, gross and payment standards as the §8.15 table does (the model gives $435.28 where the table says $434 for three).

Overlap with #9687

#9687 (approved; #9558 merged on 2026-10-13) swaps only the two taxable items for total_unemployment_compensation and social_security. This PR makes the same two choices and covers the rest of what #9677 asked for. The SC list has moved to income/unearned/sources.yaml, so whichever PR lands second needs a small rebase: keep the moved file, and renumber the integration cases (both add a Case 9). Coordination comment: #9687 (comment).

Invariants

These hold for every benefit group and month. policyengine_us/tests/test_sc_tanf_unearned_income_invariants.py checks them with Hypothesis over generated groups in one vectorized simulation, against a reference written from the manual rather than from the parameter list.

  1. Each member's gross unearned income = counted sources + floored interest and dividends − a share of min(group interest and dividends, 400/12) proportional to the member's interest and dividends. SSI recipients count nothing and take no share.
  2. The exclusion is conserved across the group, and no member's share exceeds their own interest and dividends.
  3. Discharged debt and wages never move unearned income, and negative interest never offsets other income.
  4. Gross earned income is wages, or 0 for an SSI recipient.
  5. Unearned income is monotone in interest: raising the head's interest by d adds between 0 and d/12, exactly the rise in floored interest once the group is past the exclusion, and 0 for an SSI head.
  6. The benefit group size counts the members who don't receive SSI, and the need standard is 50% of the guideline for that size (0 for an empty group).
  7. Net amounts that can be losses (capital gains, rent, partnership, trust and estate, miscellaneous) count only when positive, so a loss never offsets another source or another member's income.
  8. Countable resources are the unit's cash assets less SSI recipients' own bank, stock and bond assets.

Mutation checks: each of these is caught by the property test.

  • taxable_social_security back in the list;
  • dropping the SSI income mask;
  • dropping the interest floor;
  • counting SSI members in the size;
  • counting signed estate income without the floor;
  • dropping the SSI asset subtraction.

Tests

  • sc_tanf_gross_unearned_income.yaml (18 cases): gross Social Security; 2020 unemployment; pensions; ten non-taxable sources; withdrawals vs discharged debt; farm income; the exclusion below, at and above $400, including tax-exempt interest; sharing across members; SSI recipients; negative interest; legacy dividend_income; estate, partnership, capital and rental losses on the same and another member.
  • sc_tanf_gross_earned_income.yaml, sc_tanf_ssi_recipient.yaml, sc_tanf_benefit_group_size.yaml, sc_tanf_need_standard.yaml (including the October update) and sc_tanf_countable_resources.yaml (an SSI child's bank assets, an SSI adult's stocks and bonds, an aggregate-only input).
  • integration.yaml:
    • Case 5 now enters Social Security rather than its taxable part.
    • Case 9: child support, a child's Social Security and interest, $131.83.
    • Case 10: an SSI child with income, size 2, $344.58.
    • Case 11: the only child on SSI, parent alone, $253.89.
    • Case 12: an SSI child's $1,800 of savings no longer pushes the unit over the $2,500 limit, $344.58.
  • Tests that set spm_unit_size now set sc_tanf_benefit_group_size, with the same expected values.

Local runs (macOS) at 4b419e5, plus 96245b0's unearned YAML file (18 cases) and property test (2 passed):

  • SC TANF and SC CCAP YAML folders: 226 passed.
  • Code-health and parameter checks: 197 passed at 530cc15; the unemployment reader and person-adds guards still pass at 4b419e5.
  • Property test alone: 13.8 s of test time, 46.6 s wall including setup, 1.09 GB peak RSS.

CI cost (Linux, run 38330000257 at 91d0d3f, artifact rest-test-timings):

  • The property test runs in the Rest job's remaining group, where the model is already loaded: 1.39 s plus 0.07 s.
  • The group peaked at 7,649,148 kB over 14:05 wall, against 7,643,304 kB over 18:21 for another PR's same-day run (38375069896).
  • The addition costs about 6 MB of peak memory. The wall-clock difference is runner noise.

Review

Independent GPT-6.1 Sol review (Subfleet, hard tier, workspace-write, tests run in its own copy):

  • 530cc15, changes requested. The blocking finding: SSI income was excluded while SSI members still counted in the need standard. 39248f1 fixed it, along with the negative-interest floor, the legacy dividend input, guaranteed income and trust income. It also reverted the retirement-withdrawal exclusion and answered the royalty and military-pay findings.
  • 39248f1, changes requested. SSI recipients' resources were still counted, and signed estate income let losses offset other income. f6340c2 fixed both. The reviewer accepted royalties and military pay as limitations that predate the PR.
  • 4b419e5, approved. No blocking findings. The SSI resource and estate fixes were verified by execution and mutation. Its main non-blocking point was that the other net sources still let losses offset other income; 96245b0 fixed that.
  • 96245b0, approved (delta review).

Findings and responses are kept on the hub host in ~/reviews/sc-tanf-unearned-2026-10/.

Hub microsimulation impact (current head)

A real microsimulation on the default dataset, main at the merge base ef3fdff039 against this PR's head 96245b0f7a, for 2025 and 2026. The runs went one at a time under the shared heavy-job lock. Sums are weighted.

Output 2025 change 2026 change 2025 records changed (weighted) 2026 records changed (weighted)
sc_tanf -8.83M -7.01M 20 (55,615) 20 (56,012)
tanf -6.54M -5.60M 9 (13,857) 9 (13,956)
sc_tanf_gross_unearned_income +33.96bn +35.65bn 1,053 (2,194,395) 1,053 (2,210,066)
sc_tanf_gross_earned_income -504.65M -530.33M 31 (133,315) 31 (134,267)
sc_tanf_countable_income +33.62bn +35.30bn 673 (1,469,511) 673 (1,480,005)
spm_unit_benefits -4.55M -3.90M 9 (13,857) 9 (13,956)
state_income_tax +0 +0 0 (0) 0 (0)
income_tax +0 +0 0 (0) 0 (0)
household_net_income -4.55M -3.90M 9 (13,857) 9 (13,956)
household_benefits -4.55M -3.90M 9 (13,857) 9 (13,956)

Reading the table:

  • sc_tanf is the benefit every eligible unit would get. tanf adds state TANF only for units flagged takes_up_tanf_if_eligible; its change comes from 9 South Carolina records.
  • Household benefits fall by $1.99M less than tanf in 2025 and $1.70M less in 2026, so other benefits rise by those amounts. This run doesn't break them out by program.
  • Flooring the net-loss sources in 96245b0 raised the counted-income totals but left every benefit row unchanged from the 4b419e5 run.
  • The two sc_tanf_*_income rows sum monthly amounts over every South Carolina person or unit for the year, eligible or not; the formulas aren't gated on eligibility. They show how much more income now counts, not benefit dollars.
  • The earned-income row nets this PR's only two changes to earned income: SSI recipients' wages leave it, and farm operations income enters it.
  • sc_tanf_need_standard and sc_tanf_countable_resources are new, so they have no base to compare.

Comparison files are in fixes/10184-impact/ on the hub host.

axiom: TheAxiomFoundation/rulespec-us#1728 queued

🤖 Generated with Claude Code

MaxGhenis and others added 2 commits October 12, 2026 19:04
The SC TANF unearned income list was copied from the IRS gross income
sources, so it counted only the taxable part of Social Security,
unemployment compensation, pensions and interest. The TANF Policy Manual
(Volume 67, Sections 7.1 and 7.7) defines income as money received from
any source and counts SSA benefits at their gross amount.

Rebuild the list from the Section 7.7 chart: gross Social Security,
unemployment compensation, pensions and interest; add child support,
alimony, veterans benefits, workers' compensation, disability insurance,
survivor and railroad retirement benefits and cash contributions; drop
retirement account distributions (Section 6.6 counts the balances as
resources) and discharged debt; move farm income to earned income.
Apply the $400 per benefit group annual exclusion of interest, dividends
and royalties, and exclude SSI recipients' income.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add YAML tests for gross Social Security and 2020 unemployment
compensation, gross pensions, the non-taxable sources the chart counts,
the excluded retirement distributions and discharged debt, farm income
as earned income, the $400 interest and dividend exclusion and its
sharing across the benefit group, and SSI recipients. Integration Case 5
now enters Social Security instead of its taxable part, and Case 9 works
a full benefit from child support, a child's Social Security and
interest.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis and others added 3 commits October 12, 2026 19:46
Check, over generated benefit groups in one vectorized simulation, that
gross unearned income matches a reference written from the manual's
Section 7.7 chart, that the $400 interest and dividend exclusion is
conserved across members and never exceeds a member's own interest and
dividends, that retirement withdrawals, discharged debt and wages are
ignored, that SSI recipients count nothing, and that unearned income is
monotone in interest. YAML cannot express these identities over
arbitrary member mixes. Locally: 25 s of test time, 976 MB peak RSS.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Address the independent review of 530cc15:

- Section 3.6 (page 56) excludes an SSI recipient living with the
  benefit group, not only his or her income. Add
  sc_tanf_benefit_group_size and sc_tanf_need_standard (50% of the
  poverty guideline for that size, Section 8.15) and use the need
  standard in the payment, the need test and the gross income limit, so
  an SSI member's needs are no longer counted while their income is
  excluded.
- Floor each member's interest and dividends at zero before the $400
  exclusion, so negative interest cannot offset other income.
- Read dividends through dividend_income, which also carries legacy
  dividend inputs.
- Count guaranteed income and trust income, which are money received
  (Section 7.1); estate settlements remain resources.
- Keep counting retirement account withdrawals, now at the gross amount:
  the manual counts IRA balances as resources but does not say periodic
  withdrawals are not income.

Tests that set spm_unit_size now set the benefit group size.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Volume 67's table lists the 2025 guideline; the January 2025 property
test uses the October 2024 guideline from Volume 65 (PDF page 132).
Docstring and comments only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@MaxGhenis MaxGhenis changed the title Count South Carolina TANF unearned income at gross amounts Count South Carolina TANF unearned income at gross amounts and leave SSI recipients out of the benefit group Oct 13, 2026
@MaxGhenis
MaxGhenis marked this pull request as ready for review October 13, 2026 01:00
@codecov

codecov Bot commented Oct 13, 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 (96245b0).
⚠️ Report is 493 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff            @@
##              main    #10184   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files            4        10    +6     
  Lines           76       147   +71     
  Branches         2         2           
=========================================
+ Hits            76       147   +71     
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 4 commits October 14, 2026 10:49
Address the re-review of 39248f1:

- Sections 3.6 and 7.7 exclude the SSI recipient's resources as well as
  income and needs. Add sc_tanf_countable_resources, which subtracts SSI
  recipients' own bank, stock and bond assets from spm_unit_cash_assets
  (as Missouri's TANF does), and use it in the resource test. An
  aggregate-only asset input is counted as given.
- Count estate_income only when positive, through a positive_sources
  list: a pass-through loss is not money received (Section 7.1) and
  could otherwise offset a pension or another member's income.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#9558 merged, so income flows read total_unemployment_compensation
(reported UC, or modeled state UI when none is reported), and the
unemployment reader guard now requires it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Round-3 review (approved 4b419e5) noted that the money-received reading
behind the estate floor applies equally to the other net amounts in the
list. Move capital gains, rental, farm rent, partnership/S-corp and
miscellaneous income into positive_sources with estate income, so a net
loss counts as zero instead of offsetting another source or another
member's income (Section 7.1; Section 8.16 allows no such deduction).
This netting predates the PR: main summed these sources with their signs.

Also read each positive source with add() so any entity works, and put
the property test's list comments above their lists.

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

Copy link
Copy Markdown
Contributor Author

Hand-off to the US + core hub.

  • Head: 96245b0f7a60ef7ca480043b9ce4550fd30c972a. Base: main, with upstream main ef3fdff039 merged in.

  • Independent review: GPT-6.1 Sol on a Subfleet hard-tier lane, -s workspace-write, which ran the YAML and property tests in its own copy.

    • 530cc15: changes requested.
    • 39248f1: changes requested.
    • 4b419e5: approved.
    • 96245b0: approved (delta review of the change made in response to round 3's non-blocking finding).

    Findings and responses are in ~/reviews/sc-tanf-unearned-2026-10/: review-sol-*.md and review-response-*.md.

  • Passed: 28 CI checks on 96245b0.

  • Pending: Python tests / Full Suite - Rest (Python + variables).

  • Local runs:

    • SC TANF and SC CCAP YAML folders: 226 passed at 4b419e5.
    • Unearned YAML at 96245b0: 18 passed.
    • Property test at 96245b0: 2 passed.
    • Code-health checks: 197 passed at 530cc15.
    • Unemployment-reader and person-adds guards: pass at 4b419e5.
  • Impact: the full-file pair at 96245b0 is in the PR body (fixes/10184-impact/).

  • Depends on / merge order: Count gross unemployment compensation and Social Security in South Carolina TANF #9687 (DTrim99, approved by @hua7450) changes the same SC unearned list, swapping only Social Security and UC to total_unemployment_compensation and social_security. This PR makes the same two choices and moves the list to income/unearned/sources.yaml. Whichever lands second needs a small rebase: keep the moved file, and renumber integration.yaml (both add a Case 9). Coordination comment: Count gross unemployment compensation and Social Security in South Carolina TANF #9687 (comment).

  • Axiom: Encode South Carolina TANF unearned income and benefit group (Policy Manual Vol. 67 §§3.6, 7.1, 7.7, 8.15) TheAxiomFoundation/rulespec-us#1728, queued. The encoder's pinned corpus release us-rulespec-2026-08-08-obbb-alien-snap predates the SC TANF manual scope, and a signed run needs Max's go.

  • Holds and decisions: none.

  • Follow-ups owed (chips): the dependent-child earned income exclusion and the child-support-paid deduction (§§7.5, 8.11, 8.16); whole-dollar truncation of the §8.15 standards.

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