From 35209302a6a74a0de14ce7321a0a237df6be0043 Mon Sep 17 00:00:00 2001 From: Ziming Date: Tue, 5 May 2026 11:38:03 -0400 Subject: [PATCH 1/6] Initialize Montana SSP branch From 12ddbef27964002847abcb2be5ba87547b7d93d3 Mon Sep 17 00:00:00 2001 From: Ziming Date: Tue, 5 May 2026 11:38:20 -0400 Subject: [PATCH 2/6] Add changelog fragment for Montana SSP --- changelog.d/mt-ssp.added.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/mt-ssp.added.md diff --git a/changelog.d/mt-ssp.added.md b/changelog.d/mt-ssp.added.md new file mode 100644 index 00000000000..b548e325ee7 --- /dev/null +++ b/changelog.d/mt-ssp.added.md @@ -0,0 +1 @@ +Implemented Montana State Supplementary Payment (SSP). From a371d6a47e354b66a93b53f87e1b233202e5cd65 Mon Sep 17 00:00:00 2001 From: Ziming Date: Tue, 5 May 2026 12:29:48 -0400 Subject: [PATCH 3/6] Implement Montana State Supplementation (SSP) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes #8232 Implements Montana's Optional State Supplementation under ARM 37.43.101–104 and MCA 52-1-104. Covers aged/blind/disabled SSI recipients in qualifying living arrangements (personal care / group home / community home, foster care, transitional living). Rates frozen since 1989. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../household/household_state_benefits.yaml | 4 + .../states/mt/dphhs/ssp/amount/couple.yaml | 25 ++ .../mt/dphhs/ssp/amount/individual.yaml | 25 ++ policyengine_us/programs.yaml | 6 + .../gov/states/mt/dphhs/ssp/edge_cases.yaml | 179 ++++++++ .../gov/states/mt/dphhs/ssp/integration.yaml | 386 ++++++++++++++++++ .../dphhs/ssp/mt_ssp_couple_per_spouse.yaml | 144 +++++++ .../states/mt/dphhs/ssp/mt_ssp_eligible.yaml | 76 ++++ .../mt/dphhs/ssp/mt_ssp_individual.yaml | 80 ++++ .../gov/states/mt/dphhs/ssp/mt_ssp.py | 34 ++ .../mt/dphhs/ssp/mt_ssp_couple_per_spouse.py | 27 ++ .../states/mt/dphhs/ssp/mt_ssp_eligible.py | 25 ++ .../states/mt/dphhs/ssp/mt_ssp_individual.py | 19 + .../mt/dphhs/ssp/mt_ssp_payment_category.py | 25 ++ .../income/spm_unit/spm_unit_benefits.py | 1 + 15 files changed, 1056 insertions(+) create mode 100644 policyengine_us/parameters/gov/states/mt/dphhs/ssp/amount/couple.yaml create mode 100644 policyengine_us/parameters/gov/states/mt/dphhs/ssp/amount/individual.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/mt/dphhs/ssp/edge_cases.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/mt/dphhs/ssp/integration.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/mt/dphhs/ssp/mt_ssp_couple_per_spouse.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/mt/dphhs/ssp/mt_ssp_eligible.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/mt/dphhs/ssp/mt_ssp_individual.yaml create mode 100644 policyengine_us/variables/gov/states/mt/dphhs/ssp/mt_ssp.py create mode 100644 policyengine_us/variables/gov/states/mt/dphhs/ssp/mt_ssp_couple_per_spouse.py create mode 100644 policyengine_us/variables/gov/states/mt/dphhs/ssp/mt_ssp_eligible.py create mode 100644 policyengine_us/variables/gov/states/mt/dphhs/ssp/mt_ssp_individual.py create mode 100644 policyengine_us/variables/gov/states/mt/dphhs/ssp/mt_ssp_payment_category.py diff --git a/policyengine_us/parameters/gov/household/household_state_benefits.yaml b/policyengine_us/parameters/gov/household/household_state_benefits.yaml index b74241a36b5..32dd5fe0a6a 100644 --- a/policyengine_us/parameters/gov/household/household_state_benefits.yaml +++ b/policyengine_us/parameters/gov/household/household_state_benefits.yaml @@ -16,6 +16,8 @@ values: - ma_state_supplement # Michigan benefits - mi_ssp + # Montana benefits + - mt_ssp # Colorado benefits - co_state_supplement - co_oap @@ -83,6 +85,8 @@ values: - ma_state_supplement # Michigan benefits - mi_ssp + # Montana benefits + - mt_ssp # Colorado benefits - co_state_supplement - co_oap diff --git a/policyengine_us/parameters/gov/states/mt/dphhs/ssp/amount/couple.yaml b/policyengine_us/parameters/gov/states/mt/dphhs/ssp/amount/couple.yaml new file mode 100644 index 00000000000..eda21356125 --- /dev/null +++ b/policyengine_us/parameters/gov/states/mt/dphhs/ssp/amount/couple.yaml @@ -0,0 +1,25 @@ +description: Montana provides this amount as the couple state supplement payment under the State Supplementation program. + +metadata: + unit: currency-USD + period: month + label: Montana SSP couple payment amount + breakdown: + - mt_ssp_payment_category + reference: + - title: SSA POMS SI DEN01415.010, Montana State Supplementary Payments + href: https://secure.ssa.gov/poms.nsf/lnx/0501415010DEN + - title: SSA State Assistance Programs for SSI Recipients (January 2011), Montana, Table 1 + href: https://www.ssa.gov/policy/docs/progdesc/ssi_st_asst/2011/mt.html + +# Couple totals (not per-spouse). ARM 37.43.104 lists individual rates +# only; couple rates are published only in SSA POMS and the SSA 2011 +# report (couple = 2x individual + $5). Frozen since January 1989. +ASSISTED_LIVING_OR_GROUP_OR_COMMUNITY_HOME: + 1989-01-01: 193 +FOSTER_CARE: + 1989-01-01: 110.50 +TRANSITIONAL_LIVING: + 1989-01-01: 57 +NONE: + 1989-01-01: 0 diff --git a/policyengine_us/parameters/gov/states/mt/dphhs/ssp/amount/individual.yaml b/policyengine_us/parameters/gov/states/mt/dphhs/ssp/amount/individual.yaml new file mode 100644 index 00000000000..0ea74104232 --- /dev/null +++ b/policyengine_us/parameters/gov/states/mt/dphhs/ssp/amount/individual.yaml @@ -0,0 +1,25 @@ +description: Montana provides this amount as the individual state supplement payment under the State Supplementation program. + +metadata: + unit: currency-USD + period: month + label: Montana SSP individual payment amount + breakdown: + - mt_ssp_payment_category + reference: + - title: ARM 37.43.104(1), Payment Standards + href: https://www.law.cornell.edu/regulations/montana/ARM-37-43-104 + - title: SSA POMS SI DEN01415.010, Montana State Supplementary Payments + href: https://secure.ssa.gov/poms.nsf/lnx/0501415010DEN + - title: SSA State Assistance Programs for SSI Recipients (January 2011), Montana, Table 1 + href: https://www.ssa.gov/policy/docs/progdesc/ssi_st_asst/2011/mt.html + +# Rates frozen since January 1989 per SSA POMS SI DEN01415.010. +ASSISTED_LIVING_OR_GROUP_OR_COMMUNITY_HOME: + 1989-01-01: 94 +FOSTER_CARE: + 1989-01-01: 52.75 +TRANSITIONAL_LIVING: + 1989-01-01: 26 +NONE: + 1989-01-01: 0 diff --git a/policyengine_us/programs.yaml b/policyengine_us/programs.yaml index 76b5377b5f3..2078ef03fec 100644 --- a/policyengine_us/programs.yaml +++ b/policyengine_us/programs.yaml @@ -707,6 +707,12 @@ programs: full_name: Michigan State Supplementary Payment variable: mi_ssp parameter_prefix: gov.states.mi.mdhhs.ssp + - state: MT + status: complete + name: Montana SSP + full_name: Montana State Supplementation + variable: mt_ssp + parameter_prefix: gov.states.mt.dphhs.ssp - state: ME status: complete name: Maine SSP diff --git a/policyengine_us/tests/policy/baseline/gov/states/mt/dphhs/ssp/edge_cases.yaml b/policyengine_us/tests/policy/baseline/gov/states/mt/dphhs/ssp/edge_cases.yaml new file mode 100644 index 00000000000..b5ae45614b4 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/mt/dphhs/ssp/edge_cases.yaml @@ -0,0 +1,179 @@ +- name: Case 1, uncapped_ssi exactly $0 (boundary, strict greater-than) is ineligible. + absolute_error_margin: 0.01 + period: 2024-01 + input: + people: + person1: + # Boundary: uncapped_ssi == 0 fails the strict (uncapped_ssi > 0) gate. + uncapped_ssi: 0 + mt_ssp_payment_category: ASSISTED_LIVING_OR_GROUP_OR_COMMUNITY_HOME + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MT + output: + mt_ssp_eligible: false + mt_ssp: 0 + +- name: Case 2, uncapped_ssi exactly $0.01 (smallest positive value) is eligible. + absolute_error_margin: 0.01 + period: 2024-01 + input: + people: + person1: + # Annual $0.01 -> monthly ~$0.000833 in the MONTH formula; still > 0 + # so eligibility passes. Spillover deduction = max_(0, -uncapped_ssi) + # = 0 since uncapped_ssi is positive, so full individual rate applies. + uncapped_ssi: 0.01 + mt_ssp_payment_category: TRANSITIONAL_LIVING + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MT + output: + # K tier individual = $26 - $0 spillover = $26. + mt_ssp_eligible: true + mt_ssp: 26 + +- name: Case 3, federal SSI payment is $0 but uncapped_ssi positive (income just below FBR) is eligible. + absolute_error_margin: 0.01 + period: 2024-01 + input: + people: + person1: + # Group 2 path: categorically eligible, federal SSI takeup zeros the + # actual payment, uncapped_ssi remains positive (income just below the + # federal SSI cutoff). Distinct from Case 10 of integration.yaml: this + # case explicitly verifies the (ssi == 0, uncapped_ssi > 0) boundary. + is_ssi_eligible: true + ssi: 0 + uncapped_ssi: 12 + mt_ssp_payment_category: ASSISTED_LIVING_OR_GROUP_OR_COMMUNITY_HOME + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MT + output: + # uncapped_ssi annual $12 -> monthly $1; spillover = max_(0, -1) = 0; + # G/H/I tier = $94 - $0 = $94. + mt_ssp_eligible: true + mt_ssp: 94 + +- name: Case 4, single ineligible person (NONE category) returns zero benefit. + absolute_error_margin: 0.01 + period: 2024-01 + input: + people: + person1: + is_ssi_eligible: true + mt_ssp_payment_category: NONE + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MT + output: + # NONE category fails the qualifying-arrangement gate. + mt_ssp_eligible: false + mt_ssp: 0 + +- name: Case 5, three eligible persons in same SPM unit (parent, adult child, grandparent) sums correctly. + absolute_error_margin: 0.01 + period: 2024-01 + input: + people: + person1: + # Aged grandparent in personal care facility. + age: 80 + is_ssi_eligible: true + is_ssi_aged_blind_disabled: true + mt_ssp_payment_category: ASSISTED_LIVING_OR_GROUP_OR_COMMUNITY_HOME + person2: + # Disabled parent in foster care. + age: 50 + is_ssi_eligible: true + is_ssi_aged_blind_disabled: true + mt_ssp_payment_category: FOSTER_CARE + person3: + # Disabled adult child in transitional living. + age: 28 + is_ssi_eligible: true + is_ssi_aged_blind_disabled: true + mt_ssp_payment_category: TRANSITIONAL_LIVING + marital_units: + marital_unit1: + members: [person1] + marital_unit2: + members: [person2] + marital_unit3: + members: [person3] + spm_units: + spm_unit: + members: [person1, person2, person3] + households: + household: + members: [person1, person2, person3] + state_code: MT + output: + # Three separate marital units -> each gets individual rate. + # person1: G/H/I = $94; person2: J = $52.75; person3: K = $26. + # SPM total = $94 + $52.75 + $26 = $172.75. + mt_ssp_eligible: [true, true, true] + mt_ssp: 172.75 + +- name: Case 6, January period for a person who would move state mid-year still uses MT inputs. + absolute_error_margin: 0.01 + period: 2024-01 + input: + people: + person1: + # Period format restriction: only 2024-01 or 2024 are supported, so we + # cannot model a mid-year move directly. This case documents that for + # the January snapshot, the state_code: MT input controls the result. + is_ssi_eligible: true + mt_ssp_payment_category: FOSTER_CARE + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MT + output: + # January snapshot with MT state code -> J tier individual = $52.75. + mt_ssp_eligible: true + mt_ssp: 52.75 + +- name: Case 7, high earnings make person SSI-ineligible (uncapped_ssi=0) so MT SSP is zero. + absolute_error_margin: 0.01 + period: 2024-01 + input: + people: + person1: + # High employment income -> not SSI-eligible at all -> uncapped_ssi=0 + # (uncapped_ssi has defined_for="is_ssi_eligible"). The boundary case + # demonstrates that when SSI eligibility itself fails, MT SSP correctly + # returns zero rather than inflating from the spillover deduction. + employment_income: 60_000 + mt_ssp_payment_category: ASSISTED_LIVING_OR_GROUP_OR_COMMUNITY_HOME + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MT + output: + mt_ssp_eligible: false + mt_ssp: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/states/mt/dphhs/ssp/integration.yaml b/policyengine_us/tests/policy/baseline/gov/states/mt/dphhs/ssp/integration.yaml new file mode 100644 index 00000000000..5a81d50f129 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/mt/dphhs/ssp/integration.yaml @@ -0,0 +1,386 @@ +- name: Case 1, single eligible person in personal care facility receives $94. + absolute_error_margin: 0.01 + period: 2024-01 + input: + people: + person1: + is_ssi_eligible: true + mt_ssp_payment_category: ASSISTED_LIVING_OR_GROUP_OR_COMMUNITY_HOME + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MT + output: + # Individual rate G/H/I tier = $94 - $0 income offset = $94. + mt_ssp_eligible: true + mt_ssp: 94 + +- name: Case 2, single eligible person in foster care receives $52.75. + absolute_error_margin: 0.01 + period: 2024-01 + input: + people: + person1: + is_ssi_eligible: true + mt_ssp_payment_category: FOSTER_CARE + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MT + output: + # Individual rate J tier = $52.75 - $0 income offset = $52.75. + mt_ssp_eligible: true + mt_ssp: 52.75 + +- name: Case 3, single eligible person in transitional living receives $26. + absolute_error_margin: 0.01 + period: 2024-01 + input: + people: + person1: + is_ssi_eligible: true + mt_ssp_payment_category: TRANSITIONAL_LIVING + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MT + output: + # Individual rate K tier = $26 - $0 income offset = $26. + mt_ssp_eligible: true + mt_ssp: 26 + +- name: Case 4, joint couple both in ASSISTED_LIVING get $193 total ($96.50 each). + absolute_error_margin: 0.01 + period: 2024-01 + input: + people: + person1: + # Couple split federal SSI: $11,316 individual FBR -> use joint + # total $1,415/mo = $16,980/yr -> half is $8,490 each. + ssi: 8_490 + is_ssi_aged_blind_disabled: true + is_tax_unit_head_or_spouse: true + ssi_claim_is_joint: true + mt_ssp_payment_category: ASSISTED_LIVING_OR_GROUP_OR_COMMUNITY_HOME + person2: + ssi: 8_490 + is_ssi_aged_blind_disabled: true + is_tax_unit_head_or_spouse: true + ssi_claim_is_joint: true + mt_ssp_payment_category: ASSISTED_LIVING_OR_GROUP_OR_COMMUNITY_HOME + marital_units: + marital_unit: + members: [person1, person2] + tax_units: + tax_unit: + members: [person1, person2] + spm_units: + spm_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: MT + output: + # Couple gate met -> each spouse $193 / 2 = $96.50; SPM total $193. + mt_ssp_eligible: [true, true] + mt_ssp: 193 + +- name: Case 5, joint couple in different categories fall back to individual rates. + absolute_error_margin: 0.01 + period: 2024-01 + input: + people: + person1: + ssi: 8_490 + is_ssi_aged_blind_disabled: true + is_tax_unit_head_or_spouse: true + ssi_claim_is_joint: true + mt_ssp_payment_category: ASSISTED_LIVING_OR_GROUP_OR_COMMUNITY_HOME + person2: + ssi: 8_490 + is_ssi_aged_blind_disabled: true + is_tax_unit_head_or_spouse: true + ssi_claim_is_joint: true + mt_ssp_payment_category: FOSTER_CARE + marital_units: + marital_unit: + members: [person1, person2] + tax_units: + tax_unit: + members: [person1, person2] + spm_units: + spm_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: MT + output: + # Asymmetric categories -> couple gate fails. + # person1: G/H/I individual = $94; person2: J individual = $52.75. + # SPM total = $94 + $52.75 = $146.75. + mt_ssp_eligible: [true, true] + mt_ssp: 146.75 + +- name: Case 6, joint couple with only one eligible falls back to individual rate. + absolute_error_margin: 0.01 + period: 2024-01 + input: + people: + person1: + is_ssi_eligible: true + is_tax_unit_head_or_spouse: true + ssi_claim_is_joint: true + mt_ssp_payment_category: FOSTER_CARE + person2: + # Spouse not aged/blind/disabled -> not SSI-eligible. + is_ssi_eligible: false + is_tax_unit_head_or_spouse: true + ssi_claim_is_joint: true + mt_ssp_payment_category: FOSTER_CARE + marital_units: + marital_unit: + members: [person1, person2] + tax_units: + tax_unit: + members: [person1, person2] + spm_units: + spm_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: MT + output: + # Only person1 is MT-SSP-eligible -> couple gate fails. + # person1 gets J individual = $52.75; person2 gets $0. + mt_ssp_eligible: [true, false] + mt_ssp: 52.75 + +- name: Case 7, NONE living arrangement gets zero supplement. + absolute_error_margin: 0.01 + period: 2024-01 + input: + people: + person1: + is_ssi_eligible: true + mt_ssp_payment_category: NONE + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MT + output: + # NONE category fails the qualifying-arrangement gate. + mt_ssp_eligible: false + mt_ssp: 0 + +- name: Case 8, non-Montana resident gets zero supplement. + absolute_error_margin: 0.01 + period: 2024-01 + input: + people: + person1: + is_ssi_eligible: true + mt_ssp_payment_category: ASSISTED_LIVING_OR_GROUP_OR_COMMUNITY_HOME + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + # defined_for=StateCode.MT zeros mt_ssp outside Montana. + state_code: WY + output: + mt_ssp: 0 + +- name: Case 9, disabled child SSI recipient in foster care receives $52.75 (no age gate). + absolute_error_margin: 0.01 + period: 2024-01 + input: + people: + person1: + age: 10 + is_disabled: true + # Disabled minor receives federal SSI at 2024 individual FBR + # ($943/mo = $11,316/yr). + ssi: 11_316 + mt_ssp_payment_category: FOSTER_CARE + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MT + output: + # Children covered (309 of 968 recipients per SSA 2011 report); + # individual J tier = $52.75. + mt_ssp_eligible: true + mt_ssp: 52.75 + +- name: Case 10, Group 2 person with income just above FBR (ssi=0, uncapped_ssi>0). + absolute_error_margin: 0.01 + period: 2024-01 + input: + people: + person1: + is_ssi_eligible: true + # Categorically eligible but model takeup zeros the actual ssi + # payment. uncapped_ssi remains positive (Group 2 path). + ssi: 0 + mt_ssp_payment_category: FOSTER_CARE + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MT + output: + # Group 2 (ARM 37.43.102(2)(a)): uncapped_ssi > 0 with ssi == 0 + # still passes mt_ssp_eligible. Individual J = $52.75. + mt_ssp_eligible: true + mt_ssp: 52.75 + +- name: Case 11, eligible recipient at full SSI receives full supplement (no spillover). + absolute_error_margin: 0.01 + period: 2024-01 + input: + people: + person1: + is_ssi_eligible: true + # 2024 individual FBR = $11,316/yr -> uncapped_ssi positive, + # spillover deduction = max_(0, -uncapped_ssi) = 0. + ssi: 11_316 + mt_ssp_payment_category: ASSISTED_LIVING_OR_GROUP_OR_COMMUNITY_HOME + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MT + output: + # Full G/H/I individual rate $94 with no spillover reduction. + mt_ssp_eligible: true + mt_ssp: 94 + +- name: Case 12, historical 2010 individual in foster care still receives $52.75 (rates frozen since 1989). + absolute_error_margin: 0.01 + period: 2010-01 + input: + people: + person1: + is_ssi_eligible: true + # 2010 federal SSI individual FBR = $674/mo = $8,088/yr. + ssi: 8_088 + mt_ssp_payment_category: FOSTER_CARE + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MT + output: + # Rates frozen at 1989 levels per ARM 37.43.104; J tier still $52.75. + mt_ssp_eligible: true + mt_ssp: 52.75 + +- name: Case 13, single person with ssi_claim_is_joint=true cannot reach couple rate (marital_unit size 1). + absolute_error_margin: 0.01 + period: 2024-01 + input: + people: + person1: + is_ssi_eligible: true + # Degenerate: single person with stale joint flag. + ssi_claim_is_joint: true + mt_ssp_payment_category: ASSISTED_LIVING_OR_GROUP_OR_COMMUNITY_HOME + marital_units: + marital_unit: + members: [person1] + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MT + output: + # marital_unit.nb_persons() == 1 means couple gate is unreachable; + # falls back to individual rate $94. + mt_ssp_eligible: true + mt_ssp: 94 + +- name: Case 14, large business loss does not inflate the supplement. + absolute_error_margin: 0.01 + period: 2024-01 + input: + people: + person1: + is_ssi_eligible: true + # Large business loss; max_(0, -uncapped_ssi) clamps the spillover + # at 0 so a negative reduction cannot inflate the supplement. + self_employment_income: -50_000 + mt_ssp_payment_category: TRANSITIONAL_LIVING + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MT + output: + # Negative income does NOT inflate the supplement; max_(0, -uncapped_ssi) + # clamps spillover at 0. K tier individual rate = $26. + mt_ssp_eligible: true + mt_ssp: 26 + +- name: Case 15, multi-person SPM unit with parent + adult child both eligible sums correctly. + absolute_error_margin: 0.01 + period: 2024-01 + input: + people: + person1: + # Aged parent in personal care facility. + age: 70 + is_ssi_eligible: true + is_ssi_aged_blind_disabled: true + mt_ssp_payment_category: ASSISTED_LIVING_OR_GROUP_OR_COMMUNITY_HOME + person2: + # Adult disabled child in transitional living. + age: 30 + is_ssi_eligible: true + is_ssi_aged_blind_disabled: true + mt_ssp_payment_category: TRANSITIONAL_LIVING + marital_units: + marital_unit1: + members: [person1] + marital_unit2: + members: [person2] + spm_units: + spm_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: MT + output: + # Two separate marital units -> each gets individual rate. + # person1: G/H/I = $94; person2: K = $26. SPM total = $120. + mt_ssp_eligible: [true, true] + mt_ssp: 120 diff --git a/policyengine_us/tests/policy/baseline/gov/states/mt/dphhs/ssp/mt_ssp_couple_per_spouse.yaml b/policyengine_us/tests/policy/baseline/gov/states/mt/dphhs/ssp/mt_ssp_couple_per_spouse.yaml new file mode 100644 index 00000000000..8187f46104b --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/mt/dphhs/ssp/mt_ssp_couple_per_spouse.yaml @@ -0,0 +1,144 @@ +- name: Case 1, ASSISTED_LIVING couple gate met -> each spouse gets $96.50 ($193 / 2). + absolute_error_margin: 0.01 + period: 2024-01 + input: + people: + person1: + is_ssi_eligible: true + ssi_claim_is_joint: true + mt_ssp_payment_category: ASSISTED_LIVING_OR_GROUP_OR_COMMUNITY_HOME + person2: + is_ssi_eligible: true + ssi_claim_is_joint: true + mt_ssp_payment_category: ASSISTED_LIVING_OR_GROUP_OR_COMMUNITY_HOME + marital_units: + marital_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: MT + output: + # Couple total $193 / 2 = $96.50 per spouse. + mt_ssp_couple_per_spouse: [96.50, 96.50] + +- name: Case 2, FOSTER_CARE couple gate met -> each spouse gets $55.25 ($110.50 / 2). + absolute_error_margin: 0.01 + period: 2024-01 + input: + people: + person1: + is_ssi_eligible: true + ssi_claim_is_joint: true + mt_ssp_payment_category: FOSTER_CARE + person2: + is_ssi_eligible: true + ssi_claim_is_joint: true + mt_ssp_payment_category: FOSTER_CARE + marital_units: + marital_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: MT + output: + # Couple total $110.50 / 2 = $55.25 per spouse. + mt_ssp_couple_per_spouse: [55.25, 55.25] + +- name: Case 3, TRANSITIONAL_LIVING couple gate met -> each spouse gets $28.50 ($57 / 2). + absolute_error_margin: 0.01 + period: 2024-01 + input: + people: + person1: + is_ssi_eligible: true + ssi_claim_is_joint: true + mt_ssp_payment_category: TRANSITIONAL_LIVING + person2: + is_ssi_eligible: true + ssi_claim_is_joint: true + mt_ssp_payment_category: TRANSITIONAL_LIVING + marital_units: + marital_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: MT + output: + # Couple total $57 / 2 = $28.50 per spouse. + mt_ssp_couple_per_spouse: [28.50, 28.50] + +- name: Case 4, mismatched payment categories breaks the couple gate. + absolute_error_margin: 0.01 + period: 2024-01 + input: + people: + person1: + is_ssi_eligible: true + ssi_claim_is_joint: true + mt_ssp_payment_category: ASSISTED_LIVING_OR_GROUP_OR_COMMUNITY_HOME + person2: + is_ssi_eligible: true + ssi_claim_is_joint: true + mt_ssp_payment_category: FOSTER_CARE + marital_units: + marital_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: MT + output: + # Different categories -> gate fails; caller falls back to individual. + mt_ssp_couple_per_spouse: [0, 0] + +- name: Case 5, no joint claim breaks the couple gate. + absolute_error_margin: 0.01 + period: 2024-01 + input: + people: + person1: + is_ssi_eligible: true + ssi_claim_is_joint: false + mt_ssp_payment_category: ASSISTED_LIVING_OR_GROUP_OR_COMMUNITY_HOME + person2: + is_ssi_eligible: true + ssi_claim_is_joint: false + mt_ssp_payment_category: ASSISTED_LIVING_OR_GROUP_OR_COMMUNITY_HOME + marital_units: + marital_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: MT + output: + # No joint claim -> couple gate fails. + mt_ssp_couple_per_spouse: [0, 0] + +- name: Case 6, only one spouse eligible breaks the couple gate. + absolute_error_margin: 0.01 + period: 2024-01 + input: + people: + person1: + is_ssi_eligible: true + ssi_claim_is_joint: true + mt_ssp_payment_category: ASSISTED_LIVING_OR_GROUP_OR_COMMUNITY_HOME + person2: + # Spouse has too much income to be SSI-eligible. + employment_income: 60_000 + ssi_claim_is_joint: true + mt_ssp_payment_category: ASSISTED_LIVING_OR_GROUP_OR_COMMUNITY_HOME + marital_units: + marital_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: MT + output: + # Only person1 is mt_ssp_eligible -> couple gate fails. + mt_ssp_couple_per_spouse: [0, 0] diff --git a/policyengine_us/tests/policy/baseline/gov/states/mt/dphhs/ssp/mt_ssp_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/mt/dphhs/ssp/mt_ssp_eligible.yaml new file mode 100644 index 00000000000..055af536a77 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/mt/dphhs/ssp/mt_ssp_eligible.yaml @@ -0,0 +1,76 @@ +- name: Case 1, Montana resident with positive uncapped SSI in qualifying arrangement is eligible. + period: 2024-01 + input: + people: + person1: + is_ssi_eligible: true + mt_ssp_payment_category: ASSISTED_LIVING_OR_GROUP_OR_COMMUNITY_HOME + households: + household: + members: [person1] + state_code: MT + output: + mt_ssp_eligible: true + +- name: Case 2, Montana resident in NONE arrangement is ineligible. + period: 2024-01 + input: + people: + person1: + is_ssi_eligible: true + mt_ssp_payment_category: NONE + households: + household: + members: [person1] + state_code: MT + output: + # Living arrangement is not on the qualifying ARM 37.43.103 list. + mt_ssp_eligible: false + +- name: Case 3, Montana resident with no SSI eligibility (income too high) is ineligible. + period: 2024-01 + input: + people: + person1: + # Earnings too high to be SSI-eligible -> uncapped_ssi = 0. + employment_income: 60_000 + mt_ssp_payment_category: ASSISTED_LIVING_OR_GROUP_OR_COMMUNITY_HOME + households: + household: + members: [person1] + state_code: MT + output: + mt_ssp_eligible: false + +- name: Case 4, non-Montana resident in qualifying arrangement is ineligible. + period: 2024-01 + input: + people: + person1: + is_ssi_eligible: true + mt_ssp_payment_category: FOSTER_CARE + households: + household: + members: [person1] + state_code: WY + output: + # defined_for filter on the eligibility variable returns false outside MT. + mt_ssp_eligible: false + +- name: Case 5, Group 2 SSI-categorically-eligible person with income above FBR (ssi=0 but uncapped_ssi>0) is eligible. + period: 2024-01 + input: + people: + person1: + is_ssi_eligible: true + # Categorically eligible but no payment due to takeup; ARM + # 37.43.102(2)(a) covers Group 2 (eligible-except-for-income). + ssi: 0 + mt_ssp_payment_category: TRANSITIONAL_LIVING + households: + household: + members: [person1] + state_code: MT + output: + # uncapped_ssi > 0 gate covers Group 2 (income just below FBR). + mt_ssp_eligible: true diff --git a/policyengine_us/tests/policy/baseline/gov/states/mt/dphhs/ssp/mt_ssp_individual.yaml b/policyengine_us/tests/policy/baseline/gov/states/mt/dphhs/ssp/mt_ssp_individual.yaml new file mode 100644 index 00000000000..1bdceaa157a --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/mt/dphhs/ssp/mt_ssp_individual.yaml @@ -0,0 +1,80 @@ +- name: Case 1, ASSISTED_LIVING_OR_GROUP_OR_COMMUNITY_HOME individual rate is $94. + absolute_error_margin: 0.01 + period: 2024-01 + input: + people: + person1: + is_ssi_eligible: true + mt_ssp_payment_category: ASSISTED_LIVING_OR_GROUP_OR_COMMUNITY_HOME + households: + household: + members: [person1] + state_code: MT + output: + # ARM 37.43.104(1): G/H/I tier individual rate frozen at $94 since 1989. + mt_ssp_individual: 94 + +- name: Case 2, FOSTER_CARE individual rate is $52.75. + absolute_error_margin: 0.01 + period: 2024-01 + input: + people: + person1: + is_ssi_eligible: true + mt_ssp_payment_category: FOSTER_CARE + households: + household: + members: [person1] + state_code: MT + output: + # ARM 37.43.104(1): J tier individual rate frozen at $52.75 since 1989. + mt_ssp_individual: 52.75 + +- name: Case 3, TRANSITIONAL_LIVING individual rate is $26. + absolute_error_margin: 0.01 + period: 2024-01 + input: + people: + person1: + is_ssi_eligible: true + mt_ssp_payment_category: TRANSITIONAL_LIVING + households: + household: + members: [person1] + state_code: MT + output: + # ARM 37.43.104(1): K tier individual rate frozen at $26 since 1989. + mt_ssp_individual: 26 + +- name: Case 4, NONE category returns zero individual rate. + absolute_error_margin: 0.01 + period: 2024-01 + input: + people: + person1: + is_ssi_eligible: true + mt_ssp_payment_category: NONE + households: + household: + members: [person1] + state_code: MT + output: + # NONE tier maps to $0 in payment/individual.yaml. + mt_ssp_individual: 0 + +- name: Case 5, ineligible person (income too high) gets zero individual rate. + absolute_error_margin: 0.01 + period: 2024-01 + input: + people: + person1: + # Earnings too high to be SSI-eligible. + employment_income: 60_000 + mt_ssp_payment_category: ASSISTED_LIVING_OR_GROUP_OR_COMMUNITY_HOME + households: + household: + members: [person1] + state_code: MT + output: + # Eligibility gate fails -> individual amount is $0. + mt_ssp_individual: 0 diff --git a/policyengine_us/variables/gov/states/mt/dphhs/ssp/mt_ssp.py b/policyengine_us/variables/gov/states/mt/dphhs/ssp/mt_ssp.py new file mode 100644 index 00000000000..bd0b18b1de5 --- /dev/null +++ b/policyengine_us/variables/gov/states/mt/dphhs/ssp/mt_ssp.py @@ -0,0 +1,34 @@ +from policyengine_us.model_api import * + + +class mt_ssp(Variable): + value_type = float + entity = SPMUnit + label = "Montana State Supplementation" + unit = USD + definition_period = MONTH + defined_for = StateCode.MT + reference = ( + "https://www.law.cornell.edu/regulations/montana/ARM-37-43-101", + "https://www.law.cornell.edu/regulations/montana/ARM-37-43-102", + "https://www.law.cornell.edu/regulations/montana/ARM-37-43-103", + "https://www.law.cornell.edu/regulations/montana/ARM-37-43-104", + "https://secure.ssa.gov/poms.nsf/lnx/0501415010DEN", + "https://www.ssa.gov/policy/docs/progdesc/ssi_st_asst/2011/mt.html", + "https://leg.mt.gov/bills/mca/title_0520/chapter_0010/part_0010/section_0040/0520-0010-0010-0040.html", + ) + + def formula(spm_unit, period, parameters): + person = spm_unit.members + eligible = person("mt_ssp_eligible", period) + couple_per_spouse = person("mt_ssp_couple_per_spouse", period) + individual = person("mt_ssp_individual", period) + couple_active = couple_per_spouse > 0 + base_amount = where(couple_active, couple_per_spouse, individual) + # Residual countable income (federal SSI exhausted) spills onto + # the state supplement; uncapped_ssi is YEAR-defined so the + # framework auto-divides to monthly when accessed with period. + uncapped_ssi = person("uncapped_ssi", period) + reduction = max_(0, -uncapped_ssi) + per_person = max_(0, base_amount - reduction) * eligible + return spm_unit.sum(per_person) diff --git a/policyengine_us/variables/gov/states/mt/dphhs/ssp/mt_ssp_couple_per_spouse.py b/policyengine_us/variables/gov/states/mt/dphhs/ssp/mt_ssp_couple_per_spouse.py new file mode 100644 index 00000000000..7143b2b0e2f --- /dev/null +++ b/policyengine_us/variables/gov/states/mt/dphhs/ssp/mt_ssp_couple_per_spouse.py @@ -0,0 +1,27 @@ +from policyengine_us.model_api import * + + +class mt_ssp_couple_per_spouse(Variable): + value_type = float + entity = Person + label = "Montana SSP couple payment amount per spouse" + unit = USD + definition_period = MONTH + defined_for = "mt_ssp_eligible" + reference = ( + "https://secure.ssa.gov/poms.nsf/lnx/0501415010DEN", + "https://www.ssa.gov/policy/docs/progdesc/ssi_st_asst/2011/mt.html", + ) + + def formula(person, period, parameters): + joint_claim = person("ssi_claim_is_joint", period.this_year) + eligible = person("mt_ssp_eligible", period) + both_eligible = person.marital_unit.sum(eligible) == 2 + is_couple = person.marital_unit.nb_persons() == 2 + category = person("mt_ssp_payment_category", period) + shared_category = person.marital_unit.max(category) == person.marital_unit.min( + category + ) + couple_gate = joint_claim & both_eligible & is_couple & shared_category + p = parameters(period).gov.states.mt.dphhs.ssp + return where(couple_gate, p.amount.couple[category] / 2, 0) diff --git a/policyengine_us/variables/gov/states/mt/dphhs/ssp/mt_ssp_eligible.py b/policyengine_us/variables/gov/states/mt/dphhs/ssp/mt_ssp_eligible.py new file mode 100644 index 00000000000..c52e9507fcc --- /dev/null +++ b/policyengine_us/variables/gov/states/mt/dphhs/ssp/mt_ssp_eligible.py @@ -0,0 +1,25 @@ +from policyengine_us.model_api import * +from policyengine_us.variables.gov.states.mt.dphhs.ssp.mt_ssp_payment_category import ( + MTSSPPaymentCategory, +) + + +class mt_ssp_eligible(Variable): + value_type = bool + entity = Person + label = "Montana SSP eligible" + definition_period = MONTH + defined_for = StateCode.MT + reference = ( + "https://www.law.cornell.edu/regulations/montana/ARM-37-43-102", + "https://www.law.cornell.edu/regulations/montana/ARM-37-43-103", + ) + + def formula(person, period, parameters): + # ARM 37.43.102(2)(a) covers Group 2 (eligible-but-for-income), + # so we gate on uncapped_ssi > 0 to admit recipients whose + # countable income has zeroed the federal SSI payment. + uncapped_ssi = person("uncapped_ssi", period) + category = person("mt_ssp_payment_category", period) + in_qualifying_arrangement = category != MTSSPPaymentCategory.NONE + return (uncapped_ssi > 0) & in_qualifying_arrangement diff --git a/policyengine_us/variables/gov/states/mt/dphhs/ssp/mt_ssp_individual.py b/policyengine_us/variables/gov/states/mt/dphhs/ssp/mt_ssp_individual.py new file mode 100644 index 00000000000..a04f940949b --- /dev/null +++ b/policyengine_us/variables/gov/states/mt/dphhs/ssp/mt_ssp_individual.py @@ -0,0 +1,19 @@ +from policyengine_us.model_api import * + + +class mt_ssp_individual(Variable): + value_type = float + entity = Person + label = "Montana SSP individual payment amount" + unit = USD + definition_period = MONTH + defined_for = "mt_ssp_eligible" + reference = ( + "https://www.law.cornell.edu/regulations/montana/ARM-37-43-104", + "https://secure.ssa.gov/poms.nsf/lnx/0501415010DEN", + ) + + def formula(person, period, parameters): + category = person("mt_ssp_payment_category", period) + p = parameters(period).gov.states.mt.dphhs.ssp + return p.amount.individual[category] diff --git a/policyengine_us/variables/gov/states/mt/dphhs/ssp/mt_ssp_payment_category.py b/policyengine_us/variables/gov/states/mt/dphhs/ssp/mt_ssp_payment_category.py new file mode 100644 index 00000000000..e795fac6798 --- /dev/null +++ b/policyengine_us/variables/gov/states/mt/dphhs/ssp/mt_ssp_payment_category.py @@ -0,0 +1,25 @@ +from policyengine_us.model_api import * + + +class MTSSPPaymentCategory(Enum): + ASSISTED_LIVING_OR_GROUP_OR_COMMUNITY_HOME = ( + "Personal care, group home, or community home" + ) + FOSTER_CARE = "Foster care home" + TRANSITIONAL_LIVING = "Transitional living services" + NONE = "None" + + +class mt_ssp_payment_category(Variable): + value_type = Enum + entity = Person + label = "Montana SSP payment category" + definition_period = MONTH + defined_for = StateCode.MT + possible_values = MTSSPPaymentCategory + default_value = MTSSPPaymentCategory.NONE + reference = ( + "https://www.law.cornell.edu/regulations/montana/ARM-37-43-103", + "https://www.law.cornell.edu/regulations/montana/ARM-37-43-104", + "https://secure.ssa.gov/poms.nsf/lnx/0501415010DEN", + ) 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 4d6704d8346..daeb70f2fd0 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 @@ -29,6 +29,7 @@ def formula(spm_unit, period, parameters): "wa_ssp", # Washington benefits "mi_ssp", # Michigan benefits "me_ssp", # Maine benefits + "mt_ssp", # Montana benefits # California programs. "ca_cvrp", # California Clean Vehicle Rebate Project. # Colorado programs. From 2e77e193ed2e9e7d13af0d738e76fbad05347dff Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Fri, 22 May 2026 10:38:45 -0400 Subject: [PATCH 4/6] Align Montana SSP with monthly SSI --- .../gov/states/mt/dphhs/ssp/edge_cases.yaml | 10 ++++---- .../gov/states/mt/dphhs/ssp/integration.yaml | 25 +++++++++---------- .../gov/states/mt/dphhs/ssp/mt_ssp.py | 3 +-- 3 files changed, 18 insertions(+), 20 deletions(-) diff --git a/policyengine_us/tests/policy/baseline/gov/states/mt/dphhs/ssp/edge_cases.yaml b/policyengine_us/tests/policy/baseline/gov/states/mt/dphhs/ssp/edge_cases.yaml index b5ae45614b4..f2905685e46 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mt/dphhs/ssp/edge_cases.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mt/dphhs/ssp/edge_cases.yaml @@ -24,9 +24,9 @@ input: people: person1: - # Annual $0.01 -> monthly ~$0.000833 in the MONTH formula; still > 0 - # so eligibility passes. Spillover deduction = max_(0, -uncapped_ssi) - # = 0 since uncapped_ssi is positive, so full individual rate applies. + # Small positive uncapped SSI still passes eligibility. Spillover + # deduction = max_(0, -uncapped_ssi) = 0 since uncapped_ssi is + # positive, so full individual rate applies. uncapped_ssi: 0.01 mt_ssp_payment_category: TRANSITIONAL_LIVING spm_units: @@ -53,7 +53,7 @@ # case explicitly verifies the (ssi == 0, uncapped_ssi > 0) boundary. is_ssi_eligible: true ssi: 0 - uncapped_ssi: 12 + uncapped_ssi: 1 mt_ssp_payment_category: ASSISTED_LIVING_OR_GROUP_OR_COMMUNITY_HOME spm_units: spm_unit: @@ -63,7 +63,7 @@ members: [person1] state_code: MT output: - # uncapped_ssi annual $12 -> monthly $1; spillover = max_(0, -1) = 0; + # uncapped_ssi $1; spillover = max_(0, -1) = 0; # G/H/I tier = $94 - $0 = $94. mt_ssp_eligible: true mt_ssp: 94 diff --git a/policyengine_us/tests/policy/baseline/gov/states/mt/dphhs/ssp/integration.yaml b/policyengine_us/tests/policy/baseline/gov/states/mt/dphhs/ssp/integration.yaml index 5a81d50f129..52e29ae10a4 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mt/dphhs/ssp/integration.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mt/dphhs/ssp/integration.yaml @@ -64,15 +64,15 @@ input: people: person1: - # Couple split federal SSI: $11,316 individual FBR -> use joint - # total $1,415/mo = $16,980/yr -> half is $8,490 each. - ssi: 8_490 + # Couple split federal SSI: joint total $1,415/mo -> half is + # $707.50 each. + ssi: 707.50 is_ssi_aged_blind_disabled: true is_tax_unit_head_or_spouse: true ssi_claim_is_joint: true mt_ssp_payment_category: ASSISTED_LIVING_OR_GROUP_OR_COMMUNITY_HOME person2: - ssi: 8_490 + ssi: 707.50 is_ssi_aged_blind_disabled: true is_tax_unit_head_or_spouse: true ssi_claim_is_joint: true @@ -101,13 +101,13 @@ input: people: person1: - ssi: 8_490 + ssi: 707.50 is_ssi_aged_blind_disabled: true is_tax_unit_head_or_spouse: true ssi_claim_is_joint: true mt_ssp_payment_category: ASSISTED_LIVING_OR_GROUP_OR_COMMUNITY_HOME person2: - ssi: 8_490 + ssi: 707.50 is_ssi_aged_blind_disabled: true is_tax_unit_head_or_spouse: true ssi_claim_is_joint: true @@ -214,9 +214,8 @@ person1: age: 10 is_disabled: true - # Disabled minor receives federal SSI at 2024 individual FBR - # ($943/mo = $11,316/yr). - ssi: 11_316 + # Disabled minor receives federal SSI at 2024 individual FBR ($943/mo). + ssi: 943 mt_ssp_payment_category: FOSTER_CARE spm_units: spm_unit: @@ -262,9 +261,9 @@ people: person1: is_ssi_eligible: true - # 2024 individual FBR = $11,316/yr -> uncapped_ssi positive, + # 2024 individual FBR = $943/mo -> uncapped_ssi positive, # spillover deduction = max_(0, -uncapped_ssi) = 0. - ssi: 11_316 + ssi: 943 mt_ssp_payment_category: ASSISTED_LIVING_OR_GROUP_OR_COMMUNITY_HOME spm_units: spm_unit: @@ -285,8 +284,8 @@ people: person1: is_ssi_eligible: true - # 2010 federal SSI individual FBR = $674/mo = $8,088/yr. - ssi: 8_088 + # 2010 federal SSI individual FBR = $674/mo. + ssi: 674 mt_ssp_payment_category: FOSTER_CARE spm_units: spm_unit: diff --git a/policyengine_us/variables/gov/states/mt/dphhs/ssp/mt_ssp.py b/policyengine_us/variables/gov/states/mt/dphhs/ssp/mt_ssp.py index bd0b18b1de5..71647693bdf 100644 --- a/policyengine_us/variables/gov/states/mt/dphhs/ssp/mt_ssp.py +++ b/policyengine_us/variables/gov/states/mt/dphhs/ssp/mt_ssp.py @@ -26,8 +26,7 @@ def formula(spm_unit, period, parameters): couple_active = couple_per_spouse > 0 base_amount = where(couple_active, couple_per_spouse, individual) # Residual countable income (federal SSI exhausted) spills onto - # the state supplement; uncapped_ssi is YEAR-defined so the - # framework auto-divides to monthly when accessed with period. + # the state supplement. uncapped_ssi = person("uncapped_ssi", period) reduction = max_(0, -uncapped_ssi) per_person = max_(0, base_amount - reduction) * eligible From e4fc84a5652d46980f0f0af5c1cde9d79c17aeca Mon Sep 17 00:00:00 2001 From: Ziming Date: Tue, 13 Oct 2026 00:58:52 -0400 Subject: [PATCH 5/6] Normalize Montana SSP parameter and test formatting --- .../household/household_state_benefits.yaml | 1 + .../hud/annual_income/sources/unearned.yaml | 1 + .../ssi/state_supplement/minimum_payment.yaml | 1 + .../states/mt/dphhs/ssp/amount/couple.yaml | 6 +- .../mt/dphhs/ssp/amount/individual.yaml | 4 + .../gov/states/mt/dphhs/ssp/integration.yaml | 142 ++++++------------ .../gov/states/mt/dphhs/ssp/mt_ssp.yaml | 21 ++- 7 files changed, 79 insertions(+), 97 deletions(-) diff --git a/policyengine_us/parameters/gov/household/household_state_benefits.yaml b/policyengine_us/parameters/gov/household/household_state_benefits.yaml index 9e9403ad19a..744402b753b 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 a8a3d5f06b9..9ae1bbf4373 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/ssa/ssi/state_supplement/minimum_payment.yaml b/policyengine_us/parameters/gov/ssa/ssi/state_supplement/minimum_payment.yaml index 96464c8c040..1d3e6e2e6b6 100644 --- a/policyengine_us/parameters/gov/ssa/ssi/state_supplement/minimum_payment.yaml +++ b/policyengine_us/parameters/gov/ssa/ssi/state_supplement/minimum_payment.yaml @@ -1,6 +1,7 @@ description: The Social Security Administration pays at least this monthly amount to each recipient of a positive federally administered optional state supplement. values: 2026-01-01: 1 + metadata: unit: currency-USD period: month diff --git a/policyengine_us/parameters/gov/states/mt/dphhs/ssp/amount/couple.yaml b/policyengine_us/parameters/gov/states/mt/dphhs/ssp/amount/couple.yaml index ff723448802..7c7a83c70f1 100644 --- a/policyengine_us/parameters/gov/states/mt/dphhs/ssp/amount/couple.yaml +++ b/policyengine_us/parameters/gov/states/mt/dphhs/ssp/amount/couple.yaml @@ -14,11 +14,15 @@ metadata: ASSISTED_LIVING_OR_GROUP_OR_COMMUNITY_HOME: 2026-01-01: 193 + FOSTER_CARE: - 2026-01-01: 110.50 + 2026-01-01: 110.5 + TRANSITIONAL_LIVING: 2026-01-01: 57 + NONE: 2026-01-01: 0 + CATEGORY_B_ASSISTED_LIVING: 2026-01-01: 0 diff --git a/policyengine_us/parameters/gov/states/mt/dphhs/ssp/amount/individual.yaml b/policyengine_us/parameters/gov/states/mt/dphhs/ssp/amount/individual.yaml index 4f801bfd308..56b0ba71482 100644 --- a/policyengine_us/parameters/gov/states/mt/dphhs/ssp/amount/individual.yaml +++ b/policyengine_us/parameters/gov/states/mt/dphhs/ssp/amount/individual.yaml @@ -14,11 +14,15 @@ metadata: ASSISTED_LIVING_OR_GROUP_OR_COMMUNITY_HOME: 2026-01-01: 94 + FOSTER_CARE: 2026-01-01: 52.75 + TRANSITIONAL_LIVING: 2026-01-01: 26 + NONE: 2026-01-01: 0 + CATEGORY_B_ASSISTED_LIVING: 2026-01-01: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/states/mt/dphhs/ssp/integration.yaml b/policyengine_us/tests/policy/baseline/gov/states/mt/dphhs/ssp/integration.yaml index f8982e10db5..c3c621512f6 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mt/dphhs/ssp/integration.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mt/dphhs/ssp/integration.yaml @@ -3,191 +3,149 @@ absolute_error_margin: 0.01 input: people: - first: + person1: age: 65 mt_ssp_payment_category: ASSISTED_LIVING_OR_GROUP_OR_COMMUNITY_HOME - second: + person2: age: 65 mt_ssp_payment_category: ASSISTED_LIVING_OR_GROUP_OR_COMMUNITY_HOME marital_units: couple: - members: - - first - - second + members: [person1, person2] tax_units: unit: - members: - - first - - second + members: [person1, person2] households: household: - members: - - first - - second + members: [person1, person2] state_code: MT output: - mt_ssp: - - 96.5 - - 96.5 + mt_ssp: [96.5, 96.5] + - name: Case 2, mixed certified settings share the joint payment. period: 2026-01 absolute_error_margin: 0.01 input: people: - first: + person1: age: 65 mt_ssp_payment_category: ASSISTED_LIVING_OR_GROUP_OR_COMMUNITY_HOME - second: + person2: age: 65 mt_ssp_payment_category: FOSTER_CARE marital_units: couple: - members: - - first - - second + members: [person1, person2] tax_units: unit: - members: - - first - - second + members: [person1, person2] households: household: - members: - - first - - second + members: [person1, person2] state_code: MT output: - mt_ssp: - - 75.88 - - 75.88 + mt_ssp: [75.88, 75.88] + - name: Case 3, joint income reduces the supplement after federal SSI. period: 2026-01 absolute_error_margin: 0.01 input: people: - first: + person1: age: 65 mt_ssp_payment_category: ASSISTED_LIVING_OR_GROUP_OR_COMMUNITY_HOME - social_security_retirement: 19200 - second: + social_security_retirement: 19_200 + person2: age: 65 mt_ssp_payment_category: ASSISTED_LIVING_OR_GROUP_OR_COMMUNITY_HOME marital_units: couple: - members: - - first - - second + members: [person1, person2] tax_units: unit: - members: - - first - - second + members: [person1, person2] households: household: - members: - - first - - second + members: [person1, person2] state_code: MT output: - mt_ssp: - - 52 - - 52 + mt_ssp: [52, 52] + - name: Case 4, ineligible spouse deeming capped at individual rate. period: 2026-01 absolute_error_margin: 0.01 input: people: - first: + person1: age: 65 mt_ssp_payment_category: ASSISTED_LIVING_OR_GROUP_OR_COMMUNITY_HOME - second: + person2: age: 40 mt_ssp_payment_category: NONE - social_security_retirement: 19200 + social_security_retirement: 19_200 marital_units: couple: - members: - - first - - second + members: [person1, person2] tax_units: unit: - members: - - first - - second + members: [person1, person2] households: household: - members: - - first - - second + members: [person1, person2] state_code: MT output: - mt_ssp: - - 94 - - 0 + mt_ssp: [94, 0] + - name: Case 5, ineligible spouse income reduces state payment. period: 2026-01 absolute_error_margin: 0.01 input: people: - first: + person1: age: 65 mt_ssp_payment_category: ASSISTED_LIVING_OR_GROUP_OR_COMMUNITY_HOME - second: + person2: age: 40 mt_ssp_payment_category: NONE - social_security_retirement: 20400 + social_security_retirement: 20_400 marital_units: couple: - members: - - first - - second + members: [person1, person2] tax_units: unit: - members: - - first - - second + members: [person1, person2] households: household: - members: - - first - - second + members: [person1, person2] state_code: MT output: - mt_ssp: - - 4 - - 0 + mt_ssp: [4, 0] + - name: Case 6, ineligible spouse combined cutoff. period: 2026-01 absolute_error_margin: 0.01 input: people: - first: + person1: age: 65 mt_ssp_payment_category: ASSISTED_LIVING_OR_GROUP_OR_COMMUNITY_HOME - second: + person2: age: 40 mt_ssp_payment_category: NONE - social_security_retirement: 20448 + social_security_retirement: 20_448 marital_units: couple: - members: - - first - - second + members: [person1, person2] tax_units: unit: - members: - - first - - second + members: [person1, person2] households: household: - members: - - first - - second + members: [person1, person2] state_code: MT output: - mt_ssp: - - 0 - - 0 + mt_ssp: [0, 0] + - name: Case 7, annual benefit and SNAP income aggregation. period: 2026 absolute_error_margin: 0.01 @@ -196,7 +154,7 @@ age: 65 mt_ssp_payment_category: ASSISTED_LIVING_OR_GROUP_OR_COMMUNITY_HOME output: - mt_ssp: 1128 - household_state_benefits: 1128 - snap_unearned_income: 13056 - hud_unearned_income: 13056 + mt_ssp: 1_128 + household_state_benefits: 1_128 + snap_unearned_income: 13_056 + hud_unearned_income: 13_056 diff --git a/policyengine_us/tests/policy/baseline/gov/states/mt/dphhs/ssp/mt_ssp.yaml b/policyengine_us/tests/policy/baseline/gov/states/mt/dphhs/ssp/mt_ssp.yaml index e10810588ae..913b6cc6999 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mt/dphhs/ssp/mt_ssp.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mt/dphhs/ssp/mt_ssp.yaml @@ -8,6 +8,7 @@ mt_ssp_payment_category: ASSISTED_LIVING_OR_GROUP_OR_COMMUNITY_HOME output: mt_ssp: 94 + - name: Case 2, certified foster care. period: 2026-01 absolute_error_margin: 0.01 @@ -17,6 +18,7 @@ mt_ssp_payment_category: FOSTER_CARE output: mt_ssp: 52.75 + - name: Case 3, certified transitional services. period: 2026-01 absolute_error_margin: 0.01 @@ -26,6 +28,7 @@ mt_ssp_payment_category: TRANSITIONAL_LIVING output: mt_ssp: 26 + - name: Case 4, no approved setting. period: 2026-01 absolute_error_margin: 0.01 @@ -35,6 +38,7 @@ mt_ssp_payment_category: NONE output: mt_ssp: 0 + - name: Case 5, state payment continues after federal SSI is exhausted. period: 2026-01 absolute_error_margin: 0.01 @@ -42,10 +46,11 @@ state_code: MT age: 65 mt_ssp_payment_category: ASSISTED_LIVING_OR_GROUP_OR_COMMUNITY_HOME - social_security_retirement: 12768 + social_security_retirement: 12_768 output: ssi: 0 mt_ssp: 44 + - name: Case 6, income at combined standard. period: 2026-01 absolute_error_margin: 0.01 @@ -53,9 +58,10 @@ state_code: MT age: 65 mt_ssp_payment_category: ASSISTED_LIVING_OR_GROUP_OR_COMMUNITY_HOME - social_security_retirement: 13296 + social_security_retirement: 13_296 output: mt_ssp: 0 + - name: Case 7, positive payment below one dollar receives the federal minimum. period: 2026-01 absolute_error_margin: 0.01 @@ -63,9 +69,10 @@ state_code: MT age: 65 mt_ssp_payment_category: ASSISTED_LIVING_OR_GROUP_OR_COMMUNITY_HOME - social_security_retirement: 13295.88 + social_security_retirement: 13_295.88 output: mt_ssp: 1 + - name: Case 8, excess resources. period: 2026-01 absolute_error_margin: 0.01 @@ -73,9 +80,10 @@ state_code: MT age: 65 mt_ssp_payment_category: ASSISTED_LIVING_OR_GROUP_OR_COMMUNITY_HOME - ssi_countable_resources: 2001 + ssi_countable_resources: 2_001 output: mt_ssp: 0 + - name: Case 9, no aged blind or disabled category. period: 2026-01 absolute_error_margin: 0.01 @@ -85,6 +93,7 @@ mt_ssp_payment_category: ASSISTED_LIVING_OR_GROUP_OR_COMMUNITY_HOME output: mt_ssp: 0 + - name: Case 10, excluded category B assisted living. period: 2026-01 absolute_error_margin: 0.01 @@ -94,6 +103,7 @@ mt_ssp_payment_category: CATEGORY_B_ASSISTED_LIVING output: mt_ssp: 0 + - name: Case 11, section 1619 benefits excluded. period: 2026-01 absolute_error_margin: 0.01 @@ -104,6 +114,7 @@ mt_ssp_receives_section_1619_benefits: true output: mt_ssp: 0 + - name: Case 12, out of state custody exception. period: 2026-01 absolute_error_margin: 0.01 @@ -114,6 +125,7 @@ mt_ssp_in_state_custody: true output: mt_ssp: 94 + - name: Case 13, out of state without Montana custody. period: 2026-01 absolute_error_margin: 0.01 @@ -123,6 +135,7 @@ mt_ssp_payment_category: ASSISTED_LIVING_OR_GROUP_OR_COMMUNITY_HOME output: mt_ssp: 0 + - name: Case 14, SSI application takeup required. period: 2026-01 absolute_error_margin: 0.01 From 60e402e33751d8ba55c919de4aa6ec5db9b67070 Mon Sep 17 00:00:00 2001 From: Ziming Date: Tue, 20 Oct 2026 03:36:18 -0400 Subject: [PATCH 6/6] Address review findings on Montana SSP Gate mt_ssp to Montana, pay nothing in a Medicaid facility (federal code D carries state code Z), drop the custody and section 1619 inputs in favor of documented limits, count the SSP in the elderly homeowner or renter credit's gross household income, fix shared-list placement, descriptions and references, and add couple, disabled, 2027 and SPM benefit cases. Co-Authored-By: Claude Opus 5.5 --- changelog.d/mt-ssp.added.md | 2 +- .../household/household_state_benefits.yaml | 8 +-- .../states/mt/dphhs/ssp/amount/couple.yaml | 20 ++++-- .../mt/dphhs/ssp/amount/individual.yaml | 20 ++++-- .../gross_income_sources.yaml | 5 ++ .../usda/snap/income/sources/unearned.yaml | 4 +- policyengine_us/programs.yaml | 7 ++- .../gov/states/mt/dphhs/ssp/integration.yaml | 56 ++++++++++++++++- .../gov/states/mt/dphhs/ssp/mt_ssp.yaml | 61 ++++++++++++++++--- .../gov/states/mt/dphhs/ssp/mt_ssp.py | 45 +++++++++++--- .../mt/dphhs/ssp/mt_ssp_in_state_custody.py | 11 ---- .../mt/dphhs/ssp/mt_ssp_payment_category.py | 15 ++++- .../mt_ssp_receives_section_1619_benefits.py | 11 ---- .../income/spm_unit/spm_unit_benefits.py | 2 +- 14 files changed, 203 insertions(+), 64 deletions(-) delete mode 100644 policyengine_us/variables/gov/states/mt/dphhs/ssp/mt_ssp_in_state_custody.py delete mode 100644 policyengine_us/variables/gov/states/mt/dphhs/ssp/mt_ssp_receives_section_1619_benefits.py diff --git a/changelog.d/mt-ssp.added.md b/changelog.d/mt-ssp.added.md index f3fd97658b9..6a5cd287c93 100644 --- a/changelog.d/mt-ssp.added.md +++ b/changelog.d/mt-ssp.added.md @@ -1 +1 @@ -Add Montana optional State Supplementary Payments with income phaseout, couple and deeming calculations, verified for 2026. +Add Montana optional State Supplementary Payments with income phaseout, couple and deeming calculations, verified for 2026, and count them in Montana elderly homeowner or renter credit gross household income. diff --git a/policyengine_us/parameters/gov/household/household_state_benefits.yaml b/policyengine_us/parameters/gov/household/household_state_benefits.yaml index 3961524fc64..126bc59d2a6 100644 --- a/policyengine_us/parameters/gov/household/household_state_benefits.yaml +++ b/policyengine_us/parameters/gov/household/household_state_benefits.yaml @@ -17,6 +17,7 @@ values: - mo_child_care_subsidies # Montana benefits - mt_child_care_subsidies + - mt_ssp # Massachusetts benefits - ma_state_supplement # Maryland benefits @@ -87,7 +88,6 @@ values: # DC benefits - dc_ossp # Kansas benefits - - mt_ssp - ks_sspp # Hawaii benefits - hi_oss @@ -119,6 +119,7 @@ values: - mo_child_care_subsidies # Montana benefits - mt_child_care_subsidies + - mt_ssp # Massachusetts benefits - ma_state_supplement # Maryland benefits @@ -194,7 +195,6 @@ values: # DC benefits - dc_ossp # Kansas benefits - - mt_ssp - ks_sspp # Hawaii benefits - hi_oss @@ -230,6 +230,7 @@ values: - mo_child_care_subsidies # Montana benefits - mt_child_care_subsidies + - mt_ssp # Massachusetts benefits - ma_state_supplement # Maryland benefits @@ -306,7 +307,6 @@ values: - dc_ossp - dc_child_care_subsidies # Kansas benefits - - mt_ssp - ks_sspp # Hawaii benefits - hi_oss @@ -342,6 +342,7 @@ values: - mo_child_care_subsidies # Montana benefits - mt_child_care_subsidies + - mt_ssp # Massachusetts benefits - ma_state_supplement # Maryland benefits @@ -418,7 +419,6 @@ values: - dc_ossp - dc_child_care_subsidies # Kansas benefits - - mt_ssp - ks_sspp # Hawaii benefits - hi_oss diff --git a/policyengine_us/parameters/gov/states/mt/dphhs/ssp/amount/couple.yaml b/policyengine_us/parameters/gov/states/mt/dphhs/ssp/amount/couple.yaml index 7c7a83c70f1..4bd06139102 100644 --- a/policyengine_us/parameters/gov/states/mt/dphhs/ssp/amount/couple.yaml +++ b/policyengine_us/parameters/gov/states/mt/dphhs/ssp/amount/couple.yaml @@ -1,4 +1,4 @@ -description: Montana provides this amount as the couple state supplement payment under the State Supplementation program. +description: Montana provides these amounts as the monthly couple payment under the State Supplementary Payment program. metadata: unit: currency-USD @@ -7,22 +7,30 @@ metadata: breakdown: - mt_ssp_payment_category reference: - - title: POMS SI 01415.058 H.2, Montana payment levels for January 2026 - href: https://secure.ssa.gov/poms.nsf/lnx/0501415058#h - - title: POMS SI DEN01415.010, Montana supplementary payments + - title: POMS SI 01415.058 H.2.b, Montana couple payment levels (January 2026) + href: https://secure.ssa.gov/poms.nsf/lnx/0501415058#h2b + - title: POMS SI DEN01415.010 B.6, Montana supplementary payment categories href: https://secure.ssa.gov/poms.nsf/lnx/0501415010DEN + - title: MCA 52-1-104(1)-(3) + href: https://mca.legmt.gov/bills/mca/title_0520/chapter_0010/part_0010/section_0040/0520-0010-0010-0040.html +# POMS codes G, H and I. ASSISTED_LIVING_OR_GROUP_OR_COMMUNITY_HOME: 2026-01-01: 193 +# POMS code J. FOSTER_CARE: 2026-01-01: 110.5 +# POMS code K. TRANSITIONAL_LIVING: 2026-01-01: 57 -NONE: +# MCA 52-1-104(3) bars payments to category B assisted living residents. +CATEGORY_B_ASSISTED_LIVING: 2026-01-01: 0 -CATEGORY_B_ASSISTED_LIVING: +# No certified placement, POMS SI 01415.058 H.1 code Z (not certified in codes +# G-K); also the default for people outside Montana. +NONE: 2026-01-01: 0 diff --git a/policyengine_us/parameters/gov/states/mt/dphhs/ssp/amount/individual.yaml b/policyengine_us/parameters/gov/states/mt/dphhs/ssp/amount/individual.yaml index 56b0ba71482..d57e777713d 100644 --- a/policyengine_us/parameters/gov/states/mt/dphhs/ssp/amount/individual.yaml +++ b/policyengine_us/parameters/gov/states/mt/dphhs/ssp/amount/individual.yaml @@ -1,4 +1,4 @@ -description: Montana provides this amount as the individual state supplement payment under the State Supplementation program. +description: Montana provides these amounts as the monthly individual payment under the State Supplementary Payment program. metadata: unit: currency-USD @@ -7,22 +7,30 @@ metadata: breakdown: - mt_ssp_payment_category reference: - - title: POMS SI 01415.058 H.2, Montana payment levels for January 2026 - href: https://secure.ssa.gov/poms.nsf/lnx/0501415058#h - - title: POMS SI DEN01415.010, Montana supplementary payments + - title: POMS SI 01415.058 H.2.a, Montana individual payment levels (January 2026) + href: https://secure.ssa.gov/poms.nsf/lnx/0501415058#h2a + - title: POMS SI DEN01415.010 B.6, Montana supplementary payment categories href: https://secure.ssa.gov/poms.nsf/lnx/0501415010DEN + - title: MCA 52-1-104(1)-(3) + href: https://mca.legmt.gov/bills/mca/title_0520/chapter_0010/part_0010/section_0040/0520-0010-0010-0040.html +# POMS codes G, H and I. ASSISTED_LIVING_OR_GROUP_OR_COMMUNITY_HOME: 2026-01-01: 94 +# POMS code J. FOSTER_CARE: 2026-01-01: 52.75 +# POMS code K. TRANSITIONAL_LIVING: 2026-01-01: 26 -NONE: +# MCA 52-1-104(3) bars payments to category B assisted living residents. +CATEGORY_B_ASSISTED_LIVING: 2026-01-01: 0 -CATEGORY_B_ASSISTED_LIVING: +# No certified placement, POMS SI 01415.058 H.1 code Z (not certified in codes +# G-K); also the default for people outside Montana. +NONE: 2026-01-01: 0 diff --git a/policyengine_us/parameters/gov/states/mt/tax/income/credits/elderly_homeowner_or_renter/gross_income_sources.yaml b/policyengine_us/parameters/gov/states/mt/tax/income/credits/elderly_homeowner_or_renter/gross_income_sources.yaml index 730271ed1c2..f28c95c8377 100644 --- a/policyengine_us/parameters/gov/states/mt/tax/income/credits/elderly_homeowner_or_renter/gross_income_sources.yaml +++ b/policyengine_us/parameters/gov/states/mt/tax/income/credits/elderly_homeowner_or_renter/gross_income_sources.yaml @@ -60,6 +60,9 @@ values: # so the household sum counts the unit's TANF once. LIEAP and SNAP are # not cash public assistance (2014-2017 Form 2EC instructions). - ssi + # § 15-30-2337(9)(a)(vi) – the Montana State Supplementary Payment is + # nontaxable cash public assistance paid with SSI; ssi excludes it. + - mt_ssp - tanf_person - workers_compensation # Guaranteed income pilot payments are outside federal AGI here, and @@ -97,6 +100,7 @@ values: - strike_benefits - general_assistance - ssi + - mt_ssp - tanf_person - workers_compensation - gi_cash_assistance @@ -119,6 +123,7 @@ values: - strike_benefits - general_assistance - ssi + - mt_ssp - tanf_person - workers_compensation - gi_cash_assistance 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 b8b036b04e3..a98dc021f62 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 - - mt_ssp - ks_sspp - ky_ssp - la_oss @@ -46,6 +45,7 @@ values: - mi_ssp_person - mn_msa_person - mo_ssp + - mt_ssp - nc_sa - ne_aabd - nj_ssp @@ -101,7 +101,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/programs.yaml b/policyengine_us/programs.yaml index 55e5c1e464f..a75fa2e4543 100644 --- a/policyengine_us/programs.yaml +++ b/policyengine_us/programs.yaml @@ -1156,9 +1156,10 @@ programs: parameter_prefix: gov.states.mt.dphhs.ssp verified_years: "2026" notes: >- - Models optional supplements for state-certified care settings, - including the out-of-state legal-custody exception and section 1619 - exclusion. Mandatory grandfathered supplementation is not modeled. + Models 2026 Montana optional supplements for state-certified + personal care, group home, foster care and transitional living + placements. Mandatory grandfathered supplementation and out-of-state + custody placements are not modeled. - id: social_security name: Social Security diff --git a/policyengine_us/tests/policy/baseline/gov/states/mt/dphhs/ssp/integration.yaml b/policyengine_us/tests/policy/baseline/gov/states/mt/dphhs/ssp/integration.yaml index c3c621512f6..be285e59597 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mt/dphhs/ssp/integration.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mt/dphhs/ssp/integration.yaml @@ -146,15 +146,69 @@ output: mt_ssp: [0, 0] -- name: Case 7, annual benefit and SNAP income aggregation. +- name: Case 7, annual benefit in the benefit and income aggregates. period: 2026 absolute_error_margin: 0.01 input: state_code: MT age: 65 mt_ssp_payment_category: ASSISTED_LIVING_OR_GROUP_OR_COMMUNITY_HOME + # Keeps projected SNAP out of spm_unit_benefits. + takes_up_snap_if_eligible: false output: + # 94 x 12 = 1,128 Montana; 994 x 12 = 11,928 federal SSI. mt_ssp: 1_128 household_state_benefits: 1_128 + # 11,928 + 1,128 = 13,056. + spm_unit_benefits: 13_056 snap_unearned_income: 13_056 hud_unearned_income: 13_056 + +- name: Case 8, spouses in separate marital units after one enters a certified home. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 65 + mt_ssp_payment_category: ASSISTED_LIVING_OR_GROUP_OR_COMMUNITY_HOME + person2: + age: 65 + # From the month after they stop living together, SSA treats the spouses + # as individuals (20 CFR 416.1801(c); SI 00501.154). + marital_units: + person1_marital_unit: + members: [person1] + person2_marital_unit: + members: [person2] + tax_units: + unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: MT + output: + ssi_countable_income: [0, 0] + # Each spouse receives the 994 individual federal benefit rate. + ssi_amount_if_eligible: [994, 994] + ssi: [994, 994] + # person1: the 94 individual personal care, group or community home level. + # person2: no certified placement (POMS SI 01415.058 H.1 code Z), so 0. + mt_ssp: [94, 0] + +- name: Case 9, elderly homeowner or renter credit income counts the supplement. + period: 2026 + absolute_error_margin: 0.01 + input: + state_code: MT + age: 65 + mt_ssp_payment_category: ASSISTED_LIVING_OR_GROUP_OR_COMMUNITY_HOME + output: + # 994 x 12 = 11,928 federal SSI. + ssi: 11_928 + # 94 x 12 = 1,128 Montana supplement. + mt_ssp: 1_128 + # MCA 15-30-2337(9)(a)(vi) counts cash public assistance and relief: + # 11,928 + 1,128 = 13,056. + mt_elderly_homeowner_or_renter_credit_gross_household_income: 13_056 diff --git a/policyengine_us/tests/policy/baseline/gov/states/mt/dphhs/ssp/mt_ssp.yaml b/policyengine_us/tests/policy/baseline/gov/states/mt/dphhs/ssp/mt_ssp.yaml index 913b6cc6999..4b4635f8e8e 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mt/dphhs/ssp/mt_ssp.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mt/dphhs/ssp/mt_ssp.yaml @@ -69,8 +69,11 @@ state_code: MT age: 65 mt_ssp_payment_category: ASSISTED_LIVING_OR_GROUP_OR_COMMUNITY_HOME - social_security_retirement: 13_295.88 + # 13,055.88 / 12 = 1,087.99 countable a month, so federal SSI is 0 and + # the supplement is 94 - (1,087.99 - 994) = 0.01, raised to $1. + ssi_countable_income: 13_055.88 output: + ssi: 0 mt_ssp: 1 - name: Case 8, excess resources. @@ -104,29 +107,41 @@ output: mt_ssp: 0 -- name: Case 11, section 1619 benefits excluded. +- name: Case 11, disabled worker above the SGA limit receives no supplement. period: 2026-01 absolute_error_margin: 0.01 input: state_code: MT - age: 65 + age: 40 + meets_ssi_disability_criteria: true mt_ssp_payment_category: ASSISTED_LIVING_OR_GROUP_OR_COMMUNITY_HOME - mt_ssp_receives_section_1619_benefits: true + # Full-time work at the 2026 Montana minimum wage of $10.85 (Montana + # DLI): 10.85 x 40 hours x 52 weeks = 22,568 a year, 1,880.67 a month, + # above the 1,690 non-blind SGA limit, so the model treats this person as + # not disabled for SSI. A recipient who keeps SSI at this level under + # section 1619(a) is excluded by SI DEN01415.010 B.1, so neither path pays. + employment_income: 22_568 output: + ssi: 0 mt_ssp: 0 -- name: Case 12, out of state custody exception. +- name: Case 12, certified resident in a Medicaid facility. period: 2026-01 absolute_error_margin: 0.01 input: - state_code: WY + state_code: MT age: 65 mt_ssp_payment_category: ASSISTED_LIVING_OR_GROUP_OR_COMMUNITY_HOME - mt_ssp_in_state_custody: true + ssi_lives_in_medical_treatment_facility: true + ssi_medicaid_pays_majority_of_care: true output: - mt_ssp: 94 + # Federal code D pays the 30 reduced benefit. POMS SI 01415.058 H.2 pairs + # code D only with state code Z, so the certified rate of 94 is not paid. + ssi_federal_living_arrangement: MEDICAL_TREATMENT_FACILITY + ssi: 30 + mt_ssp: 0 -- name: Case 13, out of state without Montana custody. +- name: Case 13, Wyoming resident. period: 2026-01 absolute_error_margin: 0.01 input: @@ -146,3 +161,31 @@ takes_up_ssi_if_eligible: false output: mt_ssp: 0 + +- name: Case 15, disabled adult in certified personal care. + period: 2026-01 + absolute_error_margin: 0.01 + input: + state_code: MT + age: 40 + meets_ssi_disability_criteria: true + mt_ssp_payment_category: ASSISTED_LIVING_OR_GROUP_OR_COMMUNITY_HOME + output: + # No income: the full 994 federal benefit rate plus the 94 individual + # personal care, group or community home level. + ssi: 994 + mt_ssp: 94 + +- name: Case 16, 2027 payment after the federal benefit rate increase. + period: 2027-01 + absolute_error_margin: 0.01 + input: + state_code: MT + age: 65 + mt_ssp_payment_category: ASSISTED_LIVING_OR_GROUP_OR_COMMUNITY_HOME + output: + # The 2027 COLA raises the federal benefit rate to 1,029. The Montana + # level is the January 2026 value of 94 carried forward; no 2027 + # Montana schedule has been verified. + ssi: 1_029 + mt_ssp: 94 diff --git a/policyengine_us/variables/gov/states/mt/dphhs/ssp/mt_ssp.py b/policyengine_us/variables/gov/states/mt/dphhs/ssp/mt_ssp.py index 4b4fcf32ca0..00480452bac 100644 --- a/policyengine_us/variables/gov/states/mt/dphhs/ssp/mt_ssp.py +++ b/policyengine_us/variables/gov/states/mt/dphhs/ssp/mt_ssp.py @@ -10,19 +10,50 @@ class mt_ssp(Variable): label = "Montana State Supplementary Payment" unit = USD definition_period = MONTH + defined_for = StateCode.MT reference = ( + "https://mca.legmt.gov/bills/mca/title_0520/chapter_0010/part_0010/section_0040/0520-0010-0010-0040.html", "https://secure.ssa.gov/poms.nsf/lnx/0501415058#h", "https://secure.ssa.gov/poms.nsf/lnx/0501415010DEN", + "https://secure.ssa.gov/poms.nsf/lnx/0501320430#d", "https://www.ssa.gov/OP_Home/cfr20/416/416-2025.htm", ) def formula(person, period, parameters): - state = person.household("state_code", period.this_year) - resident = (state == StateCode.MT) | person("mt_ssp_in_state_custody", period) - excluded = person("mt_ssp_receives_section_1619_benefits", period) - category = person("mt_ssp_payment_category", period) p = parameters(period).gov.states.mt.dphhs.ssp.amount - payment = ssi_state_supplement( - person, period, parameters, p.individual[category], p.couple[category] + category = person("mt_ssp_payment_category", period) + # POMS SI 01415.058 H.2 pairs state codes G-K only with federal code A; + # federal code D (a Title XIX facility) carries state code Z, so a + # Medicaid-facility resident receives no supplement. + federal_la = person("ssi_federal_living_arrangement", period) + in_medicaid_facility = ( + federal_la == federal_la.possible_values.MEDICAL_TREATMENT_FACILITY ) - return resident * ~excluded * payment + individual = where(in_medicaid_facility, 0, p.individual[category]) + couple = where(in_medicaid_facility, 0, p.couple[category]) + # NOTE: SI DEN01415.010 B.3 lets Montana pay a recipient it places + # outside Montana while keeping legal custody. PolicyEngine assigns + # state supplements by the household's state, and the host state's + # supplement would also apply, so this exception is not modeled. + # NOTE: SI DEN01415.010 B.1 excludes section 1619(a) recipients and + # section 1619 eligibles institutionalized throughout a month in a + # medical facility. is_ssi_disabled already makes a disabled person + # whose earnings exceed SGA SSI-ineligible, so the 1619(a) group + # receives nothing here; a Title XIX facility resident falls under the + # code D rule above, and other medical facilities are not modeled. + # NOTE: The shared helper averages the spouses' couple rates, so a + # joint couple with one spouse outside a certified setting splits half + # the couple rate equally. SSA treats spouses who stop living together + # as individuals from the month after the change (20 CFR 416.1801(c); + # SI 00501.154), so such couples need separate marital units. + # NOTE: The federal couple computation reads only aged, blind or + # disabled status, so a spouse who declines SSI or is otherwise + # ineligible still makes the claim joint (issue 10138). + # NOTE: With an ineligible spouse, the helper pays the lesser of the + # individual-rate amount on the claimant's own income and the + # couple-rate amount on the deemed income; every Montana couple rate + # exceeds the individual rate (SI 01320.430 D.3.b). + # NOTE: The microdata do not record Montana's placement certification, + # so this payment appears only in household calculations that set + # mt_ssp_payment_category. + return ssi_state_supplement(person, period, parameters, individual, couple) diff --git a/policyengine_us/variables/gov/states/mt/dphhs/ssp/mt_ssp_in_state_custody.py b/policyengine_us/variables/gov/states/mt/dphhs/ssp/mt_ssp_in_state_custody.py deleted file mode 100644 index dd8f1373fc3..00000000000 --- a/policyengine_us/variables/gov/states/mt/dphhs/ssp/mt_ssp_in_state_custody.py +++ /dev/null @@ -1,11 +0,0 @@ -from policyengine_us.model_api import * - - -class mt_ssp_in_state_custody(Variable): - value_type = bool - entity = Person - label = "Retained in Montana legal custody while living outside Montana" - definition_period = MONTH - default_value = False - reference = "https://secure.ssa.gov/poms.nsf/lnx/0501415010DEN" - documentation = "Montana retains legal custody and responsibility for the certified SSI placement." diff --git a/policyengine_us/variables/gov/states/mt/dphhs/ssp/mt_ssp_payment_category.py b/policyengine_us/variables/gov/states/mt/dphhs/ssp/mt_ssp_payment_category.py index a68ee4f6d14..8cdaa1a9008 100644 --- a/policyengine_us/variables/gov/states/mt/dphhs/ssp/mt_ssp_payment_category.py +++ b/policyengine_us/variables/gov/states/mt/dphhs/ssp/mt_ssp_payment_category.py @@ -2,13 +2,13 @@ class MTSSPPaymentCategory(Enum): - NONE = "No state-certified supplementary payment placement" ASSISTED_LIVING_OR_GROUP_OR_COMMUNITY_HOME = ( "State-certified personal care, group or community home" ) FOSTER_CARE = "State-certified child or adult foster care" TRANSITIONAL_LIVING = "State-certified transitional living services" CATEGORY_B_ASSISTED_LIVING = "Excluded category B assisted living facility" + NONE = "No state-certified supplementary payment placement" class mt_ssp_payment_category(Variable): @@ -19,10 +19,21 @@ class mt_ssp_payment_category(Variable): possible_values = MTSSPPaymentCategory default_value = MTSSPPaymentCategory.NONE reference = ( + "https://secure.ssa.gov/poms.nsf/lnx/0501415058#h1", "https://secure.ssa.gov/poms.nsf/lnx/0501415010DEN", "https://mca.legmt.gov/bills/mca/title_0520/chapter_0010/part_0010/section_0040/0520-0010-0010-0040.html", + "https://secure.ssa.gov/poms.nsf/lnx/0502005030#g", + "https://secure.ssa.gov/poms.nsf/lnx/0500501154", ) documentation = """ A positive category requires Montana's placement certification, not just - residence in a licensed facility. Category B assisted living is excluded. + residence in a licensed facility. Category B assisted living is excluded + (MCA 52-1-104(3)). Certified residents are in federal living arrangement A + (SI DEN01415.010 B.6.b), so leave the household-of-another inputs unset for + them; a resident of a Medicaid facility receives no supplement. A joint + couple averages the two spouses' couple rates (SI 02005.030 G.1). Put + spouses who no longer live in the same household, such as a certified-home + resident whose spouse stays at home, in separate marital units from the + month after they stop living together. Keep one marital unit for the month + of entry. """ diff --git a/policyengine_us/variables/gov/states/mt/dphhs/ssp/mt_ssp_receives_section_1619_benefits.py b/policyengine_us/variables/gov/states/mt/dphhs/ssp/mt_ssp_receives_section_1619_benefits.py deleted file mode 100644 index f1e35c66b1f..00000000000 --- a/policyengine_us/variables/gov/states/mt/dphhs/ssp/mt_ssp_receives_section_1619_benefits.py +++ /dev/null @@ -1,11 +0,0 @@ -from policyengine_us.model_api import * - - -class mt_ssp_receives_section_1619_benefits(Variable): - value_type = bool - entity = Person - label = "Receives section 1619 benefits excluded from Montana supplementation" - definition_period = MONTH - default_value = False - reference = "https://secure.ssa.gov/poms.nsf/lnx/0501415010DEN" - documentation = "Section 1619(a) receipt, or section 1619 receipt while institutionalized throughout the month, as defined in SI DEN01415.010 B.1." 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 9505d250b8a..445e85a85e0 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 @@ -26,7 +26,7 @@ def formula(spm_unit, period, parameters): "pa_ssp", # Pennsylvania benefits "wy_ssp", # Wyoming benefits "nc_sa", # North Carolina benefits - "mt_ssp", + "mt_ssp", # Montana benefits "ks_sspp", # Kansas benefits "hi_oss", "la_oss", # Louisiana benefits