Skip to content

Commit 6edb9e7

Browse files
hua7450claude
andauthored
SSI spousal deeming uses wrong FBR: couples lose /month when deeming applies (#6843)
* SSI spousal deeming uses wrong FBR: couples lose /month when deeming applies Fixes #6842 * Fix SSI spousal deeming to correctly use couple FBR instead of individual FBR This commit fixes a critical bug in SSI spousal deeming calculations and improves code organization: 1. **Main Fix: Use couple FBR when deeming applies** - Modified `ssi_amount_if_eligible.py` to use couple rate ($1,450) instead of individual rate ($967) when spousal deeming applies - This corrects a $483/month ($5,796/year) underpayment for couples where only one spouse is eligible and the other has income exceeding the deeming threshold - Added mathematical proof in comments explaining why the regulatory cap is redundant 2. **New variable: is_ssi_spousal_deeming** - Created helper variable to determine when spousal deeming applies (spouse's countable income > $483/month) - Consolidates threshold logic in one place to avoid duplication - Clear boolean naming convention consistent with other SSI variables 3. **Bug fix: Child allocation consistency** - Fixed `ssi_unearned_income_deemed_from_ineligible_spouse.py` to multiply child allocations by `is_ssi_aged_blind_disabled` - Now consistent with `ssi_earned_income_deemed_from_ineligible_spouse.py` and parent deeming pattern 4. **Code improvements** - Refactored `ssi_income_deemed_from_ineligible_spouse.py` to reuse `is_ssi_spousal_deeming` instead of duplicating threshold check - Eliminated nested `where()` in `ssi_amount_if_eligible.py` for better readability - Better variable naming: `individual_earned` instead of `indiv_earned` 5. **Test updates** - Updated integration test expectations to reflect correct SSI amounts with couple FBR - All 102 SSI tests passing Fixes #6842 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]> * minor * Improve SSI spousal deeming implementation and add comprehensive tests This commit refines the SSI spousal deeming fix with better code organization and additional test coverage: 1. **Move cap logic from uncapped_ssi to ssi** - Makes uncapped_ssi truly "uncapped" (just amount - countable) - Consolidates all final logic (floor and cap) in the ssi output variable - Clearer separation of concerns 2. **Refactor is_ssi_spousal_deeming for clarity** - Use add() to combine earned and unearned deemed income - Rename variables for consistency (spouse_deemed_income) - Break out nested where() statements for readability 3. **Update comments with corrected understanding** - Clarify that deeming threshold compares GROSS income (not countable) - Explain why cap is necessary (not redundant) - Document that exclusions apply to COMBINED income after threshold check 4. **Add comprehensive test cases** - Case 8: Spouse earns $967 - demonstrates cap is necessary - Case 9: Spouse earns $3,000, no child - shows $0 benefit - Case 10: Spouse earns $3,000, with child - shows $234 benefit - Case 11: Single parent scenario with two children - All tests include detailed calculation step comments 5. **Fix child allocation consistency** - Added missing is_ssi_aged_blind_disabled filter to unearned income variable - Now consistent with earned income and parent deeming patterns All 105 SSI tests passing. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]> * minor * minor * rename --------- Co-authored-by: Claude <[email protected]>
1 parent a35f5da commit 6edb9e7

File tree

6 files changed

+263
-29
lines changed

6 files changed

+263
-29
lines changed

changelog_entry.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
- bump: patch
2+
changes:
3+
fixed:
4+
- Fix SSI spousal deeming to correctly use couple FBR instead of individual FBR.

policyengine_us/tests/policy/baseline/gov/ssa/ssi/integration_tests.yaml

Lines changed: 150 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -68,10 +68,12 @@
6868
output:
6969
is_ssi_eligible: [true, false, true]
7070
# Spouse's countable: $2,000 - $20 - $65 - $957.50 = $957.50/month = $11,490/year
71-
# PolicyEngine deems all of spouse's countable income (doesn't subtract spouse allowance)
71+
# Since $11,490 > $5,796 (couple - individual FBR diff), deeming applies
72+
# With deeming, use couple FBR: $1,450/month = $17,400/year
7273
ssi_income_deemed_from_ineligible_spouse: [11_490, 0, 0]
7374
ssi_countable_income: [11_490, 0, 0]
74-
ssi: [114, 0, 11_604] # Adult1: $11,604 - $11,490 = $114; Child gets full FBR
75+
# Adult1 SSI: $17,400 (couple FBR) - $11,490 (countable) = $5,910
76+
ssi: [5_910, 0, 11_604] # Adult1: $5,910 with couple rate; Child gets full FBR
7577

7678
- name: Case 3 - Both adults healthy with income + disabled child
7779
period: 2025
@@ -217,12 +219,14 @@
217219
output:
218220
is_ssi_eligible: [true, false, true]
219221
# Spouse's countable: $5,000 - $20 - $65 - $2,457.50 = $2,457.50/month = $29,490/year
220-
# PolicyEngine deems all countable income without subtracting spouse allowance
222+
# Since $29,490 > $5,796 (couple - individual FBR diff), deeming applies
223+
# With deeming, use couple FBR: $1,450/month = $17,400/year
224+
# Adult1 SSI: $17,400 (couple FBR) - $29,490 (countable) = -$12,090, capped at $0
221225
ssi_income_deemed_from_ineligible_spouse: [29_490, 0, 0]
222226
# Parent income deeming to child also gets high amount
223227
ssi_unearned_income_deemed_from_ineligible_parent: [0, 0, 17_886]
224228
ssi_countable_income: [29_490, 0, 17_646]
225-
ssi: [0, 0, 0] # Both disabled members have too much deemed income
229+
ssi: [0, 0, 0] # Both disabled members have countable income exceeding FBR
226230

227231
- name: Case 7 - Illinois single parent with $9,600 income + healthy child
228232
period: 2024-01
@@ -254,3 +258,145 @@
254258
ssi_countable_income: [357.50, 0]
255259
ssi_amount_if_eligible: [943, 943]
256260
ssi: [585.50, 0]
261+
262+
- name: Case 8 - Deeming applies, benefit capped at individual FBR
263+
period: 2025-01
264+
input:
265+
people:
266+
adult1:
267+
age: 55
268+
is_disabled: false
269+
employment_income: 967 * 12 # $967/month
270+
adult2:
271+
age: 60
272+
is_disabled: true
273+
employment_income: 0
274+
tax_units:
275+
tax_unit:
276+
members: [adult1, adult2]
277+
marital_units:
278+
marital_unit:
279+
members: [adult1, adult2]
280+
households:
281+
household:
282+
members: [adult1, adult2]
283+
state_code: NC
284+
output:
285+
is_ssi_eligible: [false, true]
286+
# Step 1: Check if deeming applies
287+
# Adult1 gross income: $967/month > $483 threshold → Deeming applies
288+
#
289+
# Step 2: Calculate deemed income
290+
# Combined income: $967/month earned
291+
# Apply exclusions: $967 - $20 (general) - $65 (earned) = $882
292+
# Take half: $882 / 2 = $441/month
293+
# Adult2 alone countable: $0
294+
# Deemed income = $441 - $0 = $441/month
295+
ssi_income_deemed_from_ineligible_spouse: [0, 441]
296+
ssi_countable_income: [0, 441]
297+
#
298+
# Step 3: Calculate benefit
299+
# Use couple FBR (deeming applies): $1,450/month
300+
# Benefit before cap: $1,450 - $441 = $1,009/month
301+
# Cap at individual FBR: min($1,009, $967) = $967/month
302+
# Note: This demonstrates the cap is necessary when spouse's gross income
303+
# is just above $483, resulting in low countable income after exclusions
304+
ssi: [0, 967]
305+
306+
- name: Case 9 - Deeming applies without child
307+
period: 2025-01
308+
input:
309+
people:
310+
adult1:
311+
age: 41
312+
is_disabled: false
313+
employment_income: 3_000 * 12 # $3,000/month
314+
adult2:
315+
age: 40
316+
is_disabled: true
317+
employment_income: 0
318+
tax_units:
319+
tax_unit:
320+
members: [adult1, adult2]
321+
marital_units:
322+
marital_unit:
323+
members: [adult1, adult2]
324+
households:
325+
household:
326+
members: [adult1, adult2]
327+
state_code: NC
328+
output:
329+
is_ssi_eligible: [false, true]
330+
# Step 1: No child allocation (no child present)
331+
# Adult1 gross income: $3,000/month (no reduction)
332+
#
333+
# Step 2: Check if deeming applies
334+
# Adult1 gross income: $3,000 > $483 threshold → Deeming applies
335+
#
336+
# Step 3: Calculate deemed income
337+
# Combined income: $3,000/month earned
338+
# Apply exclusions: $3,000 - $20 (general) - $65 (earned) = $2,915
339+
# Take half: $2,915 / 2 = $1,457.50/month
340+
# Adult2 alone countable: $0
341+
# Deemed income = $1,457.50 - $0 = $1,457.50/month
342+
ssi_income_deemed_from_ineligible_spouse: [0, 1_457.50]
343+
ssi_countable_income: [0, 1_457.50]
344+
#
345+
# Step 4: Calculate benefit
346+
# Use couple FBR (deeming applies): $1,450/month
347+
# Benefit before cap: $1,450 - $1,457.50 = -$7.50 (negative)
348+
# Benefit capped at $0 (cannot be negative)
349+
ssi: [0, 0]
350+
351+
- name: Case 10 - Deeming applies with child allocation (compare to Case 9)
352+
period: 2025-01
353+
input:
354+
people:
355+
adult1:
356+
age: 41
357+
is_disabled: false
358+
employment_income: 3_000 * 12 # $3,000/month
359+
adult2:
360+
age: 40
361+
is_disabled: true
362+
employment_income: 0
363+
child:
364+
age: 12
365+
is_disabled: false
366+
employment_income: 0
367+
tax_units:
368+
tax_unit:
369+
members: [adult1, adult2, child]
370+
marital_units:
371+
marital_unit:
372+
members: [adult1, adult2, child]
373+
households:
374+
household:
375+
members: [adult1, adult2, child]
376+
state_code: NC
377+
output:
378+
is_ssi_eligible: [false, true, false]
379+
# Step 1: Apply child allocation
380+
# Child allocation: $1,450 (couple FBR) - $967 (individual FBR) = $483/month
381+
# Child has $0 income, so full $483 allocation applies
382+
# Adult1 earned after child allocation: $3,000 - $483 = $2,517/month
383+
#
384+
# Step 2: Check if deeming applies
385+
# Adult1 income after child allocation: $2,517 > $483 threshold → Deeming applies
386+
#
387+
# Step 3: Calculate deemed income
388+
# Combined income: $2,517/month earned (after child allocation)
389+
# Apply exclusions: $2,517 - $20 (general) - $65 (earned) = $2,432
390+
# Take half: $2,432 / 2 = $1,216/month
391+
# Adult2 alone countable: $0
392+
# Deemed income = $1,216 - $0 = $1,216/month
393+
ssi_income_deemed_from_ineligible_spouse: [0, 1_216, 0]
394+
ssi_countable_income: [0, 1_216, 0]
395+
#
396+
# Step 4: Calculate benefit
397+
# Use couple FBR (deeming applies): $1,450/month
398+
# Benefit before cap: $1,450 - $1,216 = $234/month
399+
# Cap at individual FBR: min($234, $967) = $234 (no cap needed)
400+
# Note: Compare to Case 9 - child allocation ($483) reduces deemed income,
401+
# changing result from $0 to $234/month
402+
ssi: [0, 234, 0]
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
from policyengine_us.model_api import *
2+
3+
4+
class is_ssi_spousal_deeming_applies(Variable):
5+
value_type = bool
6+
entity = Person
7+
label = "SSI spousal deeming applies"
8+
definition_period = YEAR
9+
reference = "https://www.law.cornell.edu/cfr/text/20/416.1163"
10+
defined_for = "is_ssi_eligible_individual"
11+
documentation = """
12+
Returns True when spousal deeming applies according to 20 CFR §416.1163.
13+
14+
Spousal deeming applies when:
15+
1. Person is an eligible individual (not part of an eligible couple)
16+
2. Spouse is ineligible (not aged/blind/disabled)
17+
3. Spouse's leftover income (after child allocations) exceeds the difference
18+
between couple FBR and individual FBR
19+
20+
When deeming applies, the benefit calculation uses the couple FBR instead
21+
of the individual FBR, recognizing that two people have higher expenses.
22+
"""
23+
24+
def formula(person, period, parameters):
25+
# Get spouse's deemed income (after child allocations)
26+
spouse_deemed_income = add(
27+
person,
28+
period,
29+
[
30+
"ssi_earned_income_deemed_from_ineligible_spouse",
31+
"ssi_unearned_income_deemed_from_ineligible_spouse",
32+
],
33+
)
34+
35+
# Compare to FBR differential (couple rate - individual rate)
36+
p = parameters(period).gov.ssa.ssi.amount
37+
diff = (p.couple - p.individual) * MONTHS_IN_YEAR
38+
39+
# Deeming applies when spouse's deemed income exceeds the differential
40+
# Note: regulation says "not more than" means ≤, so we use > for deeming
41+
return spouse_deemed_income > diff

policyengine_us/variables/gov/ssa/ssi/eligibility/income/deemed/from_ineligible_spouse/ssi_income_deemed_from_ineligible_spouse.py

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -22,37 +22,37 @@ class ssi_income_deemed_from_ineligible_spouse(Variable):
2222
unit = USD
2323

2424
def formula(person, period, parameters):
25-
# 1. Sum leftover spouse earned/unearned (post-child allocations).
25+
# 1. Check if spousal deeming applies (avoids duplicating threshold logic)
26+
deeming_applies = person("is_ssi_spousal_deeming_applies", period)
27+
28+
# 2. If deeming applies, calculate deemed amount using "difference" approach:
29+
# (couple combined countable) - (individual alone countable)
30+
31+
# Get spouse's leftover income (post-child allocations)
2632
spouse_earned = person(
2733
"ssi_earned_income_deemed_from_ineligible_spouse", period
2834
)
2935
spouse_unearned = person(
3036
"ssi_unearned_income_deemed_from_ineligible_spouse", period
3137
)
32-
leftover_spouse = spouse_earned + spouse_unearned
33-
34-
# 2. Compare leftover to FBR difference
35-
p = parameters(period).gov.ssa.ssi.amount
36-
diff = (p.couple - p.individual) * MONTHS_IN_YEAR
37-
if_leftover_exceeds = leftover_spouse > diff
38-
39-
# 3. If leftover <= diff => no deeming
40-
# If leftover > diff => difference of incomes approach
41-
# (couple combined) - (individual alone).
42-
# (a) individual's own income
43-
indiv_earned = person("ssi_earned_income", period)
44-
indiv_unearned = person("ssi_unearned_income", period)
38+
39+
# Get individual's own income
40+
individual_earned = person("ssi_earned_income", period)
41+
individual_unearned = person("ssi_unearned_income", period)
42+
43+
# Calculate individual's countable income (alone)
4544
alone_countable = _apply_ssi_exclusions(
46-
indiv_earned, indiv_unearned, parameters, period
45+
individual_earned, individual_unearned, parameters, period
4746
)
4847

49-
# (b) couple combined income
48+
# Calculate couple's combined countable income
5049
couple_countable = _apply_ssi_exclusions(
51-
indiv_earned + spouse_earned,
52-
indiv_unearned + spouse_unearned,
50+
individual_earned + spouse_earned,
51+
individual_unearned + spouse_unearned,
5352
parameters,
5453
period,
5554
)
5655

57-
deemed_if_exceeds = max_(0, couple_countable - alone_countable)
58-
return if_leftover_exceeds * deemed_if_exceeds
56+
# Deemed amount is the difference (only when deeming applies)
57+
deemed_amount = max_(0, couple_countable - alone_countable)
58+
return deeming_applies * deemed_amount

policyengine_us/variables/gov/ssa/ssi/ssi.py

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,25 @@ class ssi(Variable):
1313
def formula(person, period, parameters):
1414
if parameters(period).gov.ssa.ssi.abolish_ssi:
1515
return 0
16-
return max_(0, person("uncapped_ssi", period))
16+
17+
uncapped = person("uncapped_ssi", period)
18+
19+
# Apply floor: SSI cannot be negative
20+
benefit = max_(0, uncapped)
21+
22+
# Apply cap when spousal deeming: cannot exceed individual FBR
23+
# per 20 CFR §416.1163
24+
# This cap is necessary when spouse's gross income is just above $483:
25+
# - Deeming applies (uses couple FBR)
26+
# - After exclusions, countable may be low
27+
# - Benefit could exceed individual FBR without this cap
28+
deeming_applies = person("is_ssi_spousal_deeming_applies", period)
29+
p = parameters(period).gov.ssa.ssi.amount
30+
individual_max = p.individual * MONTHS_IN_YEAR
31+
capped_benefit = min_(benefit, individual_max)
32+
33+
return where(
34+
deeming_applies,
35+
capped_benefit,
36+
benefit,
37+
)

policyengine_us/variables/gov/ssa/ssi/ssi_amount_if_eligible.py

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,34 @@ class ssi_amount_if_eligible(Variable):
1212
def formula(person, period, parameters):
1313
p = parameters(period).gov.ssa.ssi.amount
1414
is_dependent = person("is_tax_unit_dependent", period)
15+
16+
# Three scenarios for adults:
17+
# 1. Both spouses eligible (joint claim) → couple rate / 2
18+
# 2. One eligible, no deeming → individual rate
19+
# 3. One eligible, deeming applies → couple rate (capped in ssi)
20+
#
21+
# Note: In scenario 3, deeming applies when spouse's GROSS income > $483.
22+
# Income exclusions are applied to the COMBINED income afterwards.
23+
# This means countable income can be much lower than $483, and the
24+
# benefit can exceed individual FBR, requiring the cap in ssi.
25+
26+
is_joint_claim = person("ssi_claim_is_joint", period)
27+
deeming_applies = person("is_ssi_spousal_deeming_applies", period)
28+
29+
# Determine FBR to use based on scenario
30+
individual_or_deeming_amount = where(
31+
deeming_applies,
32+
p.couple, # Scenario 3: Deeming applies - use couple rate!
33+
p.individual, # Scenario 2: No deeming
34+
)
35+
1536
head_or_spouse_amount = where(
16-
person("ssi_claim_is_joint", period),
17-
p.couple / 2,
18-
p.individual,
37+
is_joint_claim,
38+
p.couple / 2, # Scenario 1: Both eligible
39+
individual_or_deeming_amount,
1940
)
20-
# Adults amount is based on whether it is a joint claim
41+
42+
# Adults amount is based on scenario (see above)
2143
# Dependents always use individual amount.
2244
ssi_per_month = where(
2345
is_dependent, p.individual, head_or_spouse_amount

0 commit comments

Comments
 (0)