From ef7de8a39bc95c5a0ef17ad19c2d37fc083da405 Mon Sep 17 00:00:00 2001 From: Ziming Date: Mon, 12 Oct 2026 14:31:01 -0400 Subject: [PATCH 1/6] Add South Dakota state supplementation for 2026 --- changelog.d/sd-ssp-2026.added.md | 1 + .../household/household_state_benefits.yaml | 4 + .../hud/annual_income/sources/unearned.yaml | 1 + .../dss/ssp/assisted_living_daily_rate.yaml | 13 + .../sd/dss/ssp/couple_first_amount.yaml | 10 + .../sd/dss/ssp/couple_second_amount.yaml | 10 + .../states/sd/dss/ssp/individual_amount.yaml | 10 + .../dss/ssp/joint_entry_resource_limit.yaml | 9 + .../gov/states/sd/dss/ssp/minimum_age.yaml | 9 + .../gov/states/sd/dss/ssp/resource_limit.yaml | 9 + .../usda/snap/income/sources/unearned.yaml | 7 +- policyengine_us/programs.yaml | 11 + .../gov/states/sd/dss/ssp/integration.yaml | 59 +++++ .../gov/states/sd/dss/ssp/sd_ssp.yaml | 250 ++++++++++++++++++ .../variables/gov/states/sd/dss/ssp/sd_ssp.py | 11 + .../sd/dss/ssp/sd_ssp_approved_care_cost.py | 11 + .../gov/states/sd/dss/ssp/sd_ssp_care.py | 46 ++++ .../sd/dss/ssp/sd_ssp_care_certified.py | 10 + .../gov/states/sd/dss/ssp/sd_ssp_care_days.py | 12 + .../dss/ssp/sd_ssp_care_income_deductions.py | 11 + .../dss/ssp/sd_ssp_care_income_not_applied.py | 10 + .../sd/dss/ssp/sd_ssp_care_joint_entry.py | 9 + .../states/sd/dss/ssp/sd_ssp_independent.py | 44 +++ .../sd/dss/ssp/sd_ssp_is_adult_foster_care.py | 9 + .../income/spm_unit/spm_unit_benefits.py | 1 + 25 files changed, 573 insertions(+), 4 deletions(-) create mode 100644 changelog.d/sd-ssp-2026.added.md create mode 100644 policyengine_us/parameters/gov/states/sd/dss/ssp/assisted_living_daily_rate.yaml create mode 100644 policyengine_us/parameters/gov/states/sd/dss/ssp/couple_first_amount.yaml create mode 100644 policyengine_us/parameters/gov/states/sd/dss/ssp/couple_second_amount.yaml create mode 100644 policyengine_us/parameters/gov/states/sd/dss/ssp/individual_amount.yaml create mode 100644 policyengine_us/parameters/gov/states/sd/dss/ssp/joint_entry_resource_limit.yaml create mode 100644 policyengine_us/parameters/gov/states/sd/dss/ssp/minimum_age.yaml create mode 100644 policyengine_us/parameters/gov/states/sd/dss/ssp/resource_limit.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/sd/dss/ssp/integration.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/sd/dss/ssp/sd_ssp.yaml create mode 100644 policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp.py create mode 100644 policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp_approved_care_cost.py create mode 100644 policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp_care.py create mode 100644 policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp_care_certified.py create mode 100644 policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp_care_days.py create mode 100644 policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp_care_income_deductions.py create mode 100644 policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp_care_income_not_applied.py create mode 100644 policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp_care_joint_entry.py create mode 100644 policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp_independent.py create mode 100644 policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp_is_adult_foster_care.py diff --git a/changelog.d/sd-ssp-2026.added.md b/changelog.d/sd-ssp-2026.added.md new file mode 100644 index 00000000000..50225e95074 --- /dev/null +++ b/changelog.d/sd-ssp-2026.added.md @@ -0,0 +1 @@ +Add South Dakota State Supplemental Payment for 2026. diff --git a/policyengine_us/parameters/gov/household/household_state_benefits.yaml b/policyengine_us/parameters/gov/household/household_state_benefits.yaml index d2cbd6ba2d4..52c704e30c4 100644 --- a/policyengine_us/parameters/gov/household/household_state_benefits.yaml +++ b/policyengine_us/parameters/gov/household/household_state_benefits.yaml @@ -78,6 +78,7 @@ values: # DC benefits - dc_ossp # Kansas benefits + - sd_ssp - ks_sspp # Hawaii benefits - hi_oss @@ -176,6 +177,7 @@ values: # DC benefits - dc_ossp # Kansas benefits + - sd_ssp - ks_sspp # Hawaii benefits - hi_oss @@ -279,6 +281,7 @@ values: - dc_ossp - dc_child_care_subsidies # Kansas benefits + - sd_ssp - ks_sspp # Hawaii benefits - hi_oss @@ -382,6 +385,7 @@ values: - dc_ossp - dc_child_care_subsidies # Kansas benefits + - sd_ssp - ks_sspp # Hawaii benefits - hi_oss diff --git a/policyengine_us/parameters/gov/hud/annual_income/sources/unearned.yaml b/policyengine_us/parameters/gov/hud/annual_income/sources/unearned.yaml index d2b2bbd8675..4a175e30caa 100644 --- a/policyengine_us/parameters/gov/hud/annual_income/sources/unearned.yaml +++ b/policyengine_us/parameters/gov/hud/annual_income/sources/unearned.yaml @@ -19,6 +19,7 @@ values: - strike_benefits - social_security - ssi + - sd_ssp - general_assistance - unemployment_compensation - child_support_received diff --git a/policyengine_us/parameters/gov/states/sd/dss/ssp/assisted_living_daily_rate.yaml b/policyengine_us/parameters/gov/states/sd/dss/ssp/assisted_living_daily_rate.yaml new file mode 100644 index 00000000000..7f840b4ad29 --- /dev/null +++ b/policyengine_us/parameters/gov/states/sd/dss/ssp/assisted_living_daily_rate.yaml @@ -0,0 +1,13 @@ +description: South Dakota caps its state optional assisted living reimbursement at this daily amount, + including room and board. +values: + '2026-01-01': 77.16 +metadata: + unit: currency-USD + period: day + label: South Dakota SSP assisted living daily rate + reference: + - title: LTSS fee schedule effective January 2026, State Optional Supplemental Assisted Living (08054), note 2 + href: https://dhs.sd.gov/content/dam/digital/united-states/south-dakota/sdo/pdf/long-term-services-and-supports/provider-portal/fee-schedules/LTSS-Fee-Schedule-and-In-Home-Services-Fee-Schedule-Effective-1-1-26.pdf + - title: LTSS fee schedule effective July 2026, State Optional Supplemental Assisted Living (08054), note 2 + href: https://dhs.sd.gov/content/dam/digital/united-states/south-dakota/sdo/pdf/long-term-services-and-supports/provider-portal/fee-schedules/LTSS-Fee-Schedule-Effective-7-1-2026.pdf diff --git a/policyengine_us/parameters/gov/states/sd/dss/ssp/couple_first_amount.yaml b/policyengine_us/parameters/gov/states/sd/dss/ssp/couple_first_amount.yaml new file mode 100644 index 00000000000..1f88fa7b487 --- /dev/null +++ b/policyengine_us/parameters/gov/states/sd/dss/ssp/couple_first_amount.yaml @@ -0,0 +1,10 @@ +description: South Dakota pays this amount as its first member independent supplement. +values: + '1978-07-01': 8 +metadata: + unit: currency-USD + label: South Dakota SSP first member independent supplement + reference: + - title: SSA POMS SI DEN01415.011 B.5 + href: https://secure.ssa.gov/poms.nsf/lnx/0501415011DEN + period: month diff --git a/policyengine_us/parameters/gov/states/sd/dss/ssp/couple_second_amount.yaml b/policyengine_us/parameters/gov/states/sd/dss/ssp/couple_second_amount.yaml new file mode 100644 index 00000000000..e3519249aaa --- /dev/null +++ b/policyengine_us/parameters/gov/states/sd/dss/ssp/couple_second_amount.yaml @@ -0,0 +1,10 @@ +description: South Dakota pays this amount as its second member independent supplement. +values: + '1978-07-01': 7 +metadata: + unit: currency-USD + label: South Dakota SSP second member independent supplement + reference: + - title: SSA POMS SI DEN01415.011 B.5 + href: https://secure.ssa.gov/poms.nsf/lnx/0501415011DEN + period: month diff --git a/policyengine_us/parameters/gov/states/sd/dss/ssp/individual_amount.yaml b/policyengine_us/parameters/gov/states/sd/dss/ssp/individual_amount.yaml new file mode 100644 index 00000000000..d345c4dda2e --- /dev/null +++ b/policyengine_us/parameters/gov/states/sd/dss/ssp/individual_amount.yaml @@ -0,0 +1,10 @@ +description: South Dakota pays this amount as its individual independent supplement. +values: + '1978-07-01': 15 +metadata: + unit: currency-USD + label: South Dakota SSP individual independent supplement + reference: + - title: SSA POMS SI DEN01415.011 B.5 + href: https://secure.ssa.gov/poms.nsf/lnx/0501415011DEN + period: month diff --git a/policyengine_us/parameters/gov/states/sd/dss/ssp/joint_entry_resource_limit.yaml b/policyengine_us/parameters/gov/states/sd/dss/ssp/joint_entry_resource_limit.yaml new file mode 100644 index 00000000000..39be98208e4 --- /dev/null +++ b/policyengine_us/parameters/gov/states/sd/dss/ssp/joint_entry_resource_limit.yaml @@ -0,0 +1,9 @@ +description: South Dakota applies this joint entry resource limit to long-term care assistance. +values: + '2026-01-01': 3000 +metadata: + unit: currency-USD + label: South Dakota SSP joint entry resource limit + reference: + - title: ARSD 67:46:05:30 + href: https://sdlegislature.gov/Rules/Administrative/67:46:05:30 diff --git a/policyengine_us/parameters/gov/states/sd/dss/ssp/minimum_age.yaml b/policyengine_us/parameters/gov/states/sd/dss/ssp/minimum_age.yaml new file mode 100644 index 00000000000..59f3df69ea9 --- /dev/null +++ b/policyengine_us/parameters/gov/states/sd/dss/ssp/minimum_age.yaml @@ -0,0 +1,9 @@ +description: South Dakota provides its independent state supplement to adults at or above this age. +values: + '1978-08-06': 18 +metadata: + unit: year + label: South Dakota SSP minimum age + reference: + - title: ARSD 67:12:14:02 + href: https://sdlegislature.gov/Rules/Administrative/67:12:14:02 diff --git a/policyengine_us/parameters/gov/states/sd/dss/ssp/resource_limit.yaml b/policyengine_us/parameters/gov/states/sd/dss/ssp/resource_limit.yaml new file mode 100644 index 00000000000..6a85fb53a4d --- /dev/null +++ b/policyengine_us/parameters/gov/states/sd/dss/ssp/resource_limit.yaml @@ -0,0 +1,9 @@ +description: South Dakota applies this individual resource limit to long-term care assistance. +values: + '2026-01-01': 2000 +metadata: + unit: currency-USD + label: South Dakota SSP individual resource limit + reference: + - title: ARSD 67:46:05:30 + href: https://sdlegislature.gov/Rules/Administrative/67:46:05:30 diff --git a/policyengine_us/parameters/gov/usda/snap/income/sources/unearned.yaml b/policyengine_us/parameters/gov/usda/snap/income/sources/unearned.yaml index 6f94d3d09b2..629dfa3e0b4 100644 --- a/policyengine_us/parameters/gov/usda/snap/income/sources/unearned.yaml +++ b/policyengine_us/parameters/gov/usda/snap/income/sources/unearned.yaml @@ -36,6 +36,7 @@ values: - hi_oss - id_aabd - in_ssp + - sd_ssp - ks_sspp - ky_ssp - la_oss @@ -95,9 +96,7 @@ values: metadata: reference: - - title: 7 CFR 273.9(b)(2) - href: https://www.law.cornell.edu/cfr/text/7/273.9#b_2 - - title: 7 CFR 273.9(c)(1) - href: https://www.law.cornell.edu/cfr/text/7/273.9#c_1 + - title: 7 CFR 273.9(b)(2) and (c)(1), (5) + href: https://www.law.cornell.edu/cfr/text/7/273.9 - title: POMS SI 01401.001 General Information about State Supplementation href: https://secure.ssa.gov/poms.nsf/lnx/0501401001 diff --git a/policyengine_us/programs.yaml b/policyengine_us/programs.yaml index 05cb1d13747..40c43c76700 100644 --- a/policyengine_us/programs.yaml +++ b/policyengine_us/programs.yaml @@ -1090,6 +1090,17 @@ programs: tracks. Unmodeled paths: grandfathered medically-needy-in-medical- institution recipients per WAC 388-478-0055(2)(b); DDA-determined / PVL track per (2)(f); and foster child in BRS per (2)(g). + - state: SD + status: partial + name: South Dakota SSP + full_name: South Dakota State Supplemental Payment + variable: sd_ssp + parameter_prefix: gov.states.sd.dss.ssp + verified_years: '2026' + notes: Independent living and certified nonwaiver assisted living/adult foster care. + Approved care charges and state-specific deductions require inputs; only assisted living + has a verified 2026 reimbursement cap. Out-of-state placements, historical mandatory + supplements and claim administration are not modeled. - id: social_security name: Social Security diff --git a/policyengine_us/tests/policy/baseline/gov/states/sd/dss/ssp/integration.yaml b/policyengine_us/tests/policy/baseline/gov/states/sd/dss/ssp/integration.yaml new file mode 100644 index 00000000000..e6bf3e071c8 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/sd/dss/ssp/integration.yaml @@ -0,0 +1,59 @@ +- name: Case 1, eligible SSI couple receives eight and seven dollars. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + first: + age: 65 + second: + age: 65 + marital_units: + couple: + members: + - first + - second + households: + hh: + members: + - first + - second + state_code: SD + output: + sd_ssp: + - 8 + - 7 +- name: Case 2, annual independent supplement and SNAP income. + period: '2026' + absolute_error_margin: 0.01 + input: + age: 65 + state_code: SD + output: + sd_ssp: 180 + snap_unearned_income: 12108 + hud_unearned_income: 12108 +- absolute_error_margin: 0.01 + input: + households: + hh: + members: + - first + - second + state_code: SD + marital_units: + couple: + members: + - first + - second + people: + first: + age: 65 + second: + age: 65 + social_security_retirement: 120 + name: Case 3, other cash income of either eligible spouse excludes the couple. + output: + sd_ssp: + - 0 + - 0 + period: 2026-01 diff --git a/policyengine_us/tests/policy/baseline/gov/states/sd/dss/ssp/sd_ssp.yaml b/policyengine_us/tests/policy/baseline/gov/states/sd/dss/ssp/sd_ssp.yaml new file mode 100644 index 00000000000..d08044f7f3d --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/sd/dss/ssp/sd_ssp.yaml @@ -0,0 +1,250 @@ +- name: Case 1, SSI is the only cash income. + period: 2026-01 + absolute_error_margin: 0.01 + input: + state_code: + '2026': SD + age: + '2026': 65 + output: + sd_ssp: 15 +- name: Case 2, other cash income below SSI disregard still disqualifies. + period: 2026-01 + absolute_error_margin: 0.01 + input: + state_code: + '2026': SD + age: + '2026': 65 + social_security_retirement: 120 + output: + sd_ssp: 0 +- name: Case 3, in-kind support does not disqualify. + period: 2026-01 + absolute_error_margin: 0.01 + input: + state_code: + '2026': SD + age: + '2026': 65 + ssi_federal_living_arrangement: ANOTHER_PERSONS_HOUSEHOLD + output: + sd_ssp: 15 +- name: Case 4, child is not eligible for independent supplement. + period: 2026-01 + absolute_error_margin: 0.01 + input: + state_code: + '2026': SD + age: 17 + is_blind: true + output: + sd_ssp: 0 +- name: Case 5, student is not eligible for independent supplement. + period: 2026-01 + absolute_error_margin: 0.01 + input: + state_code: + '2026': SD + age: + '2026': 65 + is_full_time_student: true + output: + sd_ssp: 0 +- name: Case 6, institutional resident is excluded. + period: 2026-01 + absolute_error_margin: 0.01 + input: + state_code: + '2026': SD + age: + '2026': 65 + ssi_federal_living_arrangement: MEDICAL_TREATMENT_FACILITY + output: + sd_ssp: 0 +- name: Case 7, no SSI payment. + period: 2026-01 + absolute_error_margin: 0.01 + input: + state_code: + '2026': SD + age: + '2026': 65 + ssi: 0 + output: + sd_ssp: 0 +- name: Case 8, nonresident. + period: 2026-01 + absolute_error_margin: 0.01 + input: + state_code: WY + age: + '2026': 65 + output: + sd_ssp: 0 +- name: Case 9, care payment uses approved monthly care bill less income. + period: 2026-01 + absolute_error_margin: 0.01 + input: + state_code: + '2026': SD + age: + '2026': 65 + sd_ssp_care_certified: true + sd_ssp_approved_care_cost: 2200 + output: + sd_ssp: 1206 +- name: Case 10, actual care bill capped at January daily reimbursement. + period: 2026-01 + absolute_error_margin: 0.01 + input: + state_code: + '2026': SD + age: + '2026': 65 + sd_ssp_care_certified: true + sd_ssp_approved_care_cost: 3000 + output: + sd_ssp: 1397.96 +- name: Case 11, July retains the January daily reimbursement cap. + period: 2026-07 + absolute_error_margin: 0.01 + input: + state_code: + '2026': SD + age: + '2026': 65 + sd_ssp_care_certified: true + sd_ssp_approved_care_cost: 3000 + output: + sd_ssp: 1397.96 +- name: Case 12, February has twenty eight billable days. + period: 2026-02 + absolute_error_margin: 0.01 + input: + state_code: + '2026': SD + age: + '2026': 65 + sd_ssp_care_certified: true + sd_ssp_approved_care_cost: 3000 + output: + sd_ssp: 1166.48 +- name: Case 13, state approved deductions reduce cost share. + period: 2026-01 + absolute_error_margin: 0.01 + input: + state_code: + '2026': SD + age: + '2026': 65 + sd_ssp_care_certified: true + sd_ssp_approved_care_cost: 2200 + sd_ssp_care_income_deductions: 100 + output: + sd_ssp: 1306 +- name: Case 14, no automatic twenty dollar SSI disregard for care. + period: 2026-01 + absolute_error_margin: 0.01 + input: + state_code: + '2026': SD + age: + '2026': 65 + sd_ssp_care_certified: true + sd_ssp_approved_care_cost: 2200 + social_security_retirement: 24000 + output: + sd_ssp: 200 +- name: Case 15, excess resources. + period: 2026-01 + absolute_error_margin: 0.01 + input: + state_code: + '2026': SD + age: + '2026': 65 + sd_ssp_care_certified: true + sd_ssp_approved_care_cost: 2200 + ssi_countable_resources: 2001 + output: + sd_ssp: 0 +- name: Case 16, certified partial month without income contribution. + period: 2026-01 + absolute_error_margin: 0.01 + input: + state_code: + '2026': SD + age: + '2026': 65 + sd_ssp_care_certified: true + sd_ssp_approved_care_cost: 1500 + sd_ssp_care_days: 10 + sd_ssp_care_income_not_applied: true + output: + sd_ssp: 771.6 +- name: Case 17, adult foster care needs its approved cost. + period: 2026-01 + absolute_error_margin: 0.01 + input: + state_code: + '2026': SD + age: + '2026': 65 + sd_ssp_care_certified: true + sd_ssp_is_adult_foster_care: true + sd_ssp_approved_care_cost: 1500 + output: + sd_ssp: 506 +- name: Case 18, no care payment without approved cost. + period: 2026-01 + absolute_error_margin: 0.01 + input: + state_code: + '2026': SD + age: + '2026': 65 + sd_ssp_care_certified: true + output: + sd_ssp: 0 +- name: Case 19, care needs certified medical eligibility. + period: 2026-01 + absolute_error_margin: 0.01 + input: + state_code: + '2026': SD + age: 40 + sd_ssp_care_certified: true + sd_ssp_approved_care_cost: 2200 + output: + sd_ssp: 0 +- name: Case 20, SSI opt out. + period: 2026-01 + absolute_error_margin: 0.01 + input: + state_code: + '2026': SD + age: + '2026': 65 + takes_up_ssi_if_eligible: false + output: + sd_ssp: 0 + +# Service 08054 remains $77.16/day: June 30*77.16-994; July 31*77.16-994. +# https://dhs.sd.gov/content/dam/digital/united-states/south-dakota/sdo/pdf/long-term-services-and-supports/provider-portal/fee-schedules/LTSS-Fee-Schedule-Effective-7-1-2026.pdf +- name: Case 21, January daily cap remains in effect through June and July. + period: 2026 + absolute_error_margin: 0.01 + input: + state_code: SD + age: 65 + sd_ssp_care_certified: + 2026-06: true + 2026-07: true + sd_ssp_approved_care_cost: + 2026-06: 3000 + 2026-07: 3000 + output: + sd_ssp: + 2026-06: 1320.8 + 2026-07: 1397.96 diff --git a/policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp.py b/policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp.py new file mode 100644 index 00000000000..93065fc758d --- /dev/null +++ b/policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp.py @@ -0,0 +1,11 @@ +from policyengine_us.model_api import * + + +class sd_ssp(Variable): + value_type = float + entity = Person + label = "South Dakota State Supplemental Payment" + definition_period = MONTH + unit = USD + reference = "https://secure.ssa.gov/poms.nsf/lnx/0501415011DEN" + adds = ["sd_ssp_independent", "sd_ssp_care"] diff --git a/policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp_approved_care_cost.py b/policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp_approved_care_cost.py new file mode 100644 index 00000000000..ec408581d3f --- /dev/null +++ b/policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp_approved_care_cost.py @@ -0,0 +1,11 @@ +from policyengine_us.model_api import * + + +class sd_ssp_approved_care_cost(Variable): + value_type = float + entity = Person + label = "Approved South Dakota SSP care bill for the month" + definition_period = MONTH + unit = USD + reference = "https://sdlegislature.gov/Rules/Administrative/67:46:04:15" + documentation = "Actual approved charge for covered days. Required for both care settings; the assisted living schedule additionally caps the payment." diff --git a/policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp_care.py b/policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp_care.py new file mode 100644 index 00000000000..738597922e2 --- /dev/null +++ b/policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp_care.py @@ -0,0 +1,46 @@ +from policyengine_us.model_api import * + + +class sd_ssp_care(Variable): + value_type = float + entity = Person + label = "South Dakota optional supplemental care payment" + definition_period = MONTH + unit = USD + reference = "https://sdlegislature.gov/Rules/Administrative/67:46:06" + defined_for = StateCode.SD + + def formula(person, period, parameters): + p = parameters(period).gov.states.sd.dss.ssp + resources = person("ssi_countable_resources", period.this_year) + joint_entry = person("sd_ssp_care_joint_entry", period) + resources = where(joint_entry, person.marital_unit.sum(resources), resources) + limit = where(joint_entry, p.joint_entry_resource_limit, p.resource_limit) + immigration = person("immigration_status", period.this_year) + citizenship = (immigration == immigration.possible_values.CITIZEN) | person( + "is_ssi_qualified_noncitizen", period.this_year + ) + eligible = ( + person("sd_ssp_care_certified", period) + & person("is_ssi_aged_blind_disabled", period.this_year) + & citizenship + & (resources <= limit) + & ~person("is_incarcerated", period) + & person("takes_up_ssi_if_eligible", period) + ) + days = clip(person("sd_ssp_care_days", period), 0, period.days) + cost = max_(person("sd_ssp_approved_care_cost", period), 0) + foster = person("sd_ssp_is_adult_foster_care", period) + cost = where(foster, cost, min_(cost, days * p.assisted_living_daily_rate)) + # Care uses gross income, including SSI, before state-approved + # deductions. Federal SSI's $20/$65 exclusions are not applied. + income = ( + max_(person("ssi_earned_income", period), 0) + + max_(person("ssi_unearned_income", period), 0) + + person("ssi", period) + ) + contribution = max_(income - person("sd_ssp_care_income_deductions", period), 0) + contribution = where( + person("sd_ssp_care_income_not_applied", period), 0, contribution + ) + return eligible * (days > 0) * max_(cost - contribution, 0) diff --git a/policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp_care_certified.py b/policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp_care_certified.py new file mode 100644 index 00000000000..7555174e920 --- /dev/null +++ b/policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp_care_certified.py @@ -0,0 +1,10 @@ +from policyengine_us.model_api import * + + +class sd_ssp_care_certified(Variable): + value_type = bool + entity = Person + label = "Certified for South Dakota nonwaiver optional supplemental care" + definition_period = MONTH + reference = "https://sdlegislature.gov/Rules/Administrative/67:46:04:15" + documentation = "Approved placement, care needs, benefit pursuit and nonfinancial program requirements; excludes Medicaid waiver services." diff --git a/policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp_care_days.py b/policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp_care_days.py new file mode 100644 index 00000000000..7cad2b42fe5 --- /dev/null +++ b/policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp_care_days.py @@ -0,0 +1,12 @@ +from policyengine_us.model_api import * + + +class sd_ssp_care_days(Variable): + value_type = int + entity = Person + label = "Covered South Dakota SSP care days" + definition_period = MONTH + reference = "https://sdlegislature.gov/Rules/Administrative/67:46:04:15" + + def formula(person, period, parameters): + return period.days diff --git a/policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp_care_income_deductions.py b/policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp_care_income_deductions.py new file mode 100644 index 00000000000..b8e0a9fc8c9 --- /dev/null +++ b/policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp_care_income_deductions.py @@ -0,0 +1,11 @@ +from policyengine_us.model_api import * + + +class sd_ssp_care_income_deductions(Variable): + value_type = float + entity = Person + label = "Approved South Dakota SSP care income deductions" + definition_period = MONTH + unit = USD + reference = "https://sdlegislature.gov/Rules/Administrative/67:46:06" + documentation = "Verified statutory exclusions, eligible insurance premiums and dependent allocations. Nursing-facility-only personal needs allowances do not automatically apply." diff --git a/policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp_care_income_not_applied.py b/policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp_care_income_not_applied.py new file mode 100644 index 00000000000..e9577bf4f6c --- /dev/null +++ b/policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp_care_income_not_applied.py @@ -0,0 +1,10 @@ +from policyengine_us.model_api import * + + +class sd_ssp_care_income_not_applied(Variable): + value_type = bool + entity = Person + label = "South Dakota care month has no income contribution" + definition_period = MONTH + reference = "https://sdlegislature.gov/Rules/Administrative/67:46:06:09" + documentation = "Certified partial-month exception under ARSD 67:46:06:09; excludes death and transfer months and cases with an assessed contribution." diff --git a/policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp_care_joint_entry.py b/policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp_care_joint_entry.py new file mode 100644 index 00000000000..dea99bc7260 --- /dev/null +++ b/policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp_care_joint_entry.py @@ -0,0 +1,9 @@ +from policyengine_us.model_api import * + + +class sd_ssp_care_joint_entry(Variable): + value_type = bool + entity = Person + label = "South Dakota spouses begin long-term care in the same month" + definition_period = MONTH + reference = "https://sdlegislature.gov/Rules/Administrative/67:46:05:30" diff --git a/policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp_independent.py b/policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp_independent.py new file mode 100644 index 00000000000..cd0da4ba773 --- /dev/null +++ b/policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp_independent.py @@ -0,0 +1,44 @@ +from policyengine_us.model_api import * + + +class sd_ssp_independent(Variable): + value_type = float + entity = Person + label = "South Dakota independent living state supplement" + definition_period = MONTH + unit = USD + reference = "https://sdlegislature.gov/Rules/Administrative/67:12:14" + defined_for = StateCode.SD + + def formula(person, period, parameters): + p = parameters(period).gov.states.sd.dss.ssp + source = parameters(period).gov.ssa.ssi.income.sources + other_income = sum( + max_(person(name, period), 0) + for name in list(source.earned) + list(source.unearned) + ) + joint = person("ssi_claim_is_joint", period.this_year) + other_income = where(joint, person.marital_unit.sum(other_income), other_income) + arrangement = person("ssi_federal_living_arrangement", period) + la = arrangement.possible_values + independent = (arrangement == la.OWN_HOUSEHOLD) | ( + arrangement == la.ANOTHER_PERSONS_HOUSEHOLD + ) + student = person("is_full_time_student", period.this_year) | person( + "is_part_time_college_student", period.this_year + ) + eligible = ( + (person("ssi", period) > 0) + & (other_income == 0) + & independent + & (person("age", period.this_year) >= p.minimum_age) + & ~student + & ~person("sd_ssp_care_certified", period) + ) + first = person.marital_unit.reference_entity.members_position == 0 + amount = where( + joint, + where(first, p.couple_first_amount, p.couple_second_amount), + p.individual_amount, + ) + return eligible * amount diff --git a/policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp_is_adult_foster_care.py b/policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp_is_adult_foster_care.py new file mode 100644 index 00000000000..9b7bb4ecc6c --- /dev/null +++ b/policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp_is_adult_foster_care.py @@ -0,0 +1,9 @@ +from policyengine_us.model_api import * + + +class sd_ssp_is_adult_foster_care(Variable): + value_type = bool + entity = Person + label = "South Dakota SSP care is licensed adult foster care" + definition_period = MONTH + reference = "https://sdlegislature.gov/Rules/Administrative/67:46:04:15" diff --git a/policyengine_us/variables/household/income/spm_unit/spm_unit_benefits.py b/policyengine_us/variables/household/income/spm_unit/spm_unit_benefits.py index 41ead044f46..8447cfa34cb 100644 --- a/policyengine_us/variables/household/income/spm_unit/spm_unit_benefits.py +++ b/policyengine_us/variables/household/income/spm_unit/spm_unit_benefits.py @@ -22,6 +22,7 @@ def formula(spm_unit, period, parameters): "ky_ssp", # Kentucky benefits "de_ssp", # Delaware benefits "fl_oss", + "sd_ssp", "ks_sspp", # Kansas benefits "hi_oss", "la_oss", # Louisiana benefits From 366c94af325b75a6843c00fed048b9642db1a54d Mon Sep 17 00:00:00 2001 From: Ziming Date: Tue, 13 Oct 2026 00:57:44 -0400 Subject: [PATCH 2/6] Normalize South Dakota SSP parameter and test formatting --- .../household/household_state_benefits.yaml | 1 + .../hud/annual_income/sources/unearned.yaml | 1 + .../dss/ssp/assisted_living_daily_rate.yaml | 14 +- .../sd/dss/ssp/couple_first_amount.yaml | 9 +- .../sd/dss/ssp/couple_second_amount.yaml | 9 +- .../states/sd/dss/ssp/individual_amount.yaml | 9 +- .../dss/ssp/joint_entry_resource_limit.yaml | 7 +- .../gov/states/sd/dss/ssp/minimum_age.yaml | 7 +- .../gov/states/sd/dss/ssp/resource_limit.yaml | 7 +- .../gov/states/sd/dss/ssp/integration.yaml | 56 +++----- .../gov/states/sd/dss/ssp/sd_ssp.yaml | 135 ++++++++++-------- 11 files changed, 136 insertions(+), 119 deletions(-) diff --git a/policyengine_us/parameters/gov/household/household_state_benefits.yaml b/policyengine_us/parameters/gov/household/household_state_benefits.yaml index 52c704e30c4..473ed3d7a51 100644 --- a/policyengine_us/parameters/gov/household/household_state_benefits.yaml +++ b/policyengine_us/parameters/gov/household/household_state_benefits.yaml @@ -405,6 +405,7 @@ values: - nj_property_tax_relief # New York benefits - ny_child_care_subsidies + metadata: unit: list period: year diff --git a/policyengine_us/parameters/gov/hud/annual_income/sources/unearned.yaml b/policyengine_us/parameters/gov/hud/annual_income/sources/unearned.yaml index 4a175e30caa..b07071f98e5 100644 --- a/policyengine_us/parameters/gov/hud/annual_income/sources/unearned.yaml +++ b/policyengine_us/parameters/gov/hud/annual_income/sources/unearned.yaml @@ -30,6 +30,7 @@ values: - disability_benefits - survivor_benefits - financial_assistance + metadata: unit: list period: year diff --git a/policyengine_us/parameters/gov/states/sd/dss/ssp/assisted_living_daily_rate.yaml b/policyengine_us/parameters/gov/states/sd/dss/ssp/assisted_living_daily_rate.yaml index 7f840b4ad29..8c36e857ea3 100644 --- a/policyengine_us/parameters/gov/states/sd/dss/ssp/assisted_living_daily_rate.yaml +++ b/policyengine_us/parameters/gov/states/sd/dss/ssp/assisted_living_daily_rate.yaml @@ -1,13 +1,13 @@ -description: South Dakota caps its state optional assisted living reimbursement at this daily amount, - including room and board. +description: South Dakota caps its state optional assisted living reimbursement at this daily amount, including room and board. values: - '2026-01-01': 77.16 + 2026-01-01: 77.16 + metadata: unit: currency-USD period: day label: South Dakota SSP assisted living daily rate reference: - - title: LTSS fee schedule effective January 2026, State Optional Supplemental Assisted Living (08054), note 2 - href: https://dhs.sd.gov/content/dam/digital/united-states/south-dakota/sdo/pdf/long-term-services-and-supports/provider-portal/fee-schedules/LTSS-Fee-Schedule-and-In-Home-Services-Fee-Schedule-Effective-1-1-26.pdf - - title: LTSS fee schedule effective July 2026, State Optional Supplemental Assisted Living (08054), note 2 - href: https://dhs.sd.gov/content/dam/digital/united-states/south-dakota/sdo/pdf/long-term-services-and-supports/provider-portal/fee-schedules/LTSS-Fee-Schedule-Effective-7-1-2026.pdf + - title: LTSS fee schedule effective January 2026, State Optional Supplemental Assisted Living (08054), note 2 + href: https://dhs.sd.gov/content/dam/digital/united-states/south-dakota/sdo/pdf/long-term-services-and-supports/provider-portal/fee-schedules/LTSS-Fee-Schedule-and-In-Home-Services-Fee-Schedule-Effective-1-1-26.pdf + - title: LTSS fee schedule effective July 2026, State Optional Supplemental Assisted Living (08054), note 2 + href: https://dhs.sd.gov/content/dam/digital/united-states/south-dakota/sdo/pdf/long-term-services-and-supports/provider-portal/fee-schedules/LTSS-Fee-Schedule-Effective-7-1-2026.pdf diff --git a/policyengine_us/parameters/gov/states/sd/dss/ssp/couple_first_amount.yaml b/policyengine_us/parameters/gov/states/sd/dss/ssp/couple_first_amount.yaml index 1f88fa7b487..dbba81adc59 100644 --- a/policyengine_us/parameters/gov/states/sd/dss/ssp/couple_first_amount.yaml +++ b/policyengine_us/parameters/gov/states/sd/dss/ssp/couple_first_amount.yaml @@ -1,10 +1,11 @@ description: South Dakota pays this amount as its first member independent supplement. values: - '1978-07-01': 8 + 1978-07-01: 8 + metadata: unit: currency-USD + period: month label: South Dakota SSP first member independent supplement reference: - - title: SSA POMS SI DEN01415.011 B.5 - href: https://secure.ssa.gov/poms.nsf/lnx/0501415011DEN - period: month + - title: SSA POMS SI DEN01415.011 B.5 + href: https://secure.ssa.gov/poms.nsf/lnx/0501415011DEN diff --git a/policyengine_us/parameters/gov/states/sd/dss/ssp/couple_second_amount.yaml b/policyengine_us/parameters/gov/states/sd/dss/ssp/couple_second_amount.yaml index e3519249aaa..8185c5c8223 100644 --- a/policyengine_us/parameters/gov/states/sd/dss/ssp/couple_second_amount.yaml +++ b/policyengine_us/parameters/gov/states/sd/dss/ssp/couple_second_amount.yaml @@ -1,10 +1,11 @@ description: South Dakota pays this amount as its second member independent supplement. values: - '1978-07-01': 7 + 1978-07-01: 7 + metadata: unit: currency-USD + period: month label: South Dakota SSP second member independent supplement reference: - - title: SSA POMS SI DEN01415.011 B.5 - href: https://secure.ssa.gov/poms.nsf/lnx/0501415011DEN - period: month + - title: SSA POMS SI DEN01415.011 B.5 + href: https://secure.ssa.gov/poms.nsf/lnx/0501415011DEN diff --git a/policyengine_us/parameters/gov/states/sd/dss/ssp/individual_amount.yaml b/policyengine_us/parameters/gov/states/sd/dss/ssp/individual_amount.yaml index d345c4dda2e..086ca2d98cf 100644 --- a/policyengine_us/parameters/gov/states/sd/dss/ssp/individual_amount.yaml +++ b/policyengine_us/parameters/gov/states/sd/dss/ssp/individual_amount.yaml @@ -1,10 +1,11 @@ description: South Dakota pays this amount as its individual independent supplement. values: - '1978-07-01': 15 + 1978-07-01: 15 + metadata: unit: currency-USD + period: month label: South Dakota SSP individual independent supplement reference: - - title: SSA POMS SI DEN01415.011 B.5 - href: https://secure.ssa.gov/poms.nsf/lnx/0501415011DEN - period: month + - title: SSA POMS SI DEN01415.011 B.5 + href: https://secure.ssa.gov/poms.nsf/lnx/0501415011DEN diff --git a/policyengine_us/parameters/gov/states/sd/dss/ssp/joint_entry_resource_limit.yaml b/policyengine_us/parameters/gov/states/sd/dss/ssp/joint_entry_resource_limit.yaml index 39be98208e4..8c9ce6ac080 100644 --- a/policyengine_us/parameters/gov/states/sd/dss/ssp/joint_entry_resource_limit.yaml +++ b/policyengine_us/parameters/gov/states/sd/dss/ssp/joint_entry_resource_limit.yaml @@ -1,9 +1,10 @@ description: South Dakota applies this joint entry resource limit to long-term care assistance. values: - '2026-01-01': 3000 + 2026-01-01: 3_000 + metadata: unit: currency-USD label: South Dakota SSP joint entry resource limit reference: - - title: ARSD 67:46:05:30 - href: https://sdlegislature.gov/Rules/Administrative/67:46:05:30 + - title: ARSD 67:46:05:30 + href: https://sdlegislature.gov/Rules/Administrative/67:46:05:30 diff --git a/policyengine_us/parameters/gov/states/sd/dss/ssp/minimum_age.yaml b/policyengine_us/parameters/gov/states/sd/dss/ssp/minimum_age.yaml index 59f3df69ea9..5caf6967c61 100644 --- a/policyengine_us/parameters/gov/states/sd/dss/ssp/minimum_age.yaml +++ b/policyengine_us/parameters/gov/states/sd/dss/ssp/minimum_age.yaml @@ -1,9 +1,10 @@ description: South Dakota provides its independent state supplement to adults at or above this age. values: - '1978-08-06': 18 + 1978-08-06: 18 + metadata: unit: year label: South Dakota SSP minimum age reference: - - title: ARSD 67:12:14:02 - href: https://sdlegislature.gov/Rules/Administrative/67:12:14:02 + - title: ARSD 67:12:14:02 + href: https://sdlegislature.gov/Rules/Administrative/67:12:14:02 diff --git a/policyengine_us/parameters/gov/states/sd/dss/ssp/resource_limit.yaml b/policyengine_us/parameters/gov/states/sd/dss/ssp/resource_limit.yaml index 6a85fb53a4d..23b7e9baf7e 100644 --- a/policyengine_us/parameters/gov/states/sd/dss/ssp/resource_limit.yaml +++ b/policyengine_us/parameters/gov/states/sd/dss/ssp/resource_limit.yaml @@ -1,9 +1,10 @@ description: South Dakota applies this individual resource limit to long-term care assistance. values: - '2026-01-01': 2000 + 2026-01-01: 2_000 + metadata: unit: currency-USD label: South Dakota SSP individual resource limit reference: - - title: ARSD 67:46:05:30 - href: https://sdlegislature.gov/Rules/Administrative/67:46:05:30 + - title: ARSD 67:46:05:30 + href: https://sdlegislature.gov/Rules/Administrative/67:46:05:30 diff --git a/policyengine_us/tests/policy/baseline/gov/states/sd/dss/ssp/integration.yaml b/policyengine_us/tests/policy/baseline/gov/states/sd/dss/ssp/integration.yaml index e6bf3e071c8..49a6fd56bc1 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/sd/dss/ssp/integration.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/sd/dss/ssp/integration.yaml @@ -3,57 +3,47 @@ absolute_error_margin: 0.01 input: people: - first: + person1: age: 65 - second: + person2: age: 65 marital_units: couple: - members: - - first - - second + members: [person1, person2] households: hh: - members: - - first - - second + members: [person1, person2] state_code: SD output: - sd_ssp: - - 8 - - 7 + sd_ssp: [8, 7] + - name: Case 2, annual independent supplement and SNAP income. - period: '2026' + period: 2026 absolute_error_margin: 0.01 input: age: 65 state_code: SD output: sd_ssp: 180 - snap_unearned_income: 12108 - hud_unearned_income: 12108 -- absolute_error_margin: 0.01 + snap_unearned_income: 12_108 + hud_unearned_income: 12_108 + +- name: Case 3, other cash income of either eligible spouse excludes the couple. + period: 2026-01 + absolute_error_margin: 0.01 input: - households: - hh: - members: - - first - - second - state_code: SD - marital_units: - couple: - members: - - first - - second people: - first: + person1: age: 65 - second: + person2: age: 65 social_security_retirement: 120 - name: Case 3, other cash income of either eligible spouse excludes the couple. + marital_units: + couple: + members: [person1, person2] + households: + hh: + members: [person1, person2] + state_code: SD output: - sd_ssp: - - 0 - - 0 - period: 2026-01 + sd_ssp: [0, 0] diff --git a/policyengine_us/tests/policy/baseline/gov/states/sd/dss/ssp/sd_ssp.yaml b/policyengine_us/tests/policy/baseline/gov/states/sd/dss/ssp/sd_ssp.yaml index d08044f7f3d..728c8457a90 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/sd/dss/ssp/sd_ssp.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/sd/dss/ssp/sd_ssp.yaml @@ -3,229 +3,248 @@ absolute_error_margin: 0.01 input: state_code: - '2026': SD + 2026: SD age: - '2026': 65 + 2026: 65 output: sd_ssp: 15 + - name: Case 2, other cash income below SSI disregard still disqualifies. period: 2026-01 absolute_error_margin: 0.01 input: state_code: - '2026': SD + 2026: SD age: - '2026': 65 + 2026: 65 social_security_retirement: 120 output: sd_ssp: 0 + - name: Case 3, in-kind support does not disqualify. period: 2026-01 absolute_error_margin: 0.01 input: state_code: - '2026': SD + 2026: SD age: - '2026': 65 + 2026: 65 ssi_federal_living_arrangement: ANOTHER_PERSONS_HOUSEHOLD output: sd_ssp: 15 + - name: Case 4, child is not eligible for independent supplement. period: 2026-01 absolute_error_margin: 0.01 input: state_code: - '2026': SD + 2026: SD age: 17 is_blind: true output: sd_ssp: 0 + - name: Case 5, student is not eligible for independent supplement. period: 2026-01 absolute_error_margin: 0.01 input: state_code: - '2026': SD + 2026: SD age: - '2026': 65 + 2026: 65 is_full_time_student: true output: sd_ssp: 0 + - name: Case 6, institutional resident is excluded. period: 2026-01 absolute_error_margin: 0.01 input: state_code: - '2026': SD + 2026: SD age: - '2026': 65 + 2026: 65 ssi_federal_living_arrangement: MEDICAL_TREATMENT_FACILITY output: sd_ssp: 0 + - name: Case 7, no SSI payment. period: 2026-01 absolute_error_margin: 0.01 input: state_code: - '2026': SD + 2026: SD age: - '2026': 65 + 2026: 65 ssi: 0 output: sd_ssp: 0 + - name: Case 8, nonresident. period: 2026-01 absolute_error_margin: 0.01 input: state_code: WY age: - '2026': 65 + 2026: 65 output: sd_ssp: 0 + - name: Case 9, care payment uses approved monthly care bill less income. period: 2026-01 absolute_error_margin: 0.01 input: state_code: - '2026': SD + 2026: SD age: - '2026': 65 + 2026: 65 sd_ssp_care_certified: true - sd_ssp_approved_care_cost: 2200 + sd_ssp_approved_care_cost: 2_200 output: - sd_ssp: 1206 + sd_ssp: 1_206 + - name: Case 10, actual care bill capped at January daily reimbursement. period: 2026-01 absolute_error_margin: 0.01 input: state_code: - '2026': SD + 2026: SD age: - '2026': 65 + 2026: 65 sd_ssp_care_certified: true - sd_ssp_approved_care_cost: 3000 + sd_ssp_approved_care_cost: 3_000 output: - sd_ssp: 1397.96 + sd_ssp: 1_397.96 + - name: Case 11, July retains the January daily reimbursement cap. period: 2026-07 absolute_error_margin: 0.01 input: state_code: - '2026': SD + 2026: SD age: - '2026': 65 + 2026: 65 sd_ssp_care_certified: true - sd_ssp_approved_care_cost: 3000 + sd_ssp_approved_care_cost: 3_000 output: - sd_ssp: 1397.96 + sd_ssp: 1_397.96 + - name: Case 12, February has twenty eight billable days. period: 2026-02 absolute_error_margin: 0.01 input: state_code: - '2026': SD + 2026: SD age: - '2026': 65 + 2026: 65 sd_ssp_care_certified: true - sd_ssp_approved_care_cost: 3000 + sd_ssp_approved_care_cost: 3_000 output: - sd_ssp: 1166.48 + sd_ssp: 1_166.48 + - name: Case 13, state approved deductions reduce cost share. period: 2026-01 absolute_error_margin: 0.01 input: state_code: - '2026': SD + 2026: SD age: - '2026': 65 + 2026: 65 sd_ssp_care_certified: true - sd_ssp_approved_care_cost: 2200 + sd_ssp_approved_care_cost: 2_200 sd_ssp_care_income_deductions: 100 output: - sd_ssp: 1306 + sd_ssp: 1_306 + - name: Case 14, no automatic twenty dollar SSI disregard for care. period: 2026-01 absolute_error_margin: 0.01 input: state_code: - '2026': SD + 2026: SD age: - '2026': 65 + 2026: 65 sd_ssp_care_certified: true - sd_ssp_approved_care_cost: 2200 - social_security_retirement: 24000 + sd_ssp_approved_care_cost: 2_200 + social_security_retirement: 24_000 output: sd_ssp: 200 + - name: Case 15, excess resources. period: 2026-01 absolute_error_margin: 0.01 input: state_code: - '2026': SD + 2026: SD age: - '2026': 65 + 2026: 65 sd_ssp_care_certified: true - sd_ssp_approved_care_cost: 2200 - ssi_countable_resources: 2001 + sd_ssp_approved_care_cost: 2_200 + ssi_countable_resources: 2_001 output: sd_ssp: 0 + - name: Case 16, certified partial month without income contribution. period: 2026-01 absolute_error_margin: 0.01 input: state_code: - '2026': SD + 2026: SD age: - '2026': 65 + 2026: 65 sd_ssp_care_certified: true - sd_ssp_approved_care_cost: 1500 + sd_ssp_approved_care_cost: 1_500 sd_ssp_care_days: 10 sd_ssp_care_income_not_applied: true output: sd_ssp: 771.6 + - name: Case 17, adult foster care needs its approved cost. period: 2026-01 absolute_error_margin: 0.01 input: state_code: - '2026': SD + 2026: SD age: - '2026': 65 + 2026: 65 sd_ssp_care_certified: true sd_ssp_is_adult_foster_care: true - sd_ssp_approved_care_cost: 1500 + sd_ssp_approved_care_cost: 1_500 output: sd_ssp: 506 + - name: Case 18, no care payment without approved cost. period: 2026-01 absolute_error_margin: 0.01 input: state_code: - '2026': SD + 2026: SD age: - '2026': 65 + 2026: 65 sd_ssp_care_certified: true output: sd_ssp: 0 + - name: Case 19, care needs certified medical eligibility. period: 2026-01 absolute_error_margin: 0.01 input: state_code: - '2026': SD + 2026: SD age: 40 sd_ssp_care_certified: true - sd_ssp_approved_care_cost: 2200 + sd_ssp_approved_care_cost: 2_200 output: sd_ssp: 0 + - name: Case 20, SSI opt out. period: 2026-01 absolute_error_margin: 0.01 input: state_code: - '2026': SD + 2026: SD age: - '2026': 65 + 2026: 65 takes_up_ssi_if_eligible: false output: sd_ssp: 0 @@ -242,9 +261,9 @@ 2026-06: true 2026-07: true sd_ssp_approved_care_cost: - 2026-06: 3000 - 2026-07: 3000 + 2026-06: 3_000 + 2026-07: 3_000 output: sd_ssp: - 2026-06: 1320.8 - 2026-07: 1397.96 + 2026-06: 1_320.8 + 2026-07: 1_397.96 From ebe5efb863ff9acaa6902b3d47b3b38a737666a6 Mon Sep 17 00:00:00 2001 From: Ziming Date: Tue, 13 Oct 2026 01:37:51 -0400 Subject: [PATCH 3/6] Fix issues from review: South Dakota SSP gates, couple rule and tests - Build per-source income arrays before summing (code-health builtin-sum rule) - Gate the independent supplement on (ssi > 0) | receives_ssi - Pay the $8/$7 couple split only when both spouses qualify; a lone qualifier gets $15 - Default the partial-month no-income rule from care days (ARSD 67:46:06:09) - Drop the SSI take-up gate from the care payment - Registry ordering, parameter periods, shorter programs.yaml note - Realistic Case 2 income; add death/transfer, receives_ssi, couple, joint-entry, care-days and earned-income cases Co-Authored-By: Claude Opus 5.5 --- .../household/household_state_benefits.yaml | 12 +- .../dss/ssp/joint_entry_resource_limit.yaml | 1 + .../gov/states/sd/dss/ssp/minimum_age.yaml | 1 + .../gov/states/sd/dss/ssp/resource_limit.yaml | 1 + .../usda/snap/income/sources/unearned.yaml | 2 +- policyengine_us/programs.yaml | 7 +- .../gov/states/sd/dss/ssp/integration.yaml | 102 +++++++++++++++- .../gov/states/sd/dss/ssp/sd_ssp.yaml | 111 +++++++++++++++++- .../gov/states/sd/dss/ssp/sd_ssp_care.py | 1 - .../dss/ssp/sd_ssp_care_income_not_applied.py | 5 +- .../states/sd/dss/ssp/sd_ssp_independent.py | 24 ++-- .../income/spm_unit/spm_unit_benefits.py | 2 +- 12 files changed, 244 insertions(+), 25 deletions(-) diff --git a/policyengine_us/parameters/gov/household/household_state_benefits.yaml b/policyengine_us/parameters/gov/household/household_state_benefits.yaml index 473ed3d7a51..03100f56233 100644 --- a/policyengine_us/parameters/gov/household/household_state_benefits.yaml +++ b/policyengine_us/parameters/gov/household/household_state_benefits.yaml @@ -69,6 +69,8 @@ values: - nm_ssi_state_supplement # South Carolina benefits - sc_ssi_state_supplement + # South Dakota benefits + - sd_ssp # Texas benefits - tx_ssi_state_supplement # Virginia benefits @@ -78,7 +80,6 @@ values: # DC benefits - dc_ossp # Kansas benefits - - sd_ssp - ks_sspp # Hawaii benefits - hi_oss @@ -162,6 +163,8 @@ values: - nm_ssi_state_supplement # South Carolina benefits - sc_ssi_state_supplement + # South Dakota benefits + - sd_ssp # Texas benefits - tx_ssi_state_supplement # Virginia benefits @@ -177,7 +180,6 @@ values: # DC benefits - dc_ossp # Kansas benefits - - sd_ssp - ks_sspp # Hawaii benefits - hi_oss @@ -265,6 +267,8 @@ values: - nm_ssi_state_supplement # South Carolina benefits - sc_ssi_state_supplement + # South Dakota benefits + - sd_ssp # Texas benefits - tx_ssi_state_supplement # Virginia benefits @@ -281,7 +285,6 @@ values: - dc_ossp - dc_child_care_subsidies # Kansas benefits - - sd_ssp - ks_sspp # Hawaii benefits - hi_oss @@ -369,6 +372,8 @@ values: - nm_ssi_state_supplement # South Carolina benefits - sc_ssi_state_supplement + # South Dakota benefits + - sd_ssp # Texas benefits - tx_ssi_state_supplement # Virginia benefits @@ -385,7 +390,6 @@ values: - dc_ossp - dc_child_care_subsidies # Kansas benefits - - sd_ssp - ks_sspp # Hawaii benefits - hi_oss diff --git a/policyengine_us/parameters/gov/states/sd/dss/ssp/joint_entry_resource_limit.yaml b/policyengine_us/parameters/gov/states/sd/dss/ssp/joint_entry_resource_limit.yaml index 8c9ce6ac080..19c96e508d0 100644 --- a/policyengine_us/parameters/gov/states/sd/dss/ssp/joint_entry_resource_limit.yaml +++ b/policyengine_us/parameters/gov/states/sd/dss/ssp/joint_entry_resource_limit.yaml @@ -4,6 +4,7 @@ values: metadata: unit: currency-USD + period: month label: South Dakota SSP joint entry resource limit reference: - title: ARSD 67:46:05:30 diff --git a/policyengine_us/parameters/gov/states/sd/dss/ssp/minimum_age.yaml b/policyengine_us/parameters/gov/states/sd/dss/ssp/minimum_age.yaml index 5caf6967c61..3b1f99ead3a 100644 --- a/policyengine_us/parameters/gov/states/sd/dss/ssp/minimum_age.yaml +++ b/policyengine_us/parameters/gov/states/sd/dss/ssp/minimum_age.yaml @@ -4,6 +4,7 @@ values: metadata: unit: year + period: year label: South Dakota SSP minimum age reference: - title: ARSD 67:12:14:02 diff --git a/policyengine_us/parameters/gov/states/sd/dss/ssp/resource_limit.yaml b/policyengine_us/parameters/gov/states/sd/dss/ssp/resource_limit.yaml index 23b7e9baf7e..3b3aa270307 100644 --- a/policyengine_us/parameters/gov/states/sd/dss/ssp/resource_limit.yaml +++ b/policyengine_us/parameters/gov/states/sd/dss/ssp/resource_limit.yaml @@ -4,6 +4,7 @@ values: metadata: unit: currency-USD + period: month label: South Dakota SSP individual resource limit reference: - title: ARSD 67:46:05:30 diff --git a/policyengine_us/parameters/gov/usda/snap/income/sources/unearned.yaml b/policyengine_us/parameters/gov/usda/snap/income/sources/unearned.yaml index 629dfa3e0b4..0a19ea56f30 100644 --- a/policyengine_us/parameters/gov/usda/snap/income/sources/unearned.yaml +++ b/policyengine_us/parameters/gov/usda/snap/income/sources/unearned.yaml @@ -36,7 +36,6 @@ values: - hi_oss - id_aabd - in_ssp - - sd_ssp - ks_sspp - ky_ssp - la_oss @@ -49,6 +48,7 @@ values: - ne_aabd - nm_ssi_state_supplement - sc_ssi_state_supplement + - sd_ssp - tx_ssi_state_supplement - wa_ssp # Listed elsewhere: ca_state_supplement is an SPM unit variable with no diff --git a/policyengine_us/programs.yaml b/policyengine_us/programs.yaml index 40c43c76700..9d74340d7e5 100644 --- a/policyengine_us/programs.yaml +++ b/policyengine_us/programs.yaml @@ -1097,10 +1097,9 @@ programs: variable: sd_ssp parameter_prefix: gov.states.sd.dss.ssp verified_years: '2026' - notes: Independent living and certified nonwaiver assisted living/adult foster care. - Approved care charges and state-specific deductions require inputs; only assisted living - has a verified 2026 reimbursement cap. Out-of-state placements, historical mandatory - supplements and claim administration are not modeled. + notes: Models 2026 independent living and certified nonwaiver assisted living/adult foster + care supplements. Out-of-state placements, historical mandatory supplements and claim + administration are not modeled. - id: social_security name: Social Security diff --git a/policyengine_us/tests/policy/baseline/gov/states/sd/dss/ssp/integration.yaml b/policyengine_us/tests/policy/baseline/gov/states/sd/dss/ssp/integration.yaml index 49a6fd56bc1..c7ddd3e4c5c 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/sd/dss/ssp/integration.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/sd/dss/ssp/integration.yaml @@ -37,7 +37,7 @@ age: 65 person2: age: 65 - social_security_retirement: 120 + social_security_retirement: 6_000 marital_units: couple: members: [person1, person2] @@ -46,4 +46,104 @@ members: [person1, person2] state_code: SD output: + # Couple countable (6_000 / 12 - 20) / 2 = 240 each; + # SSI = 1_491 / 2 - 240 = 505.50 each. + ssi: [505.5, 505.5] + # Both qualify, so pooled other income of 500 > 0 excludes both. sd_ssp: [0, 0] + +- name: Case 4, lone qualifying spouse listed second receives the individual amount. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 65 + immigration_status: UNDOCUMENTED + person2: + age: 65 + marital_units: + couple: + members: [person1, person2] + households: + hh: + members: [person1, person2] + state_code: SD + output: + # Both are aged, so the SSI claim is joint, but only person2 (citizen) + # receives SSI; person1 has no SSI and no reported receipt. + # One qualifier: individual 15, not the 7 second couple amount. + sd_ssp_independent: [0, 15] + sd_ssp_care: [0, 0] + sd_ssp: [0, 15] + +- name: Case 5, joint entry pools spouses' resources against the joint limit. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 65 + social_security_retirement: 14_400 + ssi_countable_resources: 1_800 + sd_ssp_care_certified: true + sd_ssp_approved_care_cost: 2_200 + sd_ssp_care_joint_entry: true + person2: + age: 65 + social_security_retirement: 9_600 + ssi_countable_resources: 1_400 + sd_ssp_care_certified: true + sd_ssp_approved_care_cost: 2_200 + sd_ssp_care_joint_entry: true + marital_units: + couple: + members: [person1, person2] + households: + hh: + members: [person1, person2] + state_code: SD + output: + # SSI is 0 on either ground: couple countable income + # (1_200 + 800 - 20) / 2 = 990 > 1_491 / 2 = 745.50, and pooled + # resources 1_800 + 1_400 = 3_200 > 3_000 SSI couple resource limit. + ssi: [0, 0] + sd_ssp_independent: [0, 0] + # Pooled 1_800 + 1_400 = 3_200 > 3_000 joint entry limit. + sd_ssp_care: [0, 0] + sd_ssp: [0, 0] + +- name: Case 6, without joint entry each spouse meets the individual limit. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 65 + social_security_retirement: 14_400 + ssi_countable_resources: 1_800 + sd_ssp_care_certified: true + sd_ssp_approved_care_cost: 2_200 + person2: + age: 65 + social_security_retirement: 9_600 + ssi_countable_resources: 1_400 + sd_ssp_care_certified: true + sd_ssp_approved_care_cost: 2_200 + marital_units: + couple: + members: [person1, person2] + households: + hh: + members: [person1, person2] + state_code: SD + output: + # SSI is 0 on either ground: couple countable income + # (1_200 + 800 - 20) / 2 = 990 > 1_491 / 2 = 745.50, and pooled + # resources 1_800 + 1_400 = 3_200 > 3_000 SSI couple resource limit. + ssi: [0, 0] + sd_ssp_independent: [0, 0] + # 1_800 and 1_400 are each <= 2_000; min(2_200, 31 x 77.16 = 2_391.96) + # less own income: 2_200 - 14_400 / 12 = 1_000; 2_200 - 9_600 / 12 = 1_400. + sd_ssp_care: [1_000, 1_400] + sd_ssp: [1_000, 1_400] diff --git a/policyengine_us/tests/policy/baseline/gov/states/sd/dss/ssp/sd_ssp.yaml b/policyengine_us/tests/policy/baseline/gov/states/sd/dss/ssp/sd_ssp.yaml index 728c8457a90..f8a84223ac7 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/sd/dss/ssp/sd_ssp.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/sd/dss/ssp/sd_ssp.yaml @@ -9,7 +9,7 @@ output: sd_ssp: 15 -- name: Case 2, other cash income below SSI disregard still disqualifies. +- name: Case 2, small Social Security benefit disqualifies despite an SSI payment. period: 2026-01 absolute_error_margin: 0.01 input: @@ -17,8 +17,11 @@ 2026: SD age: 2026: 65 - social_security_retirement: 120 + social_security_retirement: 6_000 output: + # 6_000 / 12 = 500 per month; SSI = 994 - (500 - 20) = 514. + ssi: 514 + # Other cash income of 500 > 0 disqualifies. sd_ssp: 0 - name: Case 3, in-kind support does not disqualify. @@ -195,8 +198,9 @@ sd_ssp_care_certified: true sd_ssp_approved_care_cost: 1_500 sd_ssp_care_days: 10 - sd_ssp_care_income_not_applied: true output: + # 10 < 31 days, so income is not applied by default; + # min(1_500, 10 x 77.16 = 771.60) - 0 = 771.60. sd_ssp: 771.6 - name: Case 17, adult foster care needs its approved cost. @@ -267,3 +271,104 @@ sd_ssp: 2026-06: 1_320.8 2026-07: 1_397.96 + +- name: Case 22, death or transfer partial month applies income. + period: 2026-01 + absolute_error_margin: 0.01 + input: + state_code: + 2026: SD + age: + 2026: 65 + sd_ssp_care_certified: true + sd_ssp_approved_care_cost: 1_800 + sd_ssp_care_days: 20 + sd_ssp_care_income_not_applied: false + output: + # min(1_800, 20 x 77.16 = 1_543.20) - SSI 994 = 549.20. + sd_ssp: 549.2 + +- name: Case 23, care payment does not require SSI takeup. + period: 2026-01 + absolute_error_margin: 0.01 + input: + state_code: + 2026: SD + age: + 2026: 65 + social_security_retirement: 18_000 + takes_up_ssi_if_eligible: false + sd_ssp_care_certified: true + sd_ssp_approved_care_cost: 2_200 + output: + # 18_000 / 12 = 1_500 per month; (1_500 - 20) > 994 leaves no SSI. + ssi: 0 + # min(2_200, 31 x 77.16 = 2_391.96) - 1_500 = 700. + sd_ssp: 700 + +- name: Case 24, reported SSI receipt qualifies when modeled SSI is zero. + period: 2026-01 + absolute_error_margin: 0.01 + input: + state_code: + 2026: SD + age: + 2026: 50 + receives_ssi: true + output: + # Age 50 with no modeled disability is not aged, blind or disabled. + ssi: 0 + # Reported receipt meets the SSI gate; no other cash income. + sd_ssp: 15 + +- name: Case 25, care days above the month length are capped at the month. + period: 2026-02 + absolute_error_margin: 0.01 + input: + state_code: + 2026: SD + age: + 2026: 65 + sd_ssp_care_certified: true + sd_ssp_approved_care_cost: 3_000 + sd_ssp_care_days: 31 + output: + # Days clip to 28; 31 < 28 is false, so income applies; + # min(3_000, 28 x 77.16 = 2_160.48) - SSI 994 = 1_166.48. + sd_ssp: 1_166.48 + +- name: Case 26, no covered care days pays nothing. + period: 2026-01 + absolute_error_margin: 0.01 + input: + state_code: + 2026: SD + age: + 2026: 65 + sd_ssp_care_certified: true + sd_ssp_is_adult_foster_care: true + sd_ssp_approved_care_cost: 1_500 + sd_ssp_care_days: 0 + output: + # 0 days fails days > 0, although the uncapped foster care bill + # is 1_500 and 0 < 31 means no income contribution. + sd_ssp: 0 + +- name: Case 27, earned income and SSI both count toward the care contribution. + period: 2026-01 + absolute_error_margin: 0.01 + input: + state_code: + 2026: SD + age: + 2026: 45 + meets_ssi_disability_criteria: true + employment_income: 3_600 + sd_ssp_care_certified: true + sd_ssp_approved_care_cost: 2_200 + output: + # 3_600 / 12 = 300 per month; countable (300 - 20 - 65) x 0.5 = 107.50; + # SSI = 994 - 107.50 = 886.50. + ssi: 886.5 + # min(2_200, 2_391.96) - (300 + 886.50) = 1_013.50. + sd_ssp: 1_013.5 diff --git a/policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp_care.py b/policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp_care.py index 738597922e2..5e5080c37f5 100644 --- a/policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp_care.py +++ b/policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp_care.py @@ -26,7 +26,6 @@ def formula(person, period, parameters): & citizenship & (resources <= limit) & ~person("is_incarcerated", period) - & person("takes_up_ssi_if_eligible", period) ) days = clip(person("sd_ssp_care_days", period), 0, period.days) cost = max_(person("sd_ssp_approved_care_cost", period), 0) diff --git a/policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp_care_income_not_applied.py b/policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp_care_income_not_applied.py index e9577bf4f6c..db75a89022a 100644 --- a/policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp_care_income_not_applied.py +++ b/policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp_care_income_not_applied.py @@ -7,4 +7,7 @@ class sd_ssp_care_income_not_applied(Variable): label = "South Dakota care month has no income contribution" definition_period = MONTH reference = "https://sdlegislature.gov/Rules/Administrative/67:46:06:09" - documentation = "Certified partial-month exception under ARSD 67:46:06:09; excludes death and transfer months and cases with an assessed contribution." + documentation = "Defaults to true for a partial care month under ARSD 67:46:06:09. Set to false for a month in which the recipient died or transferred to another long-term care facility." + + def formula(person, period, parameters): + return person("sd_ssp_care_days", period) < period.days diff --git a/policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp_independent.py b/policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp_independent.py index cd0da4ba773..5695baaa372 100644 --- a/policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp_independent.py +++ b/policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp_independent.py @@ -13,12 +13,12 @@ class sd_ssp_independent(Variable): def formula(person, period, parameters): p = parameters(period).gov.states.sd.dss.ssp source = parameters(period).gov.ssa.ssi.income.sources - other_income = sum( + floored_sources = [ max_(person(name, period), 0) for name in list(source.earned) + list(source.unearned) - ) - joint = person("ssi_claim_is_joint", period.this_year) - other_income = where(joint, person.marital_unit.sum(other_income), other_income) + ] + own_income = sum(floored_sources) + receives_ssi = (person("ssi", period) > 0) | person("receives_ssi", period) arrangement = person("ssi_federal_living_arrangement", period) la = arrangement.possible_values independent = (arrangement == la.OWN_HOUSEHOLD) | ( @@ -27,18 +27,24 @@ def formula(person, period, parameters): student = person("is_full_time_student", period.this_year) | person( "is_part_time_college_student", period.this_year ) - eligible = ( - (person("ssi", period) > 0) - & (other_income == 0) + qualifies = ( + receives_ssi & independent & (person("age", period.this_year) >= p.minimum_age) & ~student & ~person("sd_ssp_care_certified", period) ) + # NOTE: ARSD 67:12:14 and POMS DEN01415.011 B.5 are silent on a + # married recipient whose spouse does not qualify; that recipient is + # treated as an individual, with no spousal income pooling. + couple = person("ssi_claim_is_joint", period.this_year) & ( + person.marital_unit.sum(qualifies) > 1 + ) + other_income = where(couple, person.marital_unit.sum(own_income), own_income) first = person.marital_unit.reference_entity.members_position == 0 amount = where( - joint, + couple, where(first, p.couple_first_amount, p.couple_second_amount), p.individual_amount, ) - return eligible * amount + return (qualifies & (other_income == 0)) * amount diff --git a/policyengine_us/variables/household/income/spm_unit/spm_unit_benefits.py b/policyengine_us/variables/household/income/spm_unit/spm_unit_benefits.py index 8447cfa34cb..9f36bbfcae0 100644 --- a/policyengine_us/variables/household/income/spm_unit/spm_unit_benefits.py +++ b/policyengine_us/variables/household/income/spm_unit/spm_unit_benefits.py @@ -22,7 +22,7 @@ def formula(spm_unit, period, parameters): "ky_ssp", # Kentucky benefits "de_ssp", # Delaware benefits "fl_oss", - "sd_ssp", + "sd_ssp", # South Dakota benefits "ks_sspp", # Kansas benefits "hi_oss", "la_oss", # Louisiana benefits From ee7bc4be0657fef27ef9e7aacb421dfacec83c0a Mon Sep 17 00:00:00 2001 From: Ziming Date: Tue, 13 Oct 2026 02:28:56 -0400 Subject: [PATCH 4/6] =?UTF-8?q?Fix=20issues=20from=20review:=20South=20Dak?= =?UTF-8?q?ota=20SSP=20round=202=20=E2=80=94=20SNAP=20title,=20notes=20and?= =?UTF-8?q?=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Correct the shared SNAP reference title to 7 CFR 273.9(b)(2) and (c)(1) - Document the SSI-receipt assumption in the care contribution and the joint-claim couple condition - Add joint-entry pooled-limit, immigration, incarceration and student-exclusion cases Co-Authored-By: Claude Opus 5.5 --- .../usda/snap/income/sources/unearned.yaml | 2 +- .../gov/states/sd/dss/ssp/integration.yaml | 75 +++++++++++++++++++ .../gov/states/sd/dss/ssp/sd_ssp.yaml | 69 +++++++++++++++++ .../gov/states/sd/dss/ssp/sd_ssp_care.py | 3 + .../states/sd/dss/ssp/sd_ssp_independent.py | 3 + 5 files changed, 151 insertions(+), 1 deletion(-) diff --git a/policyengine_us/parameters/gov/usda/snap/income/sources/unearned.yaml b/policyengine_us/parameters/gov/usda/snap/income/sources/unearned.yaml index 0a19ea56f30..0782aec4cd2 100644 --- a/policyengine_us/parameters/gov/usda/snap/income/sources/unearned.yaml +++ b/policyengine_us/parameters/gov/usda/snap/income/sources/unearned.yaml @@ -96,7 +96,7 @@ values: metadata: reference: - - title: 7 CFR 273.9(b)(2) and (c)(1), (5) + - title: 7 CFR 273.9(b)(2) and (c)(1) href: https://www.law.cornell.edu/cfr/text/7/273.9 - title: POMS SI 01401.001 General Information about State Supplementation href: https://secure.ssa.gov/poms.nsf/lnx/0501401001 diff --git a/policyengine_us/tests/policy/baseline/gov/states/sd/dss/ssp/integration.yaml b/policyengine_us/tests/policy/baseline/gov/states/sd/dss/ssp/integration.yaml index c7ddd3e4c5c..18d74a21a8a 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/sd/dss/ssp/integration.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/sd/dss/ssp/integration.yaml @@ -147,3 +147,78 @@ # less own income: 2_200 - 14_400 / 12 = 1_000; 2_200 - 9_600 / 12 = 1_400. sd_ssp_care: [1_000, 1_400] sd_ssp: [1_000, 1_400] + +- name: Case 7, joint entry within the pooled limit qualifies both spouses. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 65 + social_security_retirement: 14_400 + ssi_countable_resources: 2_100 + sd_ssp_care_certified: true + sd_ssp_approved_care_cost: 2_200 + sd_ssp_care_joint_entry: true + person2: + age: 65 + social_security_retirement: 9_600 + ssi_countable_resources: 800 + sd_ssp_care_certified: true + sd_ssp_approved_care_cost: 2_200 + sd_ssp_care_joint_entry: true + marital_units: + couple: + members: [person1, person2] + households: + hh: + members: [person1, person2] + state_code: SD + output: + # SSI couple resources 2_100 + 800 = 2_900 <= 3_000 pass. + meets_ssi_resource_test: [true, true] + # Couple countable income (1_200 + 800 - 20) / 2 = 990 + # > 1_491 / 2 = 745.50, so SSI is 0. + ssi: [0, 0] + sd_ssp_independent: [0, 0] + # Pooled 2_100 + 800 = 2_900 <= 3_000 joint entry limit; + # min(2_200, 31 x 77.16 = 2_391.96) less own income: + # 2_200 - 14_400 / 12 = 1_000; 2_200 - 9_600 / 12 = 1_400. + sd_ssp_care: [1_000, 1_400] + sd_ssp: [1_000, 1_400] + +- name: Case 8, same couple without joint entry faces the individual limit. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 65 + social_security_retirement: 14_400 + ssi_countable_resources: 2_100 + sd_ssp_care_certified: true + sd_ssp_approved_care_cost: 2_200 + person2: + age: 65 + social_security_retirement: 9_600 + ssi_countable_resources: 800 + sd_ssp_care_certified: true + sd_ssp_approved_care_cost: 2_200 + marital_units: + couple: + members: [person1, person2] + households: + hh: + members: [person1, person2] + state_code: SD + output: + # SSI still pools the couple: 2_100 + 800 = 2_900 <= 3_000. + meets_ssi_resource_test: [true, true] + # Couple countable income (1_200 + 800 - 20) / 2 = 990 + # > 1_491 / 2 = 745.50, so SSI is 0. + ssi: [0, 0] + sd_ssp_independent: [0, 0] + # Individual 2_000 limit: person1 2_100 > 2_000 is ineligible; + # person2 800 <= 2_000: min(2_200, 2_391.96) - 9_600 / 12 = 1_400. + sd_ssp_care: [0, 1_400] + sd_ssp: [0, 1_400] diff --git a/policyengine_us/tests/policy/baseline/gov/states/sd/dss/ssp/sd_ssp.yaml b/policyengine_us/tests/policy/baseline/gov/states/sd/dss/ssp/sd_ssp.yaml index f8a84223ac7..ab4f97ed76d 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/sd/dss/ssp/sd_ssp.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/sd/dss/ssp/sd_ssp.yaml @@ -372,3 +372,72 @@ ssi: 886.5 # min(2_200, 2_391.96) - (300 + 886.50) = 1_013.50. sd_ssp: 1_013.5 + +- name: Case 28, care resident who is not an SSI qualified noncitizen. + period: 2026-01 + absolute_error_margin: 0.01 + input: + state_code: + 2026: SD + age: + 2026: 65 + immigration_status: LEGAL_PERMANENT_RESIDENT + ssi_qualifying_quarters_earnings: 0 + sd_ssp_care_certified: true + sd_ssp_approved_care_cost: 2_200 + output: + # A permanent resident with 0 of the 40 required qualifying quarters is + # not an SSI qualified noncitizen, so SSI is also 0. + ssi: 0 + # Fails the citizenship gate; otherwise + # min(2_200, 31 x 77.16 = 2_391.96) - 0 income = 2_200. + sd_ssp_care: 0 + sd_ssp: 0 + +- name: Case 29, incarcerated care resident. + period: 2026-01 + absolute_error_margin: 0.01 + input: + state_code: + 2026: SD + age: + 2026: 65 + is_incarcerated: true + sd_ssp_care_certified: true + sd_ssp_approved_care_cost: 2_200 + output: + # Fails the incarceration gate; otherwise, as in Case 9, + # min(2_200, 31 x 77.16 = 2_391.96) - SSI 994 = 1_206. + sd_ssp_care: 0 + sd_ssp: 0 + +- name: Case 30, part-time college student is excluded from the supplement. + period: 2026-01 + absolute_error_margin: 0.01 + input: + state_code: + 2026: SD + age: + 2026: 24 + meets_ssi_disability_criteria: true + is_part_time_college_student: true + output: + # Disabled with no other income: SSI = 994. + ssi: 994 + # Part-time college enrollment is a student under the exclusion. + sd_ssp: 0 + +- name: Case 31, same disabled adult who is not a student receives the supplement. + period: 2026-01 + absolute_error_margin: 0.01 + input: + state_code: + 2026: SD + age: + 2026: 24 + meets_ssi_disability_criteria: true + output: + # Disabled with no other income: SSI = 994. + ssi: 994 + # Not a student and no other cash income: individual amount 15. + sd_ssp: 15 diff --git a/policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp_care.py b/policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp_care.py index 5e5080c37f5..1aba72361b7 100644 --- a/policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp_care.py +++ b/policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp_care.py @@ -33,6 +33,9 @@ def formula(person, period, parameters): cost = where(foster, cost, min_(cost, days * p.assisted_living_daily_rate)) # Care uses gross income, including SSI, before state-approved # deductions. Federal SSI's $20/$65 exclusions are not applied. + # NOTE: this counts SSI actually received. Certification assumes an + # SSI-eligible resident has applied for SSI, because ARSD + # 67:46:04:13(2) requires the department to refer them to apply. income = ( max_(person("ssi_earned_income", period), 0) + max_(person("ssi_unearned_income", period), 0) diff --git a/policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp_independent.py b/policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp_independent.py index 5695baaa372..9ec727d3548 100644 --- a/policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp_independent.py +++ b/policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp_independent.py @@ -37,6 +37,9 @@ def formula(person, period, parameters): # NOTE: ARSD 67:12:14 and POMS DEN01415.011 B.5 are silent on a # married recipient whose spouse does not qualify; that recipient is # treated as an individual, with no spousal income pooling. + # NOTE: the couple split requires ssi_claim_is_joint, so a couple + # whose SSI receipt is given only by receives_ssi needs both spouses' + # aged, blind or disabled inputs to be paid as a couple. couple = person("ssi_claim_is_joint", period.this_year) & ( person.marital_unit.sum(qualifies) > 1 ) From 66976a31672afff49c8ad3d3a6ce8447747cc19c Mon Sep 17 00:00:00 2001 From: Ziming Date: Thu, 15 Oct 2026 18:02:16 -0400 Subject: [PATCH 5/6] Address review: South Dakota SSP state gate, joint entry income and inputs - Gate sd_ssp on StateCode.SD (aggregate list guard). - Share a joint-entry couple's income equally in the entry month (ARSD 67:46:04:10(6)), and require both spouses to be flagged. - Replace the care days and income-not-applied formula inputs with zero-default partial-month days and died/transferred inputs, and apply the 67:46:04:15 income test in partial months. - Add a care setting input so waiver and private-pay assisted living and adult foster care residents get no independent payment; require SSI pursuit for residents who would qualify. - Uprate the assisted living daily rate by the SSI COLA, update descriptions and references, and add tests. Co-Authored-By: Claude Opus 5.5 --- .../dss/ssp/assisted_living_daily_rate.yaml | 10 +- .../sd/dss/ssp/couple_first_amount.yaml | 8 +- .../sd/dss/ssp/couple_second_amount.yaml | 8 +- .../states/sd/dss/ssp/individual_amount.yaml | 8 +- .../dss/ssp/joint_entry_resource_limit.yaml | 2 +- .../gov/states/sd/dss/ssp/minimum_age.yaml | 2 +- .../gov/states/sd/dss/ssp/resource_limit.yaml | 2 +- policyengine_us/programs.yaml | 2 +- .../gov/states/sd/dss/ssp/integration.yaml | 174 +++++++++++++++++- .../gov/states/sd/dss/ssp/sd_ssp.yaml | 108 +++++++++-- .../variables/gov/states/sd/dss/ssp/sd_ssp.py | 8 +- .../gov/states/sd/dss/ssp/sd_ssp_care.py | 63 +++++-- .../sd/dss/ssp/sd_ssp_care_certified.py | 2 +- .../gov/states/sd/dss/ssp/sd_ssp_care_days.py | 12 -- .../ssp/sd_ssp_care_died_or_transferred.py | 11 ++ .../dss/ssp/sd_ssp_care_income_not_applied.py | 13 -- .../sd/dss/ssp/sd_ssp_care_joint_entry.py | 8 +- .../dss/ssp/sd_ssp_care_partial_month_days.py | 11 ++ .../states/sd/dss/ssp/sd_ssp_care_setting.py | 22 +++ .../states/sd/dss/ssp/sd_ssp_independent.py | 14 +- .../sd/dss/ssp/sd_ssp_is_adult_foster_care.py | 9 - 21 files changed, 414 insertions(+), 83 deletions(-) delete mode 100644 policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp_care_days.py create mode 100644 policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp_care_died_or_transferred.py delete mode 100644 policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp_care_income_not_applied.py create mode 100644 policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp_care_partial_month_days.py create mode 100644 policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp_care_setting.py delete mode 100644 policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp_is_adult_foster_care.py diff --git a/policyengine_us/parameters/gov/states/sd/dss/ssp/assisted_living_daily_rate.yaml b/policyengine_us/parameters/gov/states/sd/dss/ssp/assisted_living_daily_rate.yaml index 8c36e857ea3..2f0458f10d9 100644 --- a/policyengine_us/parameters/gov/states/sd/dss/ssp/assisted_living_daily_rate.yaml +++ b/policyengine_us/parameters/gov/states/sd/dss/ssp/assisted_living_daily_rate.yaml @@ -1,4 +1,4 @@ -description: South Dakota caps its state optional assisted living reimbursement at this daily amount, including room and board. +description: South Dakota limits the assisted living payment to this daily rate, including room and board, under the State Optional Supplemental Assisted Living program. values: 2026-01-01: 77.16 @@ -6,6 +6,14 @@ metadata: unit: currency-USD period: day label: South Dakota SSP assisted living daily rate + # The LTSS fee schedule (note 2) adjusts this rate each January by the + # Social Security cost-of-living adjustment; the published rate replaces the + # projection. + uprating: + parameter: gov.ssa.uprating + rounding: + type: nearest + interval: 0.01 reference: - title: LTSS fee schedule effective January 2026, State Optional Supplemental Assisted Living (08054), note 2 href: https://dhs.sd.gov/content/dam/digital/united-states/south-dakota/sdo/pdf/long-term-services-and-supports/provider-portal/fee-schedules/LTSS-Fee-Schedule-and-In-Home-Services-Fee-Schedule-Effective-1-1-26.pdf diff --git a/policyengine_us/parameters/gov/states/sd/dss/ssp/couple_first_amount.yaml b/policyengine_us/parameters/gov/states/sd/dss/ssp/couple_first_amount.yaml index dbba81adc59..7897cf34789 100644 --- a/policyengine_us/parameters/gov/states/sd/dss/ssp/couple_first_amount.yaml +++ b/policyengine_us/parameters/gov/states/sd/dss/ssp/couple_first_amount.yaml @@ -1,4 +1,4 @@ -description: South Dakota pays this amount as its first member independent supplement. +description: South Dakota provides this amount as the monthly payment to the first member of an eligible couple under the Optional State Supplemental program. values: 1978-07-01: 8 @@ -7,5 +7,9 @@ metadata: period: month label: South Dakota SSP first member independent supplement reference: - - title: SSA POMS SI DEN01415.011 B.5 + - title: SSA POMS SI DEN01415.011 B.5.b href: https://secure.ssa.gov/poms.nsf/lnx/0501415011DEN + - title: ARSD 67:12:14:04 + href: https://sdlegislature.gov/Rules/Administrative/67:12:14:04 + - title: SSA State Assistance Programs for SSI Recipients, January 2011 - South Dakota + href: https://www.ssa.gov/policy/docs/progdesc/ssi_st_asst/2011/sd.html diff --git a/policyengine_us/parameters/gov/states/sd/dss/ssp/couple_second_amount.yaml b/policyengine_us/parameters/gov/states/sd/dss/ssp/couple_second_amount.yaml index 8185c5c8223..104c24bf337 100644 --- a/policyengine_us/parameters/gov/states/sd/dss/ssp/couple_second_amount.yaml +++ b/policyengine_us/parameters/gov/states/sd/dss/ssp/couple_second_amount.yaml @@ -1,4 +1,4 @@ -description: South Dakota pays this amount as its second member independent supplement. +description: South Dakota provides this amount as the monthly payment to the second member of an eligible couple under the Optional State Supplemental program. values: 1978-07-01: 7 @@ -7,5 +7,9 @@ metadata: period: month label: South Dakota SSP second member independent supplement reference: - - title: SSA POMS SI DEN01415.011 B.5 + - title: SSA POMS SI DEN01415.011 B.5.b href: https://secure.ssa.gov/poms.nsf/lnx/0501415011DEN + - title: ARSD 67:12:14:04 + href: https://sdlegislature.gov/Rules/Administrative/67:12:14:04 + - title: SSA State Assistance Programs for SSI Recipients, January 2011 - South Dakota + href: https://www.ssa.gov/policy/docs/progdesc/ssi_st_asst/2011/sd.html diff --git a/policyengine_us/parameters/gov/states/sd/dss/ssp/individual_amount.yaml b/policyengine_us/parameters/gov/states/sd/dss/ssp/individual_amount.yaml index 086ca2d98cf..38add708379 100644 --- a/policyengine_us/parameters/gov/states/sd/dss/ssp/individual_amount.yaml +++ b/policyengine_us/parameters/gov/states/sd/dss/ssp/individual_amount.yaml @@ -1,4 +1,4 @@ -description: South Dakota pays this amount as its individual independent supplement. +description: South Dakota provides this amount as the monthly payment to an individual in an independent living arrangement under the Optional State Supplemental program. values: 1978-07-01: 15 @@ -7,5 +7,9 @@ metadata: period: month label: South Dakota SSP individual independent supplement reference: - - title: SSA POMS SI DEN01415.011 B.5 + - title: SSA POMS SI DEN01415.011 B.5.b href: https://secure.ssa.gov/poms.nsf/lnx/0501415011DEN + - title: ARSD 67:12:14:04 + href: https://sdlegislature.gov/Rules/Administrative/67:12:14:04 + - title: SSA State Assistance Programs for SSI Recipients, January 2011 - South Dakota + href: https://www.ssa.gov/policy/docs/progdesc/ssi_st_asst/2011/sd.html diff --git a/policyengine_us/parameters/gov/states/sd/dss/ssp/joint_entry_resource_limit.yaml b/policyengine_us/parameters/gov/states/sd/dss/ssp/joint_entry_resource_limit.yaml index 19c96e508d0..7134f7a0310 100644 --- a/policyengine_us/parameters/gov/states/sd/dss/ssp/joint_entry_resource_limit.yaml +++ b/policyengine_us/parameters/gov/states/sd/dss/ssp/joint_entry_resource_limit.yaml @@ -1,4 +1,4 @@ -description: South Dakota applies this joint entry resource limit to long-term care assistance. +description: South Dakota limits the resources of an individual whose spouse begins long-term care services in the same month to this amount under the long-term care assistance program. values: 2026-01-01: 3_000 diff --git a/policyengine_us/parameters/gov/states/sd/dss/ssp/minimum_age.yaml b/policyengine_us/parameters/gov/states/sd/dss/ssp/minimum_age.yaml index 3b1f99ead3a..44f95f72cec 100644 --- a/policyengine_us/parameters/gov/states/sd/dss/ssp/minimum_age.yaml +++ b/policyengine_us/parameters/gov/states/sd/dss/ssp/minimum_age.yaml @@ -1,4 +1,4 @@ -description: South Dakota provides its independent state supplement to adults at or above this age. +description: South Dakota sets this age as the minimum for the independent living payment under the Optional State Supplemental program. values: 1978-08-06: 18 diff --git a/policyengine_us/parameters/gov/states/sd/dss/ssp/resource_limit.yaml b/policyengine_us/parameters/gov/states/sd/dss/ssp/resource_limit.yaml index 3b3aa270307..77de73572ed 100644 --- a/policyengine_us/parameters/gov/states/sd/dss/ssp/resource_limit.yaml +++ b/policyengine_us/parameters/gov/states/sd/dss/ssp/resource_limit.yaml @@ -1,4 +1,4 @@ -description: South Dakota applies this individual resource limit to long-term care assistance. +description: South Dakota limits an individual's resources to this amount under the long-term care assistance program. values: 2026-01-01: 2_000 diff --git a/policyengine_us/programs.yaml b/policyengine_us/programs.yaml index 9d74340d7e5..7b3c9f82914 100644 --- a/policyengine_us/programs.yaml +++ b/policyengine_us/programs.yaml @@ -1096,7 +1096,7 @@ programs: full_name: South Dakota State Supplemental Payment variable: sd_ssp parameter_prefix: gov.states.sd.dss.ssp - verified_years: '2026' + verified_years: "2026" notes: Models 2026 independent living and certified nonwaiver assisted living/adult foster care supplements. Out-of-state placements, historical mandatory supplements and claim administration are not modeled. diff --git a/policyengine_us/tests/policy/baseline/gov/states/sd/dss/ssp/integration.yaml b/policyengine_us/tests/policy/baseline/gov/states/sd/dss/ssp/integration.yaml index 18d74a21a8a..fe2e739ede4 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/sd/dss/ssp/integration.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/sd/dss/ssp/integration.yaml @@ -25,6 +25,7 @@ state_code: SD output: sd_ssp: 180 + household_state_benefits: 180 snap_unearned_income: 12_108 hud_unearned_income: 12_108 @@ -148,7 +149,7 @@ sd_ssp_care: [1_000, 1_400] sd_ssp: [1_000, 1_400] -- name: Case 7, joint entry within the pooled limit qualifies both spouses. +- name: Case 7, joint entry within the pooled limit shares the couple's income equally. period: 2026-01 absolute_error_margin: 0.01 input: @@ -181,11 +182,11 @@ # > 1_491 / 2 = 745.50, so SSI is 0. ssi: [0, 0] sd_ssp_independent: [0, 0] - # Pooled 2_100 + 800 = 2_900 <= 3_000 joint entry limit; - # min(2_200, 31 x 77.16 = 2_391.96) less own income: - # 2_200 - 14_400 / 12 = 1_000; 2_200 - 9_600 / 12 = 1_400. - sd_ssp_care: [1_000, 1_400] - sd_ssp: [1_000, 1_400] + # Pooled 2_100 + 800 = 2_900 <= 3_000 joint entry limit. ARSD + # 67:46:04:10(6) shares the income equally in the month of entry: + # (1_200 + 800) / 2 = 1_000 each; min(2_200, 2_391.96) - 1_000 = 1_200. + sd_ssp_care: [1_200, 1_200] + sd_ssp: [1_200, 1_200] - name: Case 8, same couple without joint entry faces the individual limit. period: 2026-01 @@ -222,3 +223,164 @@ # person2 800 <= 2_000: min(2_200, 2_391.96) - 9_600 / 12 = 1_400. sd_ssp_care: [0, 1_400] sd_ssp: [0, 1_400] + +- name: Case 9, joint entry sharing keeps a spouse with high income eligible. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 70 + social_security_retirement: 30_000 + ssi_countable_resources: 1_000 + sd_ssp_care_certified: true + sd_ssp_approved_care_cost: 2_200 + sd_ssp_care_joint_entry: true + person2: + age: 68 + social_security_retirement: 6_000 + ssi_countable_resources: 1_000 + sd_ssp_care_certified: true + sd_ssp_approved_care_cost: 2_200 + sd_ssp_care_joint_entry: true + marital_units: + couple: + members: [person1, person2] + households: + hh: + members: [person1, person2] + state_code: SD + output: + ssi: [0, 0] + # (2_500 + 500) / 2 = 1_500 each; 2_200 - 1_500 = 700. Person1's own + # 2_500 would exceed the care rate. + sd_ssp_care: [700, 700] + +- name: Case 10, partial-month days for one resident leave another resident's full month. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 65 + sd_ssp_care_certified: true + sd_ssp_approved_care_cost: 2_200 + sd_ssp_care_partial_month_days: 10 + person2: + age: 65 + sd_ssp_care_certified: true + sd_ssp_approved_care_cost: 2_200 + marital_units: + unit1: + members: [person1] + unit2: + members: [person2] + tax_units: + unit1: + members: [person1] + unit2: + members: [person2] + households: + hh: + members: [person1, person2] + state_code: SD + output: + # person1: min(2_200, 10 x 77.16) = 771.60 with no income applied. + # person2: 2_200 - SSI 994 = 1_206. + sd_ssp_care: [771.6, 1_206] + +- name: Case 11, death or transfer for one partial-month resident leaves another's. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 65 + sd_ssp_care_certified: true + sd_ssp_approved_care_cost: 2_200 + sd_ssp_care_partial_month_days: 20 + sd_ssp_care_died_or_transferred: true + person2: + age: 65 + sd_ssp_care_certified: true + sd_ssp_approved_care_cost: 2_200 + sd_ssp_care_partial_month_days: 10 + marital_units: + unit1: + members: [person1] + unit2: + members: [person2] + tax_units: + unit1: + members: [person1] + unit2: + members: [person2] + households: + hh: + members: [person1, person2] + state_code: SD + output: + # person1: min(2_200, 20 x 77.16 = 1_543.20) - SSI 994 = 549.20. + # person2: income not applied, so 771.60. + sd_ssp_care: [549.2, 771.6] + +- name: Case 12, joint entry shares income before each spouse's own deductions. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 70 + social_security_retirement: 30_000 + ssi_countable_resources: 1_000 + sd_ssp_care_certified: true + sd_ssp_approved_care_cost: 2_200 + sd_ssp_care_joint_entry: true + sd_ssp_care_income_deductions: 100 + person2: + age: 68 + social_security_retirement: 6_000 + ssi_countable_resources: 1_000 + sd_ssp_care_certified: true + sd_ssp_approved_care_cost: 2_200 + sd_ssp_care_joint_entry: true + marital_units: + couple: + members: [person1, person2] + households: + hh: + members: [person1, person2] + state_code: SD + output: + # (2_500 + 500) / 2 = 1_500 each; person1 2_200 - (1_500 - 100) = 800; + # person2 2_200 - 1_500 = 700. + sd_ssp_care: [800, 700] + +- name: Case 13, joint entry flagged for only one spouse budgets each spouse alone. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 70 + social_security_retirement: 30_000 + ssi_countable_resources: 1_000 + sd_ssp_care_certified: true + sd_ssp_approved_care_cost: 2_200 + sd_ssp_care_joint_entry: true + person2: + age: 68 + social_security_retirement: 6_000 + ssi_countable_resources: 1_000 + sd_ssp_care_certified: true + sd_ssp_approved_care_cost: 2_200 + marital_units: + couple: + members: [person1, person2] + households: + hh: + members: [person1, person2] + state_code: SD + output: + # Own income: person1 2_500 > 2_200, so 0; person2 2_200 - 500 = 1_700. + sd_ssp_care: [0, 1_700] diff --git a/policyengine_us/tests/policy/baseline/gov/states/sd/dss/ssp/sd_ssp.yaml b/policyengine_us/tests/policy/baseline/gov/states/sd/dss/ssp/sd_ssp.yaml index ab4f97ed76d..4942ab186c5 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/sd/dss/ssp/sd_ssp.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/sd/dss/ssp/sd_ssp.yaml @@ -197,9 +197,9 @@ 2026: 65 sd_ssp_care_certified: true sd_ssp_approved_care_cost: 1_500 - sd_ssp_care_days: 10 + sd_ssp_care_partial_month_days: 10 output: - # 10 < 31 days, so income is not applied by default; + # 10 < 31 days, so income is not applied; # min(1_500, 10 x 77.16 = 771.60) - 0 = 771.60. sd_ssp: 771.6 @@ -212,7 +212,7 @@ age: 2026: 65 sd_ssp_care_certified: true - sd_ssp_is_adult_foster_care: true + sd_ssp_care_setting: ADULT_FOSTER_CARE sd_ssp_approved_care_cost: 1_500 output: sd_ssp: 506 @@ -282,13 +282,13 @@ 2026: 65 sd_ssp_care_certified: true sd_ssp_approved_care_cost: 1_800 - sd_ssp_care_days: 20 - sd_ssp_care_income_not_applied: false + sd_ssp_care_partial_month_days: 20 + sd_ssp_care_died_or_transferred: true output: # min(1_800, 20 x 77.16 = 1_543.20) - SSI 994 = 549.20. sd_ssp: 549.2 -- name: Case 23, care payment does not require SSI takeup. +- name: Case 23, resident over income for SSI need not take it up. period: 2026-01 absolute_error_margin: 0.01 input: @@ -321,7 +321,7 @@ # Reported receipt meets the SSI gate; no other cash income. sd_ssp: 15 -- name: Case 25, care days above the month length are capped at the month. +- name: Case 25, partial-month days of at least the month length are a full month. period: 2026-02 absolute_error_margin: 0.01 input: @@ -331,13 +331,13 @@ 2026: 65 sd_ssp_care_certified: true sd_ssp_approved_care_cost: 3_000 - sd_ssp_care_days: 31 + sd_ssp_care_partial_month_days: 31 output: - # Days clip to 28; 31 < 28 is false, so income applies; + # 31 is not below 28, so February is a full month and income applies; # min(3_000, 28 x 77.16 = 2_160.48) - SSI 994 = 1_166.48. sd_ssp: 1_166.48 -- name: Case 26, no covered care days pays nothing. +- name: Case 26, adult foster care partial month is uncapped and applies no income. period: 2026-01 absolute_error_margin: 0.01 input: @@ -346,13 +346,13 @@ age: 2026: 65 sd_ssp_care_certified: true - sd_ssp_is_adult_foster_care: true + sd_ssp_care_setting: ADULT_FOSTER_CARE sd_ssp_approved_care_cost: 1_500 - sd_ssp_care_days: 0 + sd_ssp_care_partial_month_days: 10 output: - # 0 days fails days > 0, although the uncapped foster care bill - # is 1_500 and 0 < 31 means no income contribution. - sd_ssp: 0 + # Adult foster care has no daily cap, and 10 < 31 days means no income + # contribution: 1_500 - 0. + sd_ssp: 1_500 - name: Case 27, earned income and SSI both count toward the care contribution. period: 2026-01 @@ -441,3 +441,81 @@ ssi: 994 # Not a student and no other cash income: individual amount 15. sd_ssp: 15 + +- name: Case 32, resident whose income would qualify for SSI but who does not take it up. + period: 2026-01 + absolute_error_margin: 0.01 + input: + state_code: + 2026: SD + age: + 2026: 65 + takes_up_ssi_if_eligible: false + sd_ssp_care_certified: true + sd_ssp_approved_care_cost: 2_200 + output: + # ARSD 67:46:04:13(2) and 67:46:01:14: a resident who would qualify for + # SSI must apply for it. + ssi: 0 + sd_ssp: 0 + +- name: Case 33, waiver or private-pay assisted living resident gets no independent supplement. + period: 2026-01 + absolute_error_margin: 0.01 + input: + state_code: + 2026: SD + age: + 2026: 65 + sd_ssp_care_setting: ASSISTED_LIVING + output: + # ARSD 67:12:14:02(4): assisted living residents are not eligible. + ssi: 994 + sd_ssp: 0 + +- name: Case 34, private-pay adult foster care resident gets no independent supplement. + period: 2026-01 + absolute_error_margin: 0.01 + input: + state_code: + 2026: SD + age: + 2026: 65 + sd_ssp_care_setting: ADULT_FOSTER_CARE + output: + ssi: 994 + sd_ssp: 0 + +- name: Case 35, partial-month resident whose income exceeds the monthly care rate is ineligible. + period: 2026-01 + absolute_error_margin: 0.01 + input: + state_code: + 2026: SD + age: + 2026: 65 + # $2,500 a month. + social_security_retirement: 30_000 + sd_ssp_care_certified: true + sd_ssp_approved_care_cost: 750 + sd_ssp_care_partial_month_days: 10 + output: + # ARSD 67:46:04:15: net income 2_500 > the monthly rate 750 x 31 / 10 = + # 2_325, so 67:46:06:09 never applies. + ssi: 0 + sd_ssp: 0 + +- name: Case 36, private-pay medical facility resident gets no independent supplement. + period: 2026-01 + absolute_error_margin: 0.01 + input: + state_code: + 2026: SD + age: + 2026: 65 + ssi_lives_in_medical_treatment_facility: true + ssi_medicaid_pays_majority_of_care: false + output: + # ARSD 67:12:14:02(4): institutionalized residents are not eligible. + ssi: 994 + sd_ssp: 0 diff --git a/policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp.py b/policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp.py index 93065fc758d..d739c26e12a 100644 --- a/policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp.py +++ b/policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp.py @@ -7,5 +7,11 @@ class sd_ssp(Variable): label = "South Dakota State Supplemental Payment" definition_period = MONTH unit = USD - reference = "https://secure.ssa.gov/poms.nsf/lnx/0501415011DEN" + defined_for = StateCode.SD + reference = ( + "https://sdlegislature.gov/Statutes/28-5A-1", + "https://secure.ssa.gov/poms.nsf/lnx/0501415011DEN", + "https://sdlegislature.gov/Rules/Administrative/67:12:14:04", + "https://www.ssa.gov/policy/docs/progdesc/ssi_st_asst/2011/sd.html", + ) adds = ["sd_ssp_independent", "sd_ssp_care"] diff --git a/policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp_care.py b/policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp_care.py index 1aba72361b7..ca8c5d309fb 100644 --- a/policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp_care.py +++ b/policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp_care.py @@ -7,42 +7,81 @@ class sd_ssp_care(Variable): label = "South Dakota optional supplemental care payment" definition_period = MONTH unit = USD - reference = "https://sdlegislature.gov/Rules/Administrative/67:46:06" + reference = ( + "https://sdlegislature.gov/Rules/Administrative/67:46:04:10", + "https://sdlegislature.gov/Rules/Administrative/67:46:04:13", + "https://sdlegislature.gov/Rules/Administrative/67:46:04:15", + "https://sdlegislature.gov/Rules/Administrative/67:46:05:30", + "https://sdlegislature.gov/Rules/Administrative/67:46:06:03", + "https://sdlegislature.gov/Rules/Administrative/67:46:06:09", + "https://sdlegislature.gov/Rules/Administrative/67:46:06:10", + "https://sdlegislature.gov/Rules/Administrative/67:46:01:14", + ) defined_for = StateCode.SD def formula(person, period, parameters): p = parameters(period).gov.states.sd.dss.ssp resources = person("ssi_countable_resources", period.this_year) - joint_entry = person("sd_ssp_care_joint_entry", period) + # Joint entry applies only when both spouses are flagged. + joint_entry = ( + person.marital_unit.sum(person("sd_ssp_care_joint_entry", period)) > 1 + ) resources = where(joint_entry, person.marital_unit.sum(resources), resources) limit = where(joint_entry, p.joint_entry_resource_limit, p.resource_limit) immigration = person("immigration_status", period.this_year) citizenship = (immigration == immigration.possible_values.CITIZEN) | person( "is_ssi_qualified_noncitizen", period.this_year ) + # ARSD 67:46:04:13(2) refers a resident whose income would qualify for + # SSI to apply, and 67:46:01:14 disqualifies one who fails to take + # advantage of it; a resident over income for SSI need not apply. + pursues_ssi = person("takes_up_ssi_if_eligible", period.this_year) | ( + person("uncapped_ssi", period) <= 0 + ) eligible = ( person("sd_ssp_care_certified", period) & person("is_ssi_aged_blind_disabled", period.this_year) & citizenship & (resources <= limit) & ~person("is_incarcerated", period) + & pursues_ssi ) - days = clip(person("sd_ssp_care_days", period), 0, period.days) + partial_days = person("sd_ssp_care_partial_month_days", period) + partial_month = (partial_days > 0) & (partial_days < period.days) + days = where(partial_month, partial_days, period.days) cost = max_(person("sd_ssp_approved_care_cost", period), 0) - foster = person("sd_ssp_is_adult_foster_care", period) + setting = person("sd_ssp_care_setting", period) + foster = setting == setting.possible_values.ADULT_FOSTER_CARE cost = where(foster, cost, min_(cost, days * p.assisted_living_daily_rate)) + # ARSD 67:46:04:15 makes a resident whose net income exceeds the monthly + # care rate ineligible. 67:46:06:01 applies this chapter 67:46:04 test + # before the 67:46:06:09 partial-month rule, so the covered-days charge + # is scaled to a full month. + monthly_rate = cost * period.days / days # Care uses gross income, including SSI, before state-approved # deductions. Federal SSI's $20/$65 exclusions are not applied. - # NOTE: this counts SSI actually received. Certification assumes an - # SSI-eligible resident has applied for SSI, because ARSD - # 67:46:04:13(2) requires the department to refer them to apply. - income = ( + own_income = ( max_(person("ssi_earned_income", period), 0) + max_(person("ssi_unearned_income", period), 0) + person("ssi", period) ) - contribution = max_(income - person("sd_ssp_care_income_deductions", period), 0) - contribution = where( - person("sd_ssp_care_income_not_applied", period), 0, contribution + # ARSD 67:46:04:10(6), applied to the payment by 67:46:06:03: when both + # spouses enter care in the same month, their combined income is shared + # equally in that month. The share is taken before each spouse's own + # deductions (67:46:06:05 to 06:07), following the order of the rules. + # NOTE: the month only one spouse of a couple living together enters + # assisted living or adult foster care is not modeled. 67:46:04:10(7) + # and 67:46:05:42(7) count the couple's combined income and resources + # in that month; this formula uses the entering spouse's own. + shared_income = person.marital_unit.sum(own_income) / ( + person.marital_unit.nb_persons() + ) + income = where(joint_entry, shared_income, own_income) + net_income = max_(income - person("sd_ssp_care_income_deductions", period), 0) + # 67:46:06:09: income is not applied in a partial month unless the + # recipient died or transferred to another long-term care facility. + income_not_applied = partial_month & ~person( + "sd_ssp_care_died_or_transferred", period ) - return eligible * (days > 0) * max_(cost - contribution, 0) + contribution = where(income_not_applied, 0, net_income) + return eligible * (net_income <= monthly_rate) * max_(cost - contribution, 0) diff --git a/policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp_care_certified.py b/policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp_care_certified.py index 7555174e920..ad31ef40883 100644 --- a/policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp_care_certified.py +++ b/policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp_care_certified.py @@ -7,4 +7,4 @@ class sd_ssp_care_certified(Variable): label = "Certified for South Dakota nonwaiver optional supplemental care" definition_period = MONTH reference = "https://sdlegislature.gov/Rules/Administrative/67:46:04:15" - documentation = "Approved placement, care needs, benefit pursuit and nonfinancial program requirements; excludes Medicaid waiver services." + documentation = "Approved placement, care needs, pursuit of benefits other than SSI and nonfinancial program requirements; excludes Medicaid waiver services. SSI pursuit is tested separately through takes_up_ssi_if_eligible (ARSD 67:46:01:14)." diff --git a/policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp_care_days.py b/policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp_care_days.py deleted file mode 100644 index 7cad2b42fe5..00000000000 --- a/policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp_care_days.py +++ /dev/null @@ -1,12 +0,0 @@ -from policyengine_us.model_api import * - - -class sd_ssp_care_days(Variable): - value_type = int - entity = Person - label = "Covered South Dakota SSP care days" - definition_period = MONTH - reference = "https://sdlegislature.gov/Rules/Administrative/67:46:04:15" - - def formula(person, period, parameters): - return period.days diff --git a/policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp_care_died_or_transferred.py b/policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp_care_died_or_transferred.py new file mode 100644 index 00000000000..6a7dd9222f0 --- /dev/null +++ b/policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp_care_died_or_transferred.py @@ -0,0 +1,11 @@ +from policyengine_us.model_api import * + + +class sd_ssp_care_died_or_transferred(Variable): + value_type = bool + entity = Person + label = "South Dakota SSP care recipient died or transferred to another long-term care facility this month" + definition_period = MONTH + defined_for = StateCode.SD + reference = "https://sdlegislature.gov/Rules/Administrative/67:46:06:09" + documentation = "In a partial month, income is applied toward the cost of care only when the recipient died or transferred to another long-term care facility." diff --git a/policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp_care_income_not_applied.py b/policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp_care_income_not_applied.py deleted file mode 100644 index db75a89022a..00000000000 --- a/policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp_care_income_not_applied.py +++ /dev/null @@ -1,13 +0,0 @@ -from policyengine_us.model_api import * - - -class sd_ssp_care_income_not_applied(Variable): - value_type = bool - entity = Person - label = "South Dakota care month has no income contribution" - definition_period = MONTH - reference = "https://sdlegislature.gov/Rules/Administrative/67:46:06:09" - documentation = "Defaults to true for a partial care month under ARSD 67:46:06:09. Set to false for a month in which the recipient died or transferred to another long-term care facility." - - def formula(person, period, parameters): - return person("sd_ssp_care_days", period) < period.days diff --git a/policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp_care_joint_entry.py b/policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp_care_joint_entry.py index dea99bc7260..d24721340d7 100644 --- a/policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp_care_joint_entry.py +++ b/policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp_care_joint_entry.py @@ -4,6 +4,10 @@ class sd_ssp_care_joint_entry(Variable): value_type = bool entity = Person - label = "South Dakota spouses begin long-term care in the same month" + label = "Both spouses entered South Dakota long-term care this month" definition_period = MONTH - reference = "https://sdlegislature.gov/Rules/Administrative/67:46:05:30" + reference = ( + "https://sdlegislature.gov/Rules/Administrative/67:46:05:30", + "https://sdlegislature.gov/Rules/Administrative/67:46:04:10", + ) + documentation = "True only for the calendar month in which both spouses entered a long-term care facility, such as assisted living or adult foster care. In that month the couple's combined resources are tested against the joint resource limit (ARSD 67:46:05:30), and the couple's combined gross income is shared equally between the spouses before each spouse's deductions (ARSD 67:46:04:10(6), applied through 67:46:06:03). Set it for both spouses, and leave it false for later months, when each spouse is budgeted as a separate individual. A month in which only one spouse enters care is not covered by this flag." diff --git a/policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp_care_partial_month_days.py b/policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp_care_partial_month_days.py new file mode 100644 index 00000000000..7b4ad29bc38 --- /dev/null +++ b/policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp_care_partial_month_days.py @@ -0,0 +1,11 @@ +from policyengine_us.model_api import * + + +class sd_ssp_care_partial_month_days(Variable): + value_type = int + entity = Person + label = "South Dakota SSP care residence days in a partial month" + definition_period = MONTH + defined_for = StateCode.SD + reference = "https://sdlegislature.gov/Rules/Administrative/67:46:06:09" + documentation = "Days of long-term care residence in a month of less than a full month's residence. Leave at 0 for a full month. Enter it for the month, since a yearly value applies to every month." diff --git a/policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp_care_setting.py b/policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp_care_setting.py new file mode 100644 index 00000000000..faa86a42230 --- /dev/null +++ b/policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp_care_setting.py @@ -0,0 +1,22 @@ +from policyengine_us.model_api import * + + +class SDSSPCareSetting(Enum): + ASSISTED_LIVING = "Assisted living facility" + ADULT_FOSTER_CARE = "Licensed adult foster care" + NONE = "Not in assisted living or adult foster care" + + +class sd_ssp_care_setting(Variable): + value_type = Enum + entity = Person + label = "South Dakota SSP assisted living or adult foster care setting" + definition_period = MONTH + defined_for = StateCode.SD + possible_values = SDSSPCareSetting + default_value = SDSSPCareSetting.NONE + reference = ( + "https://sdlegislature.gov/Rules/Administrative/67:12:14:02", + "https://sdlegislature.gov/Rules/Administrative/67:46:03:03", + ) + documentation = "Set for any assisted living or adult foster care resident, including residents on a Medicaid waiver or paying privately. ARSD 67:12:14:02(4) makes these residents ineligible for the independent living payment. Certified nonwaiver residents without a setting are treated as in assisted living." diff --git a/policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp_independent.py b/policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp_independent.py index 9ec727d3548..804b695e93b 100644 --- a/policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp_independent.py +++ b/policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp_independent.py @@ -27,12 +27,20 @@ def formula(person, period, parameters): student = person("is_full_time_student", period.this_year) | person( "is_part_time_college_student", period.this_year ) + setting = person("sd_ssp_care_setting", period) + in_care = ( + (setting != setting.possible_values.NONE) + | person("sd_ssp_care_certified", period) + | person("ssi_lives_in_medical_treatment_facility", period) + ) qualifies = ( receives_ssi & independent & (person("age", period.this_year) >= p.minimum_age) & ~student - & ~person("sd_ssp_care_certified", period) + # ARSD 67:12:14:02(4) and POMS DEN01415.011 B: institutionalized, + # assisted living and adult foster care residents are not eligible. + & ~in_care ) # NOTE: ARSD 67:12:14 and POMS DEN01415.011 B.5 are silent on a # married recipient whose spouse does not qualify; that recipient is @@ -40,6 +48,10 @@ def formula(person, period, parameters): # NOTE: the couple split requires ssi_claim_is_joint, so a couple # whose SSI receipt is given only by receives_ssi needs both spouses' # aged, blind or disabled inputs to be paid as a couple. + # NOTE: an eligible couple's other income is pooled. POMS SI DEN01415.011 + # B.5.b pays "individuals and couples ... who have no other income"; + # ARSD 67:12:14:03(2) reads per person ("The person is receiving other + # income"). couple = person("ssi_claim_is_joint", period.this_year) & ( person.marital_unit.sum(qualifies) > 1 ) diff --git a/policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp_is_adult_foster_care.py b/policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp_is_adult_foster_care.py deleted file mode 100644 index 9b7bb4ecc6c..00000000000 --- a/policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp_is_adult_foster_care.py +++ /dev/null @@ -1,9 +0,0 @@ -from policyengine_us.model_api import * - - -class sd_ssp_is_adult_foster_care(Variable): - value_type = bool - entity = Person - label = "South Dakota SSP care is licensed adult foster care" - definition_period = MONTH - reference = "https://sdlegislature.gov/Rules/Administrative/67:46:04:15" From b416b58e8e7f550006a9386bc445bd916ce77e0e Mon Sep 17 00:00:00 2001 From: Ziming Date: Thu, 15 Oct 2026 18:41:37 -0400 Subject: [PATCH 6/6] Stop South Dakota SSP one-month care inputs from carrying into later months A formula-less monthly input carries forward to later months, so partial days, a death or transfer, or a joint entry entered for one month also applied to every month after it. Formulas that return the default keep these entries to their month. Co-Authored-By: Claude Opus 5.5 --- .../gov/states/sd/dss/ssp/integration.yaml | 44 +++++++++++++++++++ .../gov/states/sd/dss/ssp/sd_ssp.yaml | 24 ++++++++++ .../ssp/sd_ssp_care_died_or_transferred.py | 4 ++ .../sd/dss/ssp/sd_ssp_care_joint_entry.py | 4 ++ .../dss/ssp/sd_ssp_care_partial_month_days.py | 4 ++ 5 files changed, 80 insertions(+) diff --git a/policyengine_us/tests/policy/baseline/gov/states/sd/dss/ssp/integration.yaml b/policyengine_us/tests/policy/baseline/gov/states/sd/dss/ssp/integration.yaml index fe2e739ede4..6c6f05d07e8 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/sd/dss/ssp/integration.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/sd/dss/ssp/integration.yaml @@ -384,3 +384,47 @@ output: # Own income: person1 2_500 > 2_200, so 0; person2 2_200 - 500 = 1_700. sd_ssp_care: [0, 1_700] + +- name: Case 14, joint entry flagged for the entry month budgets the spouses separately the next month. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 65 + social_security_retirement: 14_400 + ssi_countable_resources: 2_100 + sd_ssp_care_certified: + 2026-01: true + 2026-02: true + sd_ssp_approved_care_cost: + 2026-01: 2_200 + 2026-02: 2_200 + sd_ssp_care_joint_entry: + 2026-01: true + person2: + age: 65 + social_security_retirement: 9_600 + ssi_countable_resources: 800 + sd_ssp_care_certified: + 2026-01: true + 2026-02: true + sd_ssp_approved_care_cost: + 2026-01: 2_200 + 2026-02: 2_200 + sd_ssp_care_joint_entry: + 2026-01: true + marital_units: + couple: + members: [person1, person2] + households: + hh: + members: [person1, person2] + state_code: SD + output: + # January (Case 7): pooled resources and shared income, [1_200, 1_200]. + # February: separate individuals; person1's 2_100 exceeds 2_000, and + # person2 gets min(2_200, 28 x 77.16 = 2_160.48) - 800 = 1_360.48. + sd_ssp_care: + 2026-01: [1_200, 1_200] + 2026-02: [0, 1_360.48] diff --git a/policyengine_us/tests/policy/baseline/gov/states/sd/dss/ssp/sd_ssp.yaml b/policyengine_us/tests/policy/baseline/gov/states/sd/dss/ssp/sd_ssp.yaml index 4942ab186c5..9d34f6f7e1d 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/sd/dss/ssp/sd_ssp.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/sd/dss/ssp/sd_ssp.yaml @@ -519,3 +519,27 @@ # ARSD 67:12:14:02(4): institutionalized residents are not eligible. ssi: 994 sd_ssp: 0 + +- name: Case 37, partial-month days entered for one month leave the next month full. + period: 2026 + absolute_error_margin: 0.01 + input: + state_code: SD + age: 65 + sd_ssp_care_certified: + 2026-01: true + 2026-02: true + sd_ssp_approved_care_cost: + 2026-01: 2_200 + 2026-02: 2_200 + sd_ssp_care_partial_month_days: + 2026-01: 10 + output: + sd_ssp_care_partial_month_days: + 2026-01: 10 + 2026-02: 0 + # January: min(2_200, 10 x 77.16) = 771.60 with no income applied. + # February is a full month: min(2_200, 28 x 77.16 = 2_160.48) - 994. + sd_ssp_care: + 2026-01: 771.6 + 2026-02: 1_166.48 diff --git a/policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp_care_died_or_transferred.py b/policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp_care_died_or_transferred.py index 6a7dd9222f0..6af81a1e0c3 100644 --- a/policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp_care_died_or_transferred.py +++ b/policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp_care_died_or_transferred.py @@ -9,3 +9,7 @@ class sd_ssp_care_died_or_transferred(Variable): defined_for = StateCode.SD reference = "https://sdlegislature.gov/Rules/Administrative/67:46:06:09" documentation = "In a partial month, income is applied toward the cost of care only when the recipient died or transferred to another long-term care facility." + + def formula(person, period, parameters): + # A formula stops a one-month input from carrying into later months. + return False diff --git a/policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp_care_joint_entry.py b/policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp_care_joint_entry.py index d24721340d7..52c6d3e1e8e 100644 --- a/policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp_care_joint_entry.py +++ b/policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp_care_joint_entry.py @@ -11,3 +11,7 @@ class sd_ssp_care_joint_entry(Variable): "https://sdlegislature.gov/Rules/Administrative/67:46:04:10", ) documentation = "True only for the calendar month in which both spouses entered a long-term care facility, such as assisted living or adult foster care. In that month the couple's combined resources are tested against the joint resource limit (ARSD 67:46:05:30), and the couple's combined gross income is shared equally between the spouses before each spouse's deductions (ARSD 67:46:04:10(6), applied through 67:46:06:03). Set it for both spouses, and leave it false for later months, when each spouse is budgeted as a separate individual. A month in which only one spouse enters care is not covered by this flag." + + def formula(person, period, parameters): + # A formula stops a one-month input from carrying into later months. + return False diff --git a/policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp_care_partial_month_days.py b/policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp_care_partial_month_days.py index 7b4ad29bc38..ff40e826185 100644 --- a/policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp_care_partial_month_days.py +++ b/policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp_care_partial_month_days.py @@ -9,3 +9,7 @@ class sd_ssp_care_partial_month_days(Variable): defined_for = StateCode.SD reference = "https://sdlegislature.gov/Rules/Administrative/67:46:06:09" documentation = "Days of long-term care residence in a month of less than a full month's residence. Leave at 0 for a full month. Enter it for the month, since a yearly value applies to every month." + + def formula(person, period, parameters): + # A formula stops a one-month input from carrying into later months. + return 0