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..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 @@ -161,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 @@ -263,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 @@ -366,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 @@ -401,6 +409,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 0df11213631..fd7537c5a07 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 - total_unemployment_compensation - child_support_received @@ -29,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 new file mode 100644 index 00000000000..2f0458f10d9 --- /dev/null +++ b/policyengine_us/parameters/gov/states/sd/dss/ssp/assisted_living_daily_rate.yaml @@ -0,0 +1,21 @@ +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 + +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 + - 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..7897cf34789 --- /dev/null +++ b/policyengine_us/parameters/gov/states/sd/dss/ssp/couple_first_amount.yaml @@ -0,0 +1,15 @@ +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 + +metadata: + unit: currency-USD + period: month + label: South Dakota SSP first member independent supplement + reference: + - 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 new file mode 100644 index 00000000000..104c24bf337 --- /dev/null +++ b/policyengine_us/parameters/gov/states/sd/dss/ssp/couple_second_amount.yaml @@ -0,0 +1,15 @@ +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 + +metadata: + unit: currency-USD + period: month + label: South Dakota SSP second member independent supplement + reference: + - 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 new file mode 100644 index 00000000000..38add708379 --- /dev/null +++ b/policyengine_us/parameters/gov/states/sd/dss/ssp/individual_amount.yaml @@ -0,0 +1,15 @@ +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 + +metadata: + unit: currency-USD + period: month + label: South Dakota SSP individual independent supplement + reference: + - 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 new file mode 100644 index 00000000000..7134f7a0310 --- /dev/null +++ b/policyengine_us/parameters/gov/states/sd/dss/ssp/joint_entry_resource_limit.yaml @@ -0,0 +1,11 @@ +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 + +metadata: + unit: currency-USD + period: month + 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..44f95f72cec --- /dev/null +++ b/policyengine_us/parameters/gov/states/sd/dss/ssp/minimum_age.yaml @@ -0,0 +1,11 @@ +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 + +metadata: + unit: year + period: 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..77de73572ed --- /dev/null +++ b/policyengine_us/parameters/gov/states/sd/dss/ssp/resource_limit.yaml @@ -0,0 +1,11 @@ +description: South Dakota limits an individual's resources to this amount under the long-term care assistance program. +values: + 2026-01-01: 2_000 + +metadata: + unit: currency-USD + period: month + 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 e9baee603e8..3497a76142b 100644 --- a/policyengine_us/parameters/gov/usda/snap/income/sources/unearned.yaml +++ b/policyengine_us/parameters/gov/usda/snap/income/sources/unearned.yaml @@ -48,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 @@ -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) + 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 5250ab3aa89..4b5ddfb945e 100644 --- a/policyengine_us/programs.yaml +++ b/policyengine_us/programs.yaml @@ -1093,6 +1093,16 @@ 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: 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 new file mode 100644 index 00000000000..6c6f05d07e8 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/sd/dss/ssp/integration.yaml @@ -0,0 +1,430 @@ +- name: Case 1, eligible SSI couple receives eight and seven dollars. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 65 + person2: + age: 65 + marital_units: + couple: + members: [person1, person2] + households: + hh: + members: [person1, person2] + 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 + household_state_benefits: 180 + 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: + people: + person1: + age: 65 + person2: + age: 65 + social_security_retirement: 6_000 + marital_units: + couple: + members: [person1, person2] + households: + hh: + 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] + +- name: Case 7, joint entry within the pooled limit shares the couple's income equally. + 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. 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 + 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] + +- 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] + +- 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 new file mode 100644 index 00000000000..9d34f6f7e1d --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/sd/dss/ssp/sd_ssp.yaml @@ -0,0 +1,545 @@ +- 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, small Social Security benefit disqualifies despite an SSI payment. + period: 2026-01 + absolute_error_margin: 0.01 + input: + state_code: + 2026: SD + age: + 2026: 65 + 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. + 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: 2_200 + output: + 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 + age: + 2026: 65 + sd_ssp_care_certified: true + sd_ssp_approved_care_cost: 3_000 + output: + 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 + age: + 2026: 65 + sd_ssp_care_certified: true + sd_ssp_approved_care_cost: 3_000 + output: + 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 + age: + 2026: 65 + sd_ssp_care_certified: true + sd_ssp_approved_care_cost: 3_000 + output: + 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 + age: + 2026: 65 + sd_ssp_care_certified: true + sd_ssp_approved_care_cost: 2_200 + sd_ssp_care_income_deductions: 100 + output: + 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 + age: + 2026: 65 + sd_ssp_care_certified: true + 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 + age: + 2026: 65 + sd_ssp_care_certified: true + 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 + age: + 2026: 65 + sd_ssp_care_certified: true + sd_ssp_approved_care_cost: 1_500 + sd_ssp_care_partial_month_days: 10 + output: + # 10 < 31 days, so income is not applied; + # 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. + period: 2026-01 + absolute_error_margin: 0.01 + input: + state_code: + 2026: SD + age: + 2026: 65 + sd_ssp_care_certified: true + sd_ssp_care_setting: ADULT_FOSTER_CARE + 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 + 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: 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 + 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: 3_000 + 2026-07: 3_000 + output: + 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_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, resident over income for SSI need not take it up. + 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, partial-month days of at least the month length are a full 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_partial_month_days: 31 + output: + # 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, adult foster care partial month is uncapped and applies no income. + period: 2026-01 + absolute_error_margin: 0.01 + input: + state_code: + 2026: SD + age: + 2026: 65 + sd_ssp_care_certified: true + sd_ssp_care_setting: ADULT_FOSTER_CARE + sd_ssp_approved_care_cost: 1_500 + sd_ssp_care_partial_month_days: 10 + output: + # 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 + 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 + +- 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 + +- 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 + +- 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.py b/policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp.py new file mode 100644 index 00000000000..d739c26e12a --- /dev/null +++ b/policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp.py @@ -0,0 +1,17 @@ +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 + 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_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..ca8c5d309fb --- /dev/null +++ b/policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp_care.py @@ -0,0 +1,87 @@ +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: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 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 + ) + 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) + 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. + own_income = ( + max_(person("ssi_earned_income", period), 0) + + max_(person("ssi_unearned_income", period), 0) + + person("ssi", period) + ) + # 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 + ) + 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 new file mode 100644 index 00000000000..ad31ef40883 --- /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, 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_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..6af81a1e0c3 --- /dev/null +++ b/policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp_care_died_or_transferred.py @@ -0,0 +1,15 @@ +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." + + 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_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_joint_entry.py b/policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp_care_joint_entry.py new file mode 100644 index 00000000000..52c6d3e1e8e --- /dev/null +++ b/policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp_care_joint_entry.py @@ -0,0 +1,17 @@ +from policyengine_us.model_api import * + + +class sd_ssp_care_joint_entry(Variable): + value_type = bool + entity = Person + label = "Both spouses entered South Dakota long-term care this month" + definition_period = MONTH + 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." + + 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 new file mode 100644 index 00000000000..ff40e826185 --- /dev/null +++ b/policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp_care_partial_month_days.py @@ -0,0 +1,15 @@ +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." + + def formula(person, period, parameters): + # A formula stops a one-month input from carrying into later months. + return 0 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 new file mode 100644 index 00000000000..804b695e93b --- /dev/null +++ b/policyengine_us/variables/gov/states/sd/dss/ssp/sd_ssp_independent.py @@ -0,0 +1,65 @@ +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 + floored_sources = [ + max_(person(name, period), 0) + for name in list(source.earned) + list(source.unearned) + ] + 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) | ( + arrangement == la.ANOTHER_PERSONS_HOUSEHOLD + ) + 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 + # 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 + # 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. + # 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 + ) + other_income = where(couple, person.marital_unit.sum(own_income), own_income) + first = person.marital_unit.reference_entity.members_position == 0 + amount = where( + couple, + where(first, p.couple_first_amount, p.couple_second_amount), + p.individual_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 a473c2b438d..c72f6521517 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", # South Dakota benefits "ks_sspp", # Kansas benefits "hi_oss", "la_oss", # Louisiana benefits