Skip to content

CA SSI state supplement pays $0 to disabled recipients after meets_ssi_disability_criteria migration (payment amount still keys on raw is_disabled) #8549

@hua7450

Description

@hua7450

Summary

After #8364 separated meets_ssi_disability_criteria from the broad is_disabled input (so is_ssi_disabled = meets_ssi_disability_criteria & ~ssi_engaged_in_sga), the California SSI State Supplement pays $0 to non-aged disabled recipients who are modeled with the new canonical input. Eligibility and the payment amount now read disability from different sources, so the SPM unit comes back eligible but with a $0 benefit.

This was found while comparing the PolicyEngine household API current vs frontier releases (period 2026). It is a concrete, confirmed instance of the broad-is_disabled risk tracked in #8431.

Reproduction (household API, /us/calculate, period 2026, CA, single adult, $0 income & resources)

Inputs ca_state_supplement_eligible_person ca_state_supplement
aged 70 (control) True 4101.72
age 45, meets_ssi_disability_criteria: true only True 0.00 ⚠️
age 45, meets_ssi_disability_criteria: true and is_disabled: true True 4101.72

So eligibility correctly recovers with the migrated input, but the amount only appears if the (now legacy) is_disabled flag is also sent.

Root cause

  • ca_state_supplement_eligible_person correctly derives eligibility from is_ssi_aged_blind_disabled (which flows through meets_ssi_disability_criteria post-Add SSI disability criteria variable #8364). ✅
  • But the payment standard's aged/disabled count still uses raw is_disabled:
# ca_state_supplement/payment_standard/ca_state_supplement_aged_disabled_count.py
is_disabled = person("is_disabled", period)
is_aged = age >= p.aged_or_disabled.age_threshold
aged_or_disabled = is_aged | is_disabled          # <-- raw is_disabled
eligible = person("ca_state_supplement_eligible_person", period)
return spm_unit.sum(aged_or_disabled * eligible)

For a disabled SSI recipient modeled via meets_ssi_disability_criteria, is_disabled is False, so the count is 0 → ca_state_supplement_aged_disabled_amount is defined_for a count > 0 and returns 0 → payment standard 0 → max(0, payment_standard - ssi - countable_income) = 0.

The same raw is_disabled pattern appears in ca_state_supplement_dependent_amount.py.

Suggested fix

In the CA payment-amount path, derive disability from the same SSI source used for eligibilityis_ssi_disabled (consistent with is_ssi_aged_blind_disabled) — instead of raw is_disabled:

  • ca_state_supplement_aged_disabled_count.py
  • ca_state_supplement_dependent_amount.py

Add a regression test asserting that a non-aged disabled SSI recipient modeled with meets_ssi_disability_criteria receives the same supplement that is_disabled: true produced pre-#8364.

Scope — checked against programs.yaml (all implemented SSPs)

programs.yaml registers SSP variables for 18 states + DC: AK, AL, CA, CO, CT, DC, DE, GA, IN, KY, MA, ME, MI, MO, NM, SC, TX, WA (FL/HI/IA/KS/LA/MN are listed but not yet implemented).

I scanned all of them for how they derive disability. CA is the only implemented SSP that reads the raw is_disabled input in its payment path:

  • ca_state_supplement_aged_disabled_count.py
  • ca_state_supplement_dependent_amount.py

Every other implemented SSP already derives disability from the SSI-correct source (is_ssi_disabled / ssi_category / is_ssi_aged_blind_disabled) — e.g. CO co_state_supplement_eligible, MA ma_maximum_state_supplement, WA wa_ssp_payment_category all use is_ssi_disabled. This matches empirical household-API testing: MA SSP recovers cleanly with meets_ssi_disability_criteria; only CA stays at $0. So the fix is localized to CA.

Note on blindness: raw is_blind is still read directly by several SSPs (WA wa_ssp_payment_category, MA ma_maximum_state_supplement, CO co_state_supplement_eligible, CT ct_ssp_*, MO mo_ssp_category_eligible, CA ca_state_supplement_blind_amount/_dependent_amount). That's fine today (blindness has no separate SSI-criteria input), but if it ever gets a meets_ssi_*-style split like disability did, these would need the same treatment.

All code references verified against up-to-date policyengine-us (post-#8364, is_ssi_disabled = meets_ssi_disability_criteria & ~ssi_engaged_in_sga).

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions