From 16ab90e78af9bbc8237d7728da8b46a9d665c660 Mon Sep 17 00:00:00 2001 From: Ziming Date: Thu, 30 Apr 2026 18:02:00 -0400 Subject: [PATCH 01/17] Initialize MN MSA branch Co-Authored-By: Claude Opus 4.7 (1M context) --- changelog.d/mn-msa.added.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/mn-msa.added.md diff --git a/changelog.d/mn-msa.added.md b/changelog.d/mn-msa.added.md new file mode 100644 index 00000000000..02ae8cdd9ab --- /dev/null +++ b/changelog.d/mn-msa.added.md @@ -0,0 +1 @@ +Minnesota Supplemental Aid (MSA) — State Supplementary Payment to SSI. From 2116a76447be53983eeb6407b4dc691ea59f344e Mon Sep 17 00:00:00 2001 From: Ziming Date: Thu, 30 Apr 2026 20:52:24 -0400 Subject: [PATCH 02/17] =?UTF-8?q?Implement=20Minnesota=20Supplemental=20Ai?= =?UTF-8?q?d=20(MSA)=20=E2=80=94=20State=20Supplementary=20Payment=20to=20?= =?UTF-8?q?SSI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds eligibility, income, payment, and special-needs logic for Minnesota's optional state supplement administered by MN DHS under Minn. Stat. §§ 256D.33–256D.54. - 11 parameters under gov/states/mn/dhs/msa/ - 26 variables (5-value living-arrangement enum, two eligibility tracks, all 4 special-needs add-ons) - 14 test files (~50+ cases) including boundary/edge cases - Wired into spm_unit_benefits.py, household_state_benefits.yaml, programs.yaml Refs #8214 Co-Authored-By: Claude Opus 4.7 (1M context) --- .../household/household_state_benefits.yaml | 4 + .../dhs/msa/assistance_standard/amount.yaml | 47 ++ .../mn/dhs/msa/disregard/earned/initial.yaml | 14 + .../mn/dhs/msa/disregard/earned/rate.yaml | 14 + .../asset_limit/non_ssi_track.yaml | 16 + .../income_limit/couple_fbr_multiplier.yaml | 14 + .../individual_fbr_multiplier.yaml | 14 + .../housing_assistance/fbr_multiplier.yaml | 14 + .../guardian_fee/max_amount.yaml | 14 + .../msa/special_needs/guardian_fee/rate.yaml | 14 + .../representative_payee_fee/amount.yaml | 14 + .../shelter_need/fbr_multiplier.yaml | 14 + policyengine_us/programs.yaml | 6 + .../mn_msa_categorically_eligible.yaml | 103 ++++ .../eligibility/mn_msa_eligible_person.yaml | 121 +++++ .../mn_msa_gross_income_eligible.yaml | 221 ++++++++ .../mn_msa_net_income_eligible.yaml | 92 ++++ .../eligibility/mn_msa_resource_eligible.yaml | 231 +++++++++ .../mn_msa_countable_earned_income.yaml | 170 +++++++ .../msa/income/mn_msa_countable_income.yaml | 115 +++++ .../gov/states/mn/dhs/msa/integration.yaml | 475 ++++++++++++++++++ .../mn/dhs/msa/mn_msa_living_arrangement.yaml | 230 +++++++++ .../gov/states/mn/dhs/msa/mn_msa_person.yaml | 258 ++++++++++ .../payment/mn_msa_assistance_standard.yaml | 226 +++++++++ .../dhs/msa/payment/mn_msa_guardian_fee.yaml | 179 +++++++ .../payment/mn_msa_housing_assistance.yaml | 56 +++ .../mn_msa_representative_payee_fee.yaml | 55 ++ .../mn_msa_shelter_need_allowance.yaml | 56 +++ .../mn_msa_categorically_eligible.py | 25 + .../msa/eligibility/mn_msa_eligible_person.py | 31 ++ .../mn_msa_gross_income_eligible.py | 34 ++ .../eligibility/mn_msa_net_income_eligible.py | 21 + .../eligibility/mn_msa_resource_eligible.py | 24 + .../income/mn_msa_countable_earned_income.py | 26 + .../dhs/msa/income/mn_msa_countable_income.py | 19 + .../mn_msa_countable_unearned_income.py | 30 ++ .../income/mn_msa_earned_income_disregard.py | 33 ++ .../mn/dhs/msa/income/mn_msa_gross_income.py | 31 ++ .../variables/gov/states/mn/dhs/msa/mn_msa.py | 13 + .../states/mn/dhs/msa/mn_msa_has_guardian.py | 13 + .../msa/mn_msa_housing_assistance_eligible.py | 15 + .../mn/dhs/msa/mn_msa_in_group_residential.py | 13 + .../mn/dhs/msa/mn_msa_lives_with_others.py | 13 + .../mn/dhs/msa/mn_msa_living_arrangement.py | 80 +++ .../gov/states/mn/dhs/msa/mn_msa_person.py | 43 ++ .../dhs/msa/mn_msa_shelter_need_eligible.py | 13 + .../dhs/msa/mn_msa_treated_as_living_alone.py | 15 + .../msa/mn_msa_uses_representative_payee.py | 13 + .../msa/payment/mn_msa_assistance_standard.py | 19 + .../mn/dhs/msa/payment/mn_msa_guardian_fee.py | 23 + .../msa/payment/mn_msa_housing_assistance.py | 25 + .../mn_msa_representative_payee_fee.py | 24 + .../payment/mn_msa_shelter_need_allowance.py | 23 + .../msa/payment/mn_msa_special_needs_total.py | 21 + .../income/spm_unit/spm_unit_benefits.py | 1 + 55 files changed, 3428 insertions(+) create mode 100644 policyengine_us/parameters/gov/states/mn/dhs/msa/assistance_standard/amount.yaml create mode 100644 policyengine_us/parameters/gov/states/mn/dhs/msa/disregard/earned/initial.yaml create mode 100644 policyengine_us/parameters/gov/states/mn/dhs/msa/disregard/earned/rate.yaml create mode 100644 policyengine_us/parameters/gov/states/mn/dhs/msa/eligibility/asset_limit/non_ssi_track.yaml create mode 100644 policyengine_us/parameters/gov/states/mn/dhs/msa/eligibility/income_limit/couple_fbr_multiplier.yaml create mode 100644 policyengine_us/parameters/gov/states/mn/dhs/msa/eligibility/income_limit/individual_fbr_multiplier.yaml create mode 100644 policyengine_us/parameters/gov/states/mn/dhs/msa/housing_assistance/fbr_multiplier.yaml create mode 100644 policyengine_us/parameters/gov/states/mn/dhs/msa/special_needs/guardian_fee/max_amount.yaml create mode 100644 policyengine_us/parameters/gov/states/mn/dhs/msa/special_needs/guardian_fee/rate.yaml create mode 100644 policyengine_us/parameters/gov/states/mn/dhs/msa/special_needs/representative_payee_fee/amount.yaml create mode 100644 policyengine_us/parameters/gov/states/mn/dhs/msa/special_needs/shelter_need/fbr_multiplier.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/eligibility/mn_msa_categorically_eligible.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/eligibility/mn_msa_eligible_person.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/eligibility/mn_msa_gross_income_eligible.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/eligibility/mn_msa_net_income_eligible.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/eligibility/mn_msa_resource_eligible.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/income/mn_msa_countable_earned_income.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/income/mn_msa_countable_income.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/integration.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/mn_msa_living_arrangement.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/mn_msa_person.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/payment/mn_msa_assistance_standard.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/payment/mn_msa_guardian_fee.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/payment/mn_msa_housing_assistance.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/payment/mn_msa_representative_payee_fee.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/payment/mn_msa_shelter_need_allowance.yaml create mode 100644 policyengine_us/variables/gov/states/mn/dhs/msa/eligibility/mn_msa_categorically_eligible.py create mode 100644 policyengine_us/variables/gov/states/mn/dhs/msa/eligibility/mn_msa_eligible_person.py create mode 100644 policyengine_us/variables/gov/states/mn/dhs/msa/eligibility/mn_msa_gross_income_eligible.py create mode 100644 policyengine_us/variables/gov/states/mn/dhs/msa/eligibility/mn_msa_net_income_eligible.py create mode 100644 policyengine_us/variables/gov/states/mn/dhs/msa/eligibility/mn_msa_resource_eligible.py create mode 100644 policyengine_us/variables/gov/states/mn/dhs/msa/income/mn_msa_countable_earned_income.py create mode 100644 policyengine_us/variables/gov/states/mn/dhs/msa/income/mn_msa_countable_income.py create mode 100644 policyengine_us/variables/gov/states/mn/dhs/msa/income/mn_msa_countable_unearned_income.py create mode 100644 policyengine_us/variables/gov/states/mn/dhs/msa/income/mn_msa_earned_income_disregard.py create mode 100644 policyengine_us/variables/gov/states/mn/dhs/msa/income/mn_msa_gross_income.py create mode 100644 policyengine_us/variables/gov/states/mn/dhs/msa/mn_msa.py create mode 100644 policyengine_us/variables/gov/states/mn/dhs/msa/mn_msa_has_guardian.py create mode 100644 policyengine_us/variables/gov/states/mn/dhs/msa/mn_msa_housing_assistance_eligible.py create mode 100644 policyengine_us/variables/gov/states/mn/dhs/msa/mn_msa_in_group_residential.py create mode 100644 policyengine_us/variables/gov/states/mn/dhs/msa/mn_msa_lives_with_others.py create mode 100644 policyengine_us/variables/gov/states/mn/dhs/msa/mn_msa_living_arrangement.py create mode 100644 policyengine_us/variables/gov/states/mn/dhs/msa/mn_msa_person.py create mode 100644 policyengine_us/variables/gov/states/mn/dhs/msa/mn_msa_shelter_need_eligible.py create mode 100644 policyengine_us/variables/gov/states/mn/dhs/msa/mn_msa_treated_as_living_alone.py create mode 100644 policyengine_us/variables/gov/states/mn/dhs/msa/mn_msa_uses_representative_payee.py create mode 100644 policyengine_us/variables/gov/states/mn/dhs/msa/payment/mn_msa_assistance_standard.py create mode 100644 policyengine_us/variables/gov/states/mn/dhs/msa/payment/mn_msa_guardian_fee.py create mode 100644 policyengine_us/variables/gov/states/mn/dhs/msa/payment/mn_msa_housing_assistance.py create mode 100644 policyengine_us/variables/gov/states/mn/dhs/msa/payment/mn_msa_representative_payee_fee.py create mode 100644 policyengine_us/variables/gov/states/mn/dhs/msa/payment/mn_msa_shelter_need_allowance.py create mode 100644 policyengine_us/variables/gov/states/mn/dhs/msa/payment/mn_msa_special_needs_total.py diff --git a/policyengine_us/parameters/gov/household/household_state_benefits.yaml b/policyengine_us/parameters/gov/household/household_state_benefits.yaml index 6a12bf7a1bb..4479db89f2e 100644 --- a/policyengine_us/parameters/gov/household/household_state_benefits.yaml +++ b/policyengine_us/parameters/gov/household/household_state_benefits.yaml @@ -14,6 +14,8 @@ values: - ma_state_supplement # Michigan benefits - mi_ssp + # Minnesota benefits + - mn_msa # Colorado benefits - co_state_supplement - co_oap @@ -76,6 +78,8 @@ values: - ma_state_supplement # Michigan benefits - mi_ssp + # Minnesota benefits + - mn_msa # Colorado benefits - co_state_supplement - co_oap diff --git a/policyengine_us/parameters/gov/states/mn/dhs/msa/assistance_standard/amount.yaml b/policyengine_us/parameters/gov/states/mn/dhs/msa/assistance_standard/amount.yaml new file mode 100644 index 00000000000..a891f64a7f9 --- /dev/null +++ b/policyengine_us/parameters/gov/states/mn/dhs/msa/assistance_standard/amount.yaml @@ -0,0 +1,47 @@ +description: Minnesota provides this combined federal-plus-state assistance standard by living arrangement under the Minnesota Supplemental Aid program. + +metadata: + unit: currency-USD + period: month + label: Minnesota Supplemental Aid assistance standard amount + breakdown: + - mn_msa_living_arrangement + reference: + - title: Minn. Stat. § 256D.44 Subd. 2 (Standards of assistance for basic needs) + href: https://www.revisor.mn.gov/statutes/cite/256D.44 + - title: Minn. Stat. § 256D.44 Subd. 3 (Standards of assistance for shelter needs) + href: https://www.revisor.mn.gov/statutes/cite/256D.44 + - title: MN DHS Combined Manual 0020.21 - MSA Assistance Standards (01/2026) + href: https://www.dhs.state.mn.us/main/groups/county_access/documents/pub/mndhs-073585.pdf#page=4 + - title: MN House Research Department - Minnesota Supplemental Aid (October 2024), MSA Assistance Standards table + href: https://www.house.mn.gov/hrd/pubs/pap_MSA.pdf#page=6 + - title: SSA State Assistance Programs for SSI Recipients, January 2011 - Minnesota, Table 1 + href: https://www.ssa.gov/policy/docs/progdesc/ssi_st_asst/2011/mn.html + +INDIVIDUAL_LIVING_ALONE: + 2011-01-01: 735 + 2024-01-01: 1_004 + 2026-01-01: 1_055 + +INDIVIDUAL_LIVING_WITH_OTHERS: + 2011-01-01: 542 + 2024-01-01: 721 + 2026-01-01: 755.33 + +COUPLE_LIVING_ALONE: + 2011-01-01: 1_102 + 2024-01-01: 1_506 + 2026-01-01: 1_582 + +COUPLE_LIVING_WITH_OTHERS: + 2011-01-01: 738 + 2024-01-01: 1_007 + 2026-01-01: 1_058 + +MEDICAID_FACILITY: + 2011-01-01: 89 + 2024-01-01: 155 + 2026-01-01: 162 + +NONE: + 2011-01-01: 0 diff --git a/policyengine_us/parameters/gov/states/mn/dhs/msa/disregard/earned/initial.yaml b/policyengine_us/parameters/gov/states/mn/dhs/msa/disregard/earned/initial.yaml new file mode 100644 index 00000000000..af06ed0c9f1 --- /dev/null +++ b/policyengine_us/parameters/gov/states/mn/dhs/msa/disregard/earned/initial.yaml @@ -0,0 +1,14 @@ +description: Minnesota disregards this initial amount of earned income for non-SSI recipients under the Minnesota Supplemental Aid program. + +values: + 1989-01-01: 65 + +metadata: + unit: currency-USD + period: month + label: Minnesota Supplemental Aid earned income initial disregard + reference: + - title: MN DHS Combined Manual 0018.18 - Earned Income Disregards (01/2026) + href: https://www.dhs.state.mn.us/main/groups/county_access/documents/pub/mndhs-073585.pdf#page=2 + - title: Minn. Stat. § 256D.44 Subd. 1 (Eligibility) + href: https://www.revisor.mn.gov/statutes/cite/256D.44 diff --git a/policyengine_us/parameters/gov/states/mn/dhs/msa/disregard/earned/rate.yaml b/policyengine_us/parameters/gov/states/mn/dhs/msa/disregard/earned/rate.yaml new file mode 100644 index 00000000000..668c4ff76b5 --- /dev/null +++ b/policyengine_us/parameters/gov/states/mn/dhs/msa/disregard/earned/rate.yaml @@ -0,0 +1,14 @@ +description: Minnesota disregards this share of remaining earned income for non-SSI recipients under the Minnesota Supplemental Aid program. + +values: + 1989-01-01: 0.5 + +metadata: + unit: /1 + period: month + label: Minnesota Supplemental Aid earned income disregard rate + reference: + - title: MN DHS Combined Manual 0018.18 - Earned Income Disregards (01/2026) + href: https://www.dhs.state.mn.us/main/groups/county_access/documents/pub/mndhs-073585.pdf#page=2 + - title: Minn. Stat. § 256D.44 Subd. 1 (Eligibility) + href: https://www.revisor.mn.gov/statutes/cite/256D.44 diff --git a/policyengine_us/parameters/gov/states/mn/dhs/msa/eligibility/asset_limit/non_ssi_track.yaml b/policyengine_us/parameters/gov/states/mn/dhs/msa/eligibility/asset_limit/non_ssi_track.yaml new file mode 100644 index 00000000000..60037161726 --- /dev/null +++ b/policyengine_us/parameters/gov/states/mn/dhs/msa/eligibility/asset_limit/non_ssi_track.yaml @@ -0,0 +1,16 @@ +description: Minnesota limits countable resources to this amount for non-SSI excess-income recipients under the Minnesota Supplemental Aid program. + +values: + 1989-01-01: 10_000 + +metadata: + unit: currency-USD + period: month + label: Minnesota Supplemental Aid non-SSI track asset limit + reference: + - title: Minn. Stat. § 256D.37 (Resource limits) + href: https://www.revisor.mn.gov/statutes/cite/256D.37 + - title: LawHelpMN - Minnesota Supplemental Aid Fact Sheet + href: https://www.lawhelpmn.org/self-help-library/fact-sheet/minnesota-supplemental-aid-msa + - title: MN House Research Department - Minnesota Supplemental Aid (October 2024) + href: https://www.house.mn.gov/hrd/pubs/pap_MSA.pdf#page=2 diff --git a/policyengine_us/parameters/gov/states/mn/dhs/msa/eligibility/income_limit/couple_fbr_multiplier.yaml b/policyengine_us/parameters/gov/states/mn/dhs/msa/eligibility/income_limit/couple_fbr_multiplier.yaml new file mode 100644 index 00000000000..e3729330356 --- /dev/null +++ b/policyengine_us/parameters/gov/states/mn/dhs/msa/eligibility/income_limit/couple_fbr_multiplier.yaml @@ -0,0 +1,14 @@ +description: Minnesota limits gross income to this multiple of the federal Supplemental Security Income individual benefit rate for a couple under the Minnesota Supplemental Aid program. + +values: + 1989-01-01: 6 + +metadata: + unit: /1 + period: month + label: Minnesota Supplemental Aid couple gross income FBR multiplier + reference: + - title: Minn. Stat. § 256D.44 Subd. 1 (Eligibility) + href: https://www.revisor.mn.gov/statutes/cite/256D.44 + - title: MN House Research Department - Minnesota Supplemental Aid (October 2024) + href: https://www.house.mn.gov/hrd/pubs/pap_MSA.pdf#page=2 diff --git a/policyengine_us/parameters/gov/states/mn/dhs/msa/eligibility/income_limit/individual_fbr_multiplier.yaml b/policyengine_us/parameters/gov/states/mn/dhs/msa/eligibility/income_limit/individual_fbr_multiplier.yaml new file mode 100644 index 00000000000..5f9879c5704 --- /dev/null +++ b/policyengine_us/parameters/gov/states/mn/dhs/msa/eligibility/income_limit/individual_fbr_multiplier.yaml @@ -0,0 +1,14 @@ +description: Minnesota limits gross income to this multiple of the federal Supplemental Security Income individual benefit rate for an individual under the Minnesota Supplemental Aid program. + +values: + 1989-01-01: 3 + +metadata: + unit: /1 + period: month + label: Minnesota Supplemental Aid individual gross income FBR multiplier + reference: + - title: Minn. Stat. § 256D.44 Subd. 1 (Eligibility) + href: https://www.revisor.mn.gov/statutes/cite/256D.44 + - title: MN House Research Department - Minnesota Supplemental Aid (October 2024) + href: https://www.house.mn.gov/hrd/pubs/pap_MSA.pdf#page=2 diff --git a/policyengine_us/parameters/gov/states/mn/dhs/msa/housing_assistance/fbr_multiplier.yaml b/policyengine_us/parameters/gov/states/mn/dhs/msa/housing_assistance/fbr_multiplier.yaml new file mode 100644 index 00000000000..3b878324c8e --- /dev/null +++ b/policyengine_us/parameters/gov/states/mn/dhs/msa/housing_assistance/fbr_multiplier.yaml @@ -0,0 +1,14 @@ +description: Minnesota provides housing assistance at this multiple of the federal Supplemental Security Income individual benefit rate under the Minnesota Supplemental Aid program. + +values: + 1989-01-01: 0.5 + +metadata: + unit: /1 + period: month + label: Minnesota Supplemental Aid housing assistance FBR multiplier + reference: + - title: Minn. Stat. § 256D.44 Subd. 5 (Special needs) + href: https://www.revisor.mn.gov/statutes/cite/256D.44 + - title: MN DHS Combined Manual 0023.24 - MSA Housing Assistance + href: https://www.dhs.state.mn.us/main/idcplg?IdcService=GET_DYNAMIC_CONVERSION&dDocName=cm_002324&RevisionSelectionMethod=LatestReleased diff --git a/policyengine_us/parameters/gov/states/mn/dhs/msa/special_needs/guardian_fee/max_amount.yaml b/policyengine_us/parameters/gov/states/mn/dhs/msa/special_needs/guardian_fee/max_amount.yaml new file mode 100644 index 00000000000..c536fc0b20d --- /dev/null +++ b/policyengine_us/parameters/gov/states/mn/dhs/msa/special_needs/guardian_fee/max_amount.yaml @@ -0,0 +1,14 @@ +description: Minnesota caps the guardian or conservator fee special-needs allowance at this amount under the Minnesota Supplemental Aid program. + +values: + 1989-01-01: 100 + +metadata: + unit: currency-USD + period: month + label: Minnesota Supplemental Aid guardian fee maximum amount + reference: + - title: Minn. Stat. § 256D.44 Subd. 5 (Special needs) + href: https://www.revisor.mn.gov/statutes/cite/256D.44 + - title: SSA State Assistance Programs for SSI Recipients, January 2011 - Minnesota, Section 9.5 + href: https://www.ssa.gov/policy/docs/progdesc/ssi_st_asst/2011/mn.html diff --git a/policyengine_us/parameters/gov/states/mn/dhs/msa/special_needs/guardian_fee/rate.yaml b/policyengine_us/parameters/gov/states/mn/dhs/msa/special_needs/guardian_fee/rate.yaml new file mode 100644 index 00000000000..5c78cc5b2c4 --- /dev/null +++ b/policyengine_us/parameters/gov/states/mn/dhs/msa/special_needs/guardian_fee/rate.yaml @@ -0,0 +1,14 @@ +description: Minnesota provides this share of gross monthly income as a guardian or conservator fee special-needs allowance under the Minnesota Supplemental Aid program. + +values: + 1989-01-01: 0.05 + +metadata: + unit: /1 + period: month + label: Minnesota Supplemental Aid guardian fee rate + reference: + - title: Minn. Stat. § 256D.44 Subd. 5 (Special needs) + href: https://www.revisor.mn.gov/statutes/cite/256D.44 + - title: SSA State Assistance Programs for SSI Recipients, January 2011 - Minnesota, Section 9.5 + href: https://www.ssa.gov/policy/docs/progdesc/ssi_st_asst/2011/mn.html diff --git a/policyengine_us/parameters/gov/states/mn/dhs/msa/special_needs/representative_payee_fee/amount.yaml b/policyengine_us/parameters/gov/states/mn/dhs/msa/special_needs/representative_payee_fee/amount.yaml new file mode 100644 index 00000000000..e34eb840e79 --- /dev/null +++ b/policyengine_us/parameters/gov/states/mn/dhs/msa/special_needs/representative_payee_fee/amount.yaml @@ -0,0 +1,14 @@ +description: Minnesota provides this amount as the representative payee service special-needs allowance under the Minnesota Supplemental Aid program. + +values: + 2026-01-01: 57 + +metadata: + unit: currency-USD + period: month + label: Minnesota Supplemental Aid representative payee fee amount + reference: + - title: MN DHS Combined Manual 0023.21 - Representative Payee Services (01/2026) + href: https://www.dhs.state.mn.us/main/groups/county_access/documents/pub/mndhs-073585.pdf#page=6 + - title: Minn. Stat. § 256D.44 Subd. 5 (Special needs) + href: https://www.revisor.mn.gov/statutes/cite/256D.44 diff --git a/policyengine_us/parameters/gov/states/mn/dhs/msa/special_needs/shelter_need/fbr_multiplier.yaml b/policyengine_us/parameters/gov/states/mn/dhs/msa/special_needs/shelter_need/fbr_multiplier.yaml new file mode 100644 index 00000000000..ef4e84a25af --- /dev/null +++ b/policyengine_us/parameters/gov/states/mn/dhs/msa/special_needs/shelter_need/fbr_multiplier.yaml @@ -0,0 +1,14 @@ +description: Minnesota provides this multiple of the federal Supplemental Security Income individual benefit rate as the shelter need special-needs allowance under the Minnesota Supplemental Aid program. + +values: + 1989-01-01: 0.5 + +metadata: + unit: /1 + period: month + label: Minnesota Supplemental Aid shelter need FBR multiplier + reference: + - title: Minn. Stat. § 256D.44 Subd. 5(g) (Shelter needs special allowance) + href: https://www.revisor.mn.gov/statutes/cite/256D.44 + - title: SSA State Assistance Programs for SSI Recipients, January 2011 - Minnesota, Section 9.5 + href: https://www.ssa.gov/policy/docs/progdesc/ssi_st_asst/2011/mn.html diff --git a/policyengine_us/programs.yaml b/policyengine_us/programs.yaml index 67ad00323c9..2d19e3760ee 100644 --- a/policyengine_us/programs.yaml +++ b/policyengine_us/programs.yaml @@ -657,6 +657,12 @@ programs: full_name: Michigan State Supplementary Payment variable: mi_ssp parameter_prefix: gov.states.mi.mdhhs.ssp + - state: MN + status: complete + name: Minnesota MSA + full_name: Minnesota Supplemental Aid + variable: mn_msa + parameter_prefix: gov.states.mn.dhs.msa - state: KS status: complete name: Kansas SSPP diff --git a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/eligibility/mn_msa_categorically_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/eligibility/mn_msa_categorically_eligible.yaml new file mode 100644 index 00000000000..b97268d7c6f --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/eligibility/mn_msa_categorically_eligible.yaml @@ -0,0 +1,103 @@ +- name: Case 1, aged person is categorically eligible. + period: 2026-01 + input: + people: + person1: + age: 70 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 1_000 + marital_units: + marital_unit: + members: [person1] + tax_units: + tax_unit: + members: [person1] + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MN + output: + mn_msa_categorically_eligible: true + +- name: Case 2, blind child is categorically eligible. + period: 2026-01 + input: + people: + person1: + age: 10 + is_blind: true + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 500 + marital_units: + marital_unit: + members: [person1] + tax_units: + tax_unit: + members: [person1] + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MN + output: + mn_msa_categorically_eligible: true + +- name: Case 3, disabled adult is categorically eligible. + period: 2026-01 + input: + people: + person1: + age: 40 + is_blind: false + is_disabled: true + immigration_status: CITIZEN + ssi_countable_resources: 500 + marital_units: + marital_unit: + members: [person1] + tax_units: + tax_unit: + members: [person1] + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MN + output: + mn_msa_categorically_eligible: true + +- name: Case 4, non-aged non-blind non-disabled adult is not categorically eligible. + period: 2026-01 + input: + people: + person1: + age: 40 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 500 + marital_units: + marital_unit: + members: [person1] + tax_units: + tax_unit: + members: [person1] + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MN + output: + mn_msa_categorically_eligible: false diff --git a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/eligibility/mn_msa_eligible_person.yaml b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/eligibility/mn_msa_eligible_person.yaml new file mode 100644 index 00000000000..4d4959cf3f1 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/eligibility/mn_msa_eligible_person.yaml @@ -0,0 +1,121 @@ +- name: Case 1, aged SSI recipient is eligible (SSI-receipt path). + period: 2026-01 + input: + people: + person1: + age: 70 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 1_000 + mn_msa_lives_with_others: false + mn_msa_treated_as_living_alone: false + ssi: 11_928 # $994/mo + employment_income: 0 + marital_units: + marital_unit: + members: [person1] + tax_units: + tax_unit: + members: [person1] + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MN + output: + mn_msa_eligible_person: true + +- name: Case 2, SSI-eligible-except-for-income at MSA standard is eligible (non-SSI path). + period: 2026-01 + input: + people: + person1: + age: 70 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 5_000 # below non-SSI $10k cap, above SSI $2k cap + mn_msa_lives_with_others: false + mn_msa_treated_as_living_alone: false + ssi: 0 + ssi_unearned_income: 12_000 # $1,000/mo unearned (above SSI FBR but below MSA standard) + marital_units: + marital_unit: + members: [person1] + tax_units: + tax_unit: + members: [person1] + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MN + output: + # Income $1,000 < $1,055 individual living alone standard. + # Gross < 300% FBR ($2,982). + mn_msa_eligible_person: true + +- name: Case 3, non-aged non-blind non-disabled adult is ineligible. + period: 2026-01 + input: + people: + person1: + age: 40 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 500 + mn_msa_lives_with_others: false + mn_msa_treated_as_living_alone: false + ssi: 0 + marital_units: + marital_unit: + members: [person1] + tax_units: + tax_unit: + members: [person1] + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MN + output: + mn_msa_eligible_person: false + +- name: Case 4, income above MSA standard is ineligible. + period: 2026-01 + input: + people: + person1: + age: 70 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 500 + mn_msa_lives_with_others: false + mn_msa_treated_as_living_alone: false + ssi: 0 + ssi_unearned_income: 24_000 # $2,000/mo > $1,055 standard + marital_units: + marital_unit: + members: [person1] + tax_units: + tax_unit: + members: [person1] + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MN + output: + # Countable $2,000 > $1,055 standard -> not income eligible. + mn_msa_eligible_person: false diff --git a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/eligibility/mn_msa_gross_income_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/eligibility/mn_msa_gross_income_eligible.yaml new file mode 100644 index 00000000000..6304b43331a --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/eligibility/mn_msa_gross_income_eligible.yaml @@ -0,0 +1,221 @@ +- name: Case 1, individual at exactly 300% of SSI FBR is gross-income eligible. + period: 2026-01 + input: + people: + person1: + age: 70 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 500 + employment_income: 35_784 # $2,982/mo = 3 x $994 + marital_units: + marital_unit: + members: [person1] + tax_units: + tax_unit: + members: [person1] + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MN + output: + # Cap = 3 x $994 = $2,982 -> $2,982 <= $2,982 passes. + mn_msa_gross_income_eligible: true + +- name: Case 2, couple at exactly 600% of SSI FBR is gross-income eligible. + period: 2026-01 + input: + people: + person1: + age: 70 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 800 + ssi_claim_is_joint: true + employment_income: 35_784 # $2,982/mo + person2: + age: 68 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 800 + ssi_claim_is_joint: true + employment_income: 35_784 # $2,982/mo -> couple total $5,964 = 6 x $994 + 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: MN + output: + # Couple cap = 6 x $994 = $5,964 -> $5,964 <= $5,964 passes. + mn_msa_gross_income_eligible: [true, true] + +- name: Case 3, individual above 300% of SSI FBR is not gross-income eligible. + period: 2026-01 + input: + people: + person1: + age: 70 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 500 + employment_income: 36_000 # $3,000/mo > $2,982 cap + marital_units: + marital_unit: + members: [person1] + tax_units: + tax_unit: + members: [person1] + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MN + output: + mn_msa_gross_income_eligible: false + +- name: Case 4, individual at $1 below 300% of SSI FBR is gross-income eligible. + period: 2026-01 + input: + people: + person1: + age: 70 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 500 + ssi_unearned_income: 35_772 # $2,981/mo, $1 below the $2,982 cap + marital_units: + marital_unit: + members: [person1] + tax_units: + tax_unit: + members: [person1] + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MN + output: + # $2,981 < $2,982 cap -> eligible. + mn_msa_gross_income_eligible: true + +- name: Case 5, individual at $1 above 300% of SSI FBR is not gross-income eligible. + period: 2026-01 + input: + people: + person1: + age: 70 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 500 + ssi_unearned_income: 35_796 # $2,983/mo, $1 above the $2,982 cap + marital_units: + marital_unit: + members: [person1] + tax_units: + tax_unit: + members: [person1] + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MN + output: + # $2,983 > $2,982 cap -> not eligible. + mn_msa_gross_income_eligible: false + +- name: Case 6, couple at $2 below 600% of SSI FBR is gross-income eligible. + period: 2026-01 + input: + people: + person1: + age: 70 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 800 + ssi_claim_is_joint: true + ssi_unearned_income: 35_772 # $2,981/mo + person2: + age: 68 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 800 + ssi_claim_is_joint: true + ssi_unearned_income: 35_772 # $2,981/mo -> couple total $5,962 + 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: MN + output: + # Couple gross $5,962 < $5,964 cap -> eligible. + mn_msa_gross_income_eligible: [true, true] + +- name: Case 7, couple at $2 above 600% of SSI FBR is not gross-income eligible. + period: 2026-01 + input: + people: + person1: + age: 70 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 800 + ssi_claim_is_joint: true + ssi_unearned_income: 35_796 # $2,983/mo + person2: + age: 68 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 800 + ssi_claim_is_joint: true + ssi_unearned_income: 35_796 # $2,983/mo -> couple total $5,966 + 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: MN + output: + # Couple gross $5,966 > $5,964 cap -> not eligible. + mn_msa_gross_income_eligible: [false, false] diff --git a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/eligibility/mn_msa_net_income_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/eligibility/mn_msa_net_income_eligible.yaml new file mode 100644 index 00000000000..731f886ea1d --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/eligibility/mn_msa_net_income_eligible.yaml @@ -0,0 +1,92 @@ +- name: Case 1, countable income exactly at MSA standard is net-income eligible. + period: 2026-01 + input: + people: + person1: + age: 70 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 5_000 + mn_msa_lives_with_others: false + mn_msa_treated_as_living_alone: false + ssi: 0 + ssi_unearned_income: 12_660 # $1,055/mo, exactly at INDIVIDUAL_LIVING_ALONE standard + marital_units: + marital_unit: + members: [person1] + tax_units: + tax_unit: + members: [person1] + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MN + output: + # Countable $1,055 <= $1,055 standard -> eligible. + mn_msa_net_income_eligible: true + +- name: Case 2, countable income $1 above MSA standard is not net-income eligible. + period: 2026-01 + input: + people: + person1: + age: 70 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 5_000 + mn_msa_lives_with_others: false + mn_msa_treated_as_living_alone: false + ssi: 0 + ssi_unearned_income: 12_672 # $1,056/mo, $1 above the $1,055 standard + marital_units: + marital_unit: + members: [person1] + tax_units: + tax_unit: + members: [person1] + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MN + output: + # Countable $1,056 > $1,055 standard -> not eligible. + mn_msa_net_income_eligible: false + +- name: Case 3, countable income $1 below MSA standard is net-income eligible. + period: 2026-01 + input: + people: + person1: + age: 70 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 5_000 + mn_msa_lives_with_others: false + mn_msa_treated_as_living_alone: false + ssi: 0 + ssi_unearned_income: 12_648 # $1,054/mo + marital_units: + marital_unit: + members: [person1] + tax_units: + tax_unit: + members: [person1] + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MN + output: + # Countable $1,054 <= $1,055 standard -> eligible. + mn_msa_net_income_eligible: true diff --git a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/eligibility/mn_msa_resource_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/eligibility/mn_msa_resource_eligible.yaml new file mode 100644 index 00000000000..03f723c0538 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/eligibility/mn_msa_resource_eligible.yaml @@ -0,0 +1,231 @@ +- name: Case 1, SSI recipient with low resources is resource eligible. + period: 2026-01 + input: + people: + person1: + age: 70 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 1_500 + ssi: 11_928 + marital_units: + marital_unit: + members: [person1] + tax_units: + tax_unit: + members: [person1] + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MN + output: + # SSI track: federal $2,000 limit already enforced via is_ssi_eligible. + mn_msa_resource_eligible: true + +- name: Case 2, non-SSI track at $10,000 boundary is resource eligible. + period: 2026-01 + input: + people: + person1: + age: 70 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 10_000 + ssi: 0 + marital_units: + marital_unit: + members: [person1] + tax_units: + tax_unit: + members: [person1] + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MN + output: + # Non-SSI track: $10,000 cap, exact boundary passes. + mn_msa_resource_eligible: true + +- name: Case 3, non-SSI track above $10,000 is not resource eligible. + period: 2026-01 + input: + people: + person1: + age: 70 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 10_500 + ssi: 0 + marital_units: + marital_unit: + members: [person1] + tax_units: + tax_unit: + members: [person1] + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MN + output: + mn_msa_resource_eligible: false + +- name: Case 4, non-SSI track at $9,999 is resource eligible. + period: 2026-01 + input: + people: + person1: + age: 70 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 9_999 + ssi: 0 + marital_units: + marital_unit: + members: [person1] + tax_units: + tax_unit: + members: [person1] + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MN + output: + # Non-SSI track: $9,999 <= $10,000 cap -> eligible. + mn_msa_resource_eligible: true + +- name: Case 5, non-SSI track at $10,001 is not resource eligible. + period: 2026-01 + input: + people: + person1: + age: 70 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 10_001 + ssi: 0 + marital_units: + marital_unit: + members: [person1] + tax_units: + tax_unit: + members: [person1] + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MN + output: + # Non-SSI track: $10,001 > $10,000 cap -> not eligible. + mn_msa_resource_eligible: false + +- name: Case 6, single SSI recipient with resources just below SSI $2,000 cap. + period: 2026-01 + input: + people: + person1: + age: 70 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 1_999 + ssi: 11_928 + marital_units: + marital_unit: + members: [person1] + tax_units: + tax_unit: + members: [person1] + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MN + output: + # SSI track: $1,999 < $2,000 federal cap, is_ssi_eligible passes. + mn_msa_resource_eligible: true + +- name: Case 7, SSI couple with combined resources just below $3,000 cap. + period: 2026-01 + input: + people: + person1: + age: 70 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 1_499 + ssi_claim_is_joint: true + ssi: 8_946 + person2: + age: 68 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 1_499 + ssi_claim_is_joint: true + ssi: 8_946 + 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: MN + output: + # SSI track for both: federal $3,000 couple cap enforced via is_ssi_eligible. + mn_msa_resource_eligible: [true, true] + +- name: Case 8, person with high resources but ssi=0 falls back to non-SSI track. + period: 2026-01 + input: + people: + person1: + age: 70 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 9_500 # > $2k SSI cap, < $10k non-SSI cap + ssi: 0 + marital_units: + marital_unit: + members: [person1] + tax_units: + tax_unit: + members: [person1] + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MN + output: + # ssi=0 -> non-SSI track applies; $9,500 <= $10,000 -> eligible. + mn_msa_resource_eligible: true diff --git a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/income/mn_msa_countable_earned_income.yaml b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/income/mn_msa_countable_earned_income.yaml new file mode 100644 index 00000000000..fcfcb69d79c --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/income/mn_msa_countable_earned_income.yaml @@ -0,0 +1,170 @@ +- name: Case 1, earnings below the $65 initial disregard yield zero countable earned. + period: 2026-01 + input: + people: + person1: + age: 70 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 500 + ssi_earned_income: 600 # $50/mo (annual = $50 * 12) + marital_units: + marital_unit: + members: [person1] + tax_units: + tax_unit: + members: [person1] + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MN + output: + # Earned $50, after $65 disregard = max(0, 50 - 65) = $0; no remainder to halve. + mn_msa_countable_earned_income: 0 + +- name: Case 2, earnings above $65 disregard apply $65 plus half remainder. + period: 2026-01 + input: + people: + person1: + age: 70 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 500 + ssi_earned_income: 3_000 # $250/mo + marital_units: + marital_unit: + members: [person1] + tax_units: + tax_unit: + members: [person1] + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MN + output: + # Earned $250, less $65 = $185, halved = $92.50 countable. + mn_msa_countable_earned_income: 92.50 + +- name: Case 3, earnings exactly at $65 boundary yield zero countable. + period: 2026-01 + input: + people: + person1: + age: 70 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 500 + ssi_earned_income: 780 # $65/mo + marital_units: + marital_unit: + members: [person1] + tax_units: + tax_unit: + members: [person1] + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MN + output: + # Earned $65 - $65 = $0, half of $0 = $0. + mn_msa_countable_earned_income: 0 + +- name: Case 4, zero earnings yield zero countable. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 70 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 500 + ssi_earned_income: 0 + marital_units: + marital_unit: + members: [person1] + tax_units: + tax_unit: + members: [person1] + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MN + output: + # No earnings -> no countable earned income. + mn_msa_countable_earned_income: 0 + +- name: Case 5, earnings $1 above the $65 disregard yield half-of-remainder countable. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 70 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 500 + ssi_earned_income: 792 # $66/mo, $1 above the $65 disregard + marital_units: + marital_unit: + members: [person1] + tax_units: + tax_unit: + members: [person1] + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MN + output: + # Earned $66 - $65 = $1; half = $0.50 countable. + mn_msa_countable_earned_income: 0.50 + +- name: Case 6, large earnings apply $65 plus half remainder. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 70 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 500 + ssi_earned_income: 12_000 # $1,000/mo + marital_units: + marital_unit: + members: [person1] + tax_units: + tax_unit: + members: [person1] + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MN + output: + # Earned $1,000 - $65 = $935; halved = $467.50 countable. + mn_msa_countable_earned_income: 467.50 diff --git a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/income/mn_msa_countable_income.yaml b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/income/mn_msa_countable_income.yaml new file mode 100644 index 00000000000..2349a411b2d --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/income/mn_msa_countable_income.yaml @@ -0,0 +1,115 @@ +- name: Case 1, unearned income only adds directly to countable. + period: 2026-01 + input: + people: + person1: + age: 70 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 500 + ssi_unearned_income: 7_200 # $600/mo + ssi_earned_income: 0 + marital_units: + marital_unit: + members: [person1] + tax_units: + tax_unit: + members: [person1] + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MN + output: + mn_msa_countable_income: 600 + +- name: Case 2, earned plus unearned income post-disregard. + period: 2026-01 + input: + people: + person1: + age: 70 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 500 + ssi_unearned_income: 2_400 # $200/mo + ssi_earned_income: 3_000 # $250/mo + marital_units: + marital_unit: + members: [person1] + tax_units: + tax_unit: + members: [person1] + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MN + output: + # Unearned $200 + countable earned (250 - 65)/2 = $92.50 -> $292.50 total. + mn_msa_countable_income: 292.50 + +- name: Case 3, negative net earnings floor at zero do not subtract from unearned. + period: 2026-01 + input: + people: + person1: + age: 70 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 500 + ssi_unearned_income: 6_000 # $500/mo + ssi_earned_income: 600 # $50/mo (below $65 disregard) + marital_units: + marital_unit: + members: [person1] + tax_units: + tax_unit: + members: [person1] + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MN + output: + # Unearned $500 + earned floored at $0 = $500 (no negative spillover). + mn_msa_countable_income: 500 + +- name: Case 4, extreme negative self-employment does not produce negative countable. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 70 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 500 + self_employment_income: -12_000_000 # extreme negative + ssi_unearned_income: 0 + marital_units: + marital_unit: + members: [person1] + tax_units: + tax_unit: + members: [person1] + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MN + output: + # Earned floored at 0; unearned 0; total countable = 0. + mn_msa_countable_income: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/integration.yaml b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/integration.yaml new file mode 100644 index 00000000000..00413006468 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/integration.yaml @@ -0,0 +1,475 @@ +- name: Case 1, single SSI recipient living alone with no other income. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 70 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 1_000 + mn_msa_lives_with_others: false + mn_msa_treated_as_living_alone: false + ssi: 11_928 # $994/mo + ssi_earned_income: 0 + ssi_unearned_income: 0 + employment_income: 0 + marital_units: + marital_unit: + members: [person1] + tax_units: + tax_unit: + members: [person1] + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MN + output: + mn_msa_living_arrangement: INDIVIDUAL_LIVING_ALONE + mn_msa_assistance_standard: 1_055 + mn_msa_countable_income: 0 + mn_msa_eligible_person: true + # Need $1,055 - federal SSI $994 - countable $0 = $61. + mn_msa_person: 61 + mn_msa: 61 + +- name: Case 2, SSI couple living with others gets the couple-rate gating. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 70 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 800 + mn_msa_lives_with_others: true + mn_msa_treated_as_living_alone: false + ssi_claim_is_joint: true + ssi: 8_946 # $745.50/mo (couple FBR $1,491 / 2) + ssi_earned_income: 0 + ssi_unearned_income: 0 + person2: + age: 68 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 800 + mn_msa_lives_with_others: true + mn_msa_treated_as_living_alone: false + ssi_claim_is_joint: true + ssi: 8_946 + ssi_earned_income: 0 + ssi_unearned_income: 0 + 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: MN + output: + mn_msa_living_arrangement: [COUPLE_LIVING_WITH_OTHERS, COUPLE_LIVING_WITH_OTHERS] + mn_msa_assistance_standard: [1_058, 1_058] + mn_msa_eligible_person: [true, true] + # COUPLE_LIVING_WITH_OTHERS $1,058 is the couple total; per person = + # $529. Per spouse: max(0, $529 - $745.50 - $0) = $0 (couple SSI + # exceeds the couple supplement standard for this living arrangement). + mn_msa_person: [0, 0] + mn_msa: 0 + +- name: Case 3, non-SSI track recipient with income above SSI but below MSA standard. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 70 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 5_000 # above $2k SSI cap, below $10k non-SSI cap + mn_msa_lives_with_others: false + mn_msa_treated_as_living_alone: false + ssi: 0 + ssi_earned_income: 0 + ssi_unearned_income: 12_000 # $1,000/mo + employment_income: 0 + marital_units: + marital_unit: + members: [person1] + tax_units: + tax_unit: + members: [person1] + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MN + output: + mn_msa_living_arrangement: INDIVIDUAL_LIVING_ALONE + mn_msa_assistance_standard: 1_055 + mn_msa_countable_income: 1_000 + mn_msa_eligible_person: true + # Need $1,055 - countable $1,000 (no federal SSI to subtract) = $55. + mn_msa_person: 55 + mn_msa: 55 + +- name: Case 4, Medicaid facility recipient receives PNA-only state portion. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 80 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 200 + ssi_lives_in_medical_treatment_facility: true + ssi_medicaid_pays_majority_of_care: true + mn_msa_lives_with_others: false + mn_msa_treated_as_living_alone: false + ssi: 360 # $30/mo FLA-D + ssi_earned_income: 0 + ssi_unearned_income: 0 + marital_units: + marital_unit: + members: [person1] + tax_units: + tax_unit: + members: [person1] + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MN + output: + mn_msa_living_arrangement: MEDICAID_FACILITY + mn_msa_assistance_standard: 162 + mn_msa_countable_income: 0 + mn_msa_eligible_person: true + # Need $162 - federal SSI $30 - countable $0 = $132 (PNA only as state portion). + mn_msa_person: 132 + mn_msa: 132 + +- name: Case 5, recipient with MSA Housing Assistance add-on. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 50 + is_blind: false + is_disabled: true + immigration_status: CITIZEN + ssi_countable_resources: 500 + mn_msa_lives_with_others: false + mn_msa_treated_as_living_alone: false + mn_msa_housing_assistance_eligible: true + ssi: 11_928 + ssi_earned_income: 0 + ssi_unearned_income: 0 + marital_units: + marital_unit: + members: [person1] + tax_units: + tax_unit: + members: [person1] + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MN + output: + mn_msa_living_arrangement: INDIVIDUAL_LIVING_ALONE + mn_msa_assistance_standard: 1_055 + mn_msa_housing_assistance: 497 + mn_msa_eligible_person: true + # Need $1,055 + housing add-on $497 = $1,552; less SSI $994 - countable $0 = $558. + mn_msa_person: 558 + mn_msa: 558 + +- name: Case 6, negative countable income does not inflate benefit. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 70 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 500 + mn_msa_lives_with_others: false + mn_msa_treated_as_living_alone: false + ssi: 11_928 + self_employment_income: -60_000_000 # extreme negative + marital_units: + marital_unit: + members: [person1] + tax_units: + tax_unit: + members: [person1] + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MN + output: + # Countable income components are floored at 0; benefit cannot exceed need - SSI. + mn_msa_assistance_standard: 1_055 + mn_msa_countable_income: 0 + mn_msa_person: 61 + mn_msa: 61 + +- name: Case 7, non-ABD adult is ineligible. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 500 + mn_msa_lives_with_others: false + mn_msa_treated_as_living_alone: false + ssi: 0 + marital_units: + marital_unit: + members: [person1] + tax_units: + tax_unit: + members: [person1] + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MN + output: + mn_msa_categorically_eligible: false + mn_msa_eligible_person: false + mn_msa_person: 0 + mn_msa: 0 + +- name: Case 8, 2024 single SSI recipient living alone uses 2024 rates. + period: 2024-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 70 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 1_000 + mn_msa_lives_with_others: false + mn_msa_treated_as_living_alone: false + ssi: 11_316 # $943/mo (2024 federal individual FBR) + ssi_earned_income: 0 + ssi_unearned_income: 0 + marital_units: + marital_unit: + members: [person1] + tax_units: + tax_unit: + members: [person1] + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MN + output: + mn_msa_living_arrangement: INDIVIDUAL_LIVING_ALONE + mn_msa_assistance_standard: 1_004 + mn_msa_eligible_person: true + # Need $1,004 - federal SSI $943 - countable $0 = $61. + mn_msa_person: 61 + mn_msa: 61 + +- name: Case 9, asymmetric eligibility couple — only one spouse SSI-eligible. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 70 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 800 + mn_msa_lives_with_others: false + mn_msa_treated_as_living_alone: false + ssi_claim_is_joint: false + ssi: 11_928 # eligible spouse, full FBR + ssi_earned_income: 0 + ssi_unearned_income: 0 + person2: + age: 40 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 800 + mn_msa_lives_with_others: false + mn_msa_treated_as_living_alone: false + ssi_claim_is_joint: false + ssi: 0 # ineligible spouse, not aged/blind/disabled + 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: MN + output: + # Only one spouse is SSI-eligible -> individual rate, not couple rate. + mn_msa_living_arrangement: [INDIVIDUAL_LIVING_ALONE, NONE] + mn_msa_eligible_person: [true, false] + # person1: $1,055 - $994 - $0 = $61. + # person2: not categorically eligible. + mn_msa_person: [61, 0] + mn_msa: 61 + +- name: Case 10, recipient with shelter-need allowance add-on. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 70 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 500 + mn_msa_lives_with_others: false + mn_msa_treated_as_living_alone: false + mn_msa_shelter_need_eligible: true + ssi: 11_928 + ssi_earned_income: 0 + ssi_unearned_income: 0 + marital_units: + marital_unit: + members: [person1] + tax_units: + tax_unit: + members: [person1] + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MN + output: + mn_msa_assistance_standard: 1_055 + mn_msa_shelter_need_allowance: 497 # 0.5 * $994 + mn_msa_eligible_person: true + # Base: $1,055 - $994 - $0 = $61; plus $497 shelter-need add-on = $558. + mn_msa_person: 558 + mn_msa: 558 + +- name: Case 11, recipient with representative payee fee and guardian fee combined. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 70 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 500 + mn_msa_lives_with_others: false + mn_msa_treated_as_living_alone: false + mn_msa_uses_representative_payee: true + mn_msa_has_guardian: true + ssi: 11_928 # $994/mo federal SSI + ssi_earned_income: 0 + ssi_unearned_income: 12_000 # $1,000/mo gross unearned + marital_units: + marital_unit: + members: [person1] + tax_units: + tax_unit: + members: [person1] + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MN + output: + mn_msa_assistance_standard: 1_055 + mn_msa_representative_payee_fee: 57 + # Gross = SSI $994 + unearned $1,000 = $1,994; 5% = $99.70 < $100 cap. + mn_msa_guardian_fee: 99.70 + # Base: max(0, $1,055 - $994 - $1,000 countable) = $0; + # plus $57 payee fee + $99.70 guardian fee = $156.70. + mn_msa_person: 156.70 + mn_msa: 156.70 + +- name: Case 12, person not lawfully present is ineligible regardless of resources or income. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 70 + is_blind: false + is_disabled: false + immigration_status: UNDOCUMENTED + ssi_countable_resources: 500 + mn_msa_lives_with_others: false + mn_msa_treated_as_living_alone: false + ssi: 0 + ssi_unearned_income: 0 + marital_units: + marital_unit: + members: [person1] + tax_units: + tax_unit: + members: [person1] + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MN + output: + # Not receiving SSI and not citizen/legal immigrant -> immigration test fails. + mn_msa_eligible_person: false + mn_msa_person: 0 + mn_msa: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/mn_msa_living_arrangement.yaml b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/mn_msa_living_arrangement.yaml new file mode 100644 index 00000000000..435831ec00d --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/mn_msa_living_arrangement.yaml @@ -0,0 +1,230 @@ +- name: Case 1, single SSI-eligible person living alone. + period: 2026-01 + input: + people: + person1: + age: 70 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 1_000 + mn_msa_lives_with_others: false + mn_msa_treated_as_living_alone: false + ssi: 11_928 # $994/mo + employment_income: 0 + marital_units: + marital_unit: + members: [person1] + tax_units: + tax_unit: + members: [person1] + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MN + output: + mn_msa_living_arrangement: INDIVIDUAL_LIVING_ALONE + +- name: Case 2, single SSI-eligible person living with others. + period: 2026-01 + input: + people: + person1: + age: 70 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 1_000 + mn_msa_lives_with_others: true + mn_msa_treated_as_living_alone: false + ssi: 11_928 + employment_income: 0 + marital_units: + marital_unit: + members: [person1] + tax_units: + tax_unit: + members: [person1] + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MN + output: + mn_msa_living_arrangement: INDIVIDUAL_LIVING_WITH_OTHERS + +- name: Case 3, married SSI-eligible couple living alone joint claim. + period: 2026-01 + input: + people: + person1: + age: 70 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 800 + mn_msa_lives_with_others: false + mn_msa_treated_as_living_alone: false + ssi_claim_is_joint: true + ssi: 8_946 # half of couple FBR $1,491 + person2: + age: 68 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 800 + mn_msa_lives_with_others: false + mn_msa_treated_as_living_alone: false + ssi_claim_is_joint: true + ssi: 8_946 + 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: MN + output: + mn_msa_living_arrangement: [COUPLE_LIVING_ALONE, COUPLE_LIVING_ALONE] + +- name: Case 4, married SSI-eligible couple living with others joint claim. + period: 2026-01 + input: + people: + person1: + age: 72 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 800 + mn_msa_lives_with_others: true + mn_msa_treated_as_living_alone: false + ssi_claim_is_joint: true + ssi: 8_946 + person2: + age: 70 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 800 + mn_msa_lives_with_others: true + mn_msa_treated_as_living_alone: false + ssi_claim_is_joint: true + ssi: 8_946 + 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: MN + output: + mn_msa_living_arrangement: [COUPLE_LIVING_WITH_OTHERS, COUPLE_LIVING_WITH_OTHERS] + +- name: Case 5, person in Medicaid facility. + period: 2026-01 + input: + people: + person1: + age: 80 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 200 + ssi_lives_in_medical_treatment_facility: true + ssi_medicaid_pays_majority_of_care: true + mn_msa_lives_with_others: false + mn_msa_treated_as_living_alone: false + ssi: 360 # $30/mo FLA-D + marital_units: + marital_unit: + members: [person1] + tax_units: + tax_unit: + members: [person1] + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MN + output: + mn_msa_living_arrangement: MEDICAID_FACILITY + +- name: Case 6, HCBS treated-as-living-alone overrides cohabitation. + period: 2026-01 + input: + people: + person1: + age: 60 + is_blind: false + is_disabled: true + immigration_status: CITIZEN + ssi_countable_resources: 500 + mn_msa_lives_with_others: true + mn_msa_treated_as_living_alone: true + ssi: 11_928 + marital_units: + marital_unit: + members: [person1] + tax_units: + tax_unit: + members: [person1] + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MN + output: + # Treated-as-living-alone forces INDIVIDUAL_LIVING_ALONE despite cohabitation. + mn_msa_living_arrangement: INDIVIDUAL_LIVING_ALONE + +- name: Case 7, non-categorically-eligible person defaults to NONE. + period: 2026-01 + input: + people: + person1: + age: 40 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 500 + mn_msa_lives_with_others: false + mn_msa_treated_as_living_alone: false + ssi: 0 + marital_units: + marital_unit: + members: [person1] + tax_units: + tax_unit: + members: [person1] + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MN + output: + # Not aged/blind/disabled -> not categorically eligible -> NONE. + mn_msa_living_arrangement: NONE diff --git a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/mn_msa_person.yaml b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/mn_msa_person.yaml new file mode 100644 index 00000000000..b0785cffaa2 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/mn_msa_person.yaml @@ -0,0 +1,258 @@ +- name: Case 1, SSI-track aged person living alone with no countable income. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 70 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 500 + mn_msa_lives_with_others: false + mn_msa_treated_as_living_alone: false + ssi: 11_928 # $994/mo + ssi_earned_income: 0 + ssi_unearned_income: 0 + marital_units: + marital_unit: + members: [person1] + tax_units: + tax_unit: + members: [person1] + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MN + output: + # Need standard $1,055 - federal SSI $994 - countable income $0 = $61. + mn_msa_person: 61 + +- name: Case 2, non-SSI track aged person with $1,000/mo unearned income living alone. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 70 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 5_000 + mn_msa_lives_with_others: false + mn_msa_treated_as_living_alone: false + ssi: 0 + ssi_unearned_income: 12_000 # $1,000/mo + marital_units: + marital_unit: + members: [person1] + tax_units: + tax_unit: + members: [person1] + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MN + output: + # Need standard $1,055 - countable $1,000 (no federal SSI to subtract) = $55. + mn_msa_person: 55 + +- name: Case 3, Medicaid facility recipient receives PNA-only state portion. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 80 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 200 + ssi_lives_in_medical_treatment_facility: true + ssi_medicaid_pays_majority_of_care: true + mn_msa_lives_with_others: false + mn_msa_treated_as_living_alone: false + ssi: 360 # $30/mo FLA-D + ssi_earned_income: 0 + ssi_unearned_income: 0 + marital_units: + marital_unit: + members: [person1] + tax_units: + tax_unit: + members: [person1] + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MN + output: + # Standard $162 - federal SSI $30 - countable $0 = $132 (PNA only, state portion). + mn_msa_person: 132 + +- name: Case 4, SSI couple living alone splits the couple-rate 50/50 across spouses. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 70 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 800 + mn_msa_lives_with_others: false + mn_msa_treated_as_living_alone: false + ssi_claim_is_joint: true + ssi: 8_946 # $745.50/mo (couple FBR $1,491 / 2) + ssi_earned_income: 0 + ssi_unearned_income: 0 + person2: + age: 68 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 800 + mn_msa_lives_with_others: false + mn_msa_treated_as_living_alone: false + ssi_claim_is_joint: true + ssi: 8_946 + ssi_earned_income: 0 + ssi_unearned_income: 0 + 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: MN + output: + # Per person: $1,582 / 2 = $791 standard - SSI $745.50 - countable $0 = $45.50. + mn_msa_person: [45.50, 45.50] + +- name: Case 5, asymmetric eligibility couple — only one spouse SSI-eligible. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 70 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 800 + mn_msa_lives_with_others: false + mn_msa_treated_as_living_alone: false + ssi_claim_is_joint: false + ssi: 11_928 # eligible spouse, full FBR + ssi_earned_income: 0 + ssi_unearned_income: 0 + person2: + age: 40 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 800 + mn_msa_lives_with_others: false + mn_msa_treated_as_living_alone: false + ssi_claim_is_joint: false + ssi: 0 # ineligible spouse, not aged/blind/disabled + 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: MN + output: + # Couple gate fails -> individual rate for eligible spouse: $1,055 - $994 - $0 = $61. + # Ineligible spouse zeroed by mn_msa_eligible_person filter. + mn_msa_person: [61, 0] + +- name: Case 6, 2024 individual living alone with SSI receipt. + period: 2024-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 70 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 500 + mn_msa_lives_with_others: false + mn_msa_treated_as_living_alone: false + ssi: 11_316 # $943/mo (2024 federal individual FBR) + ssi_earned_income: 0 + ssi_unearned_income: 0 + marital_units: + marital_unit: + members: [person1] + tax_units: + tax_unit: + members: [person1] + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MN + output: + # 2024: $1,004 standard - $943 SSI - $0 countable = $61. + mn_msa_person: 61 + +- name: Case 7, marital unit of size 1 with ssi_claim_is_joint=true cannot trigger couple gate. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 70 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 500 + mn_msa_lives_with_others: false + mn_msa_treated_as_living_alone: false + ssi_claim_is_joint: true # spurious; marital_unit.sum cannot reach 2 + ssi: 11_928 # $994/mo + ssi_earned_income: 0 + ssi_unearned_income: 0 + marital_units: + marital_unit: + members: [person1] + tax_units: + tax_unit: + members: [person1] + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MN + output: + # Couple gate unreachable for size-1 marital unit -> individual rate. + mn_msa_living_arrangement: INDIVIDUAL_LIVING_ALONE + mn_msa_person: 61 diff --git a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/payment/mn_msa_assistance_standard.yaml b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/payment/mn_msa_assistance_standard.yaml new file mode 100644 index 00000000000..7fc91021caa --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/payment/mn_msa_assistance_standard.yaml @@ -0,0 +1,226 @@ +- name: Case 1, individual living alone in 2026. + period: 2026-01 + input: + people: + person1: + age: 70 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 500 + mn_msa_lives_with_others: false + mn_msa_treated_as_living_alone: false + ssi: 11_928 + marital_units: + marital_unit: + members: [person1] + tax_units: + tax_unit: + members: [person1] + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MN + output: + mn_msa_assistance_standard: 1_055 + +- name: Case 2, individual living with others in 2026. + period: 2026-01 + input: + people: + person1: + age: 70 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 500 + mn_msa_lives_with_others: true + mn_msa_treated_as_living_alone: false + ssi: 11_928 + marital_units: + marital_unit: + members: [person1] + tax_units: + tax_unit: + members: [person1] + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MN + output: + mn_msa_assistance_standard: 755.33 + +- name: Case 3, couple living alone joint claim in 2026. + period: 2026-01 + input: + people: + person1: + age: 70 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 800 + mn_msa_lives_with_others: false + mn_msa_treated_as_living_alone: false + ssi_claim_is_joint: true + ssi: 8_946 + person2: + age: 68 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 800 + mn_msa_lives_with_others: false + mn_msa_treated_as_living_alone: false + ssi_claim_is_joint: true + ssi: 8_946 + 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: MN + output: + mn_msa_assistance_standard: [1_582, 1_582] + +- name: Case 4, couple living with others joint claim in 2026. + period: 2026-01 + input: + people: + person1: + age: 72 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 800 + mn_msa_lives_with_others: true + mn_msa_treated_as_living_alone: false + ssi_claim_is_joint: true + ssi: 8_946 + person2: + age: 70 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 800 + mn_msa_lives_with_others: true + mn_msa_treated_as_living_alone: false + ssi_claim_is_joint: true + ssi: 8_946 + 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: MN + output: + mn_msa_assistance_standard: [1_058, 1_058] + +- name: Case 5, person in Medicaid facility in 2026. + period: 2026-01 + input: + people: + person1: + age: 80 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 200 + ssi_lives_in_medical_treatment_facility: true + ssi_medicaid_pays_majority_of_care: true + mn_msa_lives_with_others: false + mn_msa_treated_as_living_alone: false + ssi: 360 + marital_units: + marital_unit: + members: [person1] + tax_units: + tax_unit: + members: [person1] + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MN + output: + mn_msa_assistance_standard: 162 + +- name: Case 6, NONE arrangement returns zero standard. + period: 2026-01 + input: + people: + person1: + age: 40 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 500 + mn_msa_lives_with_others: false + mn_msa_treated_as_living_alone: false + ssi: 0 + marital_units: + marital_unit: + members: [person1] + tax_units: + tax_unit: + members: [person1] + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MN + output: + mn_msa_assistance_standard: 0 + +- name: Case 7, individual living alone in 2024 uses 2024 rate. + period: 2024-01 + input: + people: + person1: + age: 70 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 500 + mn_msa_lives_with_others: false + mn_msa_treated_as_living_alone: false + ssi: 11_316 # $943/mo for 2024 + marital_units: + marital_unit: + members: [person1] + tax_units: + tax_unit: + members: [person1] + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MN + output: + mn_msa_assistance_standard: 1_004 diff --git a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/payment/mn_msa_guardian_fee.yaml b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/payment/mn_msa_guardian_fee.yaml new file mode 100644 index 00000000000..f9824946ec7 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/payment/mn_msa_guardian_fee.yaml @@ -0,0 +1,179 @@ +- name: Case 1, guardian fee bound by 5% of gross income (under $100 cap). + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 70 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 500 + mn_msa_has_guardian: true + ssi_unearned_income: 24_000 # $2,000/mo gross + marital_units: + marital_unit: + members: [person1] + tax_units: + tax_unit: + members: [person1] + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MN + output: + # 5% of $2,000 = $100; min($100, $100 cap) = $100. + mn_msa_guardian_fee: 100 + +- name: Case 2, guardian fee bound by $100 hard cap. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 70 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 500 + mn_msa_has_guardian: true + ssi_unearned_income: 36_000 # $3,000/mo gross -> 5% = $150 + marital_units: + marital_unit: + members: [person1] + tax_units: + tax_unit: + members: [person1] + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MN + output: + # min(5% of $3,000 = $150, $100 cap) = $100. + mn_msa_guardian_fee: 100 + +- name: Case 3, guardian fee bound by 5% of low gross income. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 70 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 500 + mn_msa_has_guardian: true + ssi: 0 + ssi_unearned_income: 12_000 # $1,000/mo -> 5% = $50 + marital_units: + marital_unit: + members: [person1] + tax_units: + tax_unit: + members: [person1] + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MN + output: + # min(5% of $1,000 = $50, $100 cap) = $50. + mn_msa_guardian_fee: 50 + +- name: Case 4, no guardian (zero fee). + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 70 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 500 + mn_msa_has_guardian: false + ssi_unearned_income: 24_000 + marital_units: + marital_unit: + members: [person1] + tax_units: + tax_unit: + members: [person1] + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MN + output: + mn_msa_guardian_fee: 0 + +- name: Case 5, gross income $1 below $2,000 boundary uses 5%, not the $100 cap. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 70 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 500 + mn_msa_has_guardian: true + ssi_unearned_income: 23_988 # $1,999/mo gross -> 5% = $99.95 + marital_units: + marital_unit: + members: [person1] + tax_units: + tax_unit: + members: [person1] + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MN + output: + # min(5% of $1,999 = $99.95, $100 cap) = $99.95. + mn_msa_guardian_fee: 99.95 + +- name: Case 6, gross income $1 above $2,000 boundary is capped at $100. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 70 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 500 + mn_msa_has_guardian: true + ssi_unearned_income: 24_012 # $2,001/mo gross -> 5% = $100.05 + marital_units: + marital_unit: + members: [person1] + tax_units: + tax_unit: + members: [person1] + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MN + output: + # min(5% of $2,001 = $100.05, $100 cap) = $100. + mn_msa_guardian_fee: 100 diff --git a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/payment/mn_msa_housing_assistance.yaml b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/payment/mn_msa_housing_assistance.yaml new file mode 100644 index 00000000000..e34350abbbe --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/payment/mn_msa_housing_assistance.yaml @@ -0,0 +1,56 @@ +- name: Case 1, housing-assistance eligible person receives half of SSI individual FBR in 2026. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 50 + is_blind: false + is_disabled: true + immigration_status: CITIZEN + ssi_countable_resources: 500 + mn_msa_housing_assistance_eligible: true + marital_units: + marital_unit: + members: [person1] + tax_units: + tax_unit: + members: [person1] + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MN + output: + # 0.5 * $994 (2026 individual FBR) = $497. + mn_msa_housing_assistance: 497 + +- name: Case 2, person not flagged as housing-assistance-eligible receives nothing. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 50 + is_blind: false + is_disabled: true + immigration_status: CITIZEN + ssi_countable_resources: 500 + mn_msa_housing_assistance_eligible: false + marital_units: + marital_unit: + members: [person1] + tax_units: + tax_unit: + members: [person1] + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MN + output: + mn_msa_housing_assistance: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/payment/mn_msa_representative_payee_fee.yaml b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/payment/mn_msa_representative_payee_fee.yaml new file mode 100644 index 00000000000..f85ffc7970e --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/payment/mn_msa_representative_payee_fee.yaml @@ -0,0 +1,55 @@ +- name: Case 1, person using representative payee receives $57 fee in 2026. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 70 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 500 + mn_msa_uses_representative_payee: true + marital_units: + marital_unit: + members: [person1] + tax_units: + tax_unit: + members: [person1] + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MN + output: + mn_msa_representative_payee_fee: 57 + +- name: Case 2, person not using representative payee receives nothing. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 70 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 500 + mn_msa_uses_representative_payee: false + marital_units: + marital_unit: + members: [person1] + tax_units: + tax_unit: + members: [person1] + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MN + output: + mn_msa_representative_payee_fee: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/payment/mn_msa_shelter_need_allowance.yaml b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/payment/mn_msa_shelter_need_allowance.yaml new file mode 100644 index 00000000000..f452f0ceed8 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/payment/mn_msa_shelter_need_allowance.yaml @@ -0,0 +1,56 @@ +- name: Case 1, shelter need eligible person receives half of SSI individual FBR in 2026. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 70 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 500 + mn_msa_shelter_need_eligible: true + marital_units: + marital_unit: + members: [person1] + tax_units: + tax_unit: + members: [person1] + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MN + output: + # 0.5 * $994 (2026 individual FBR) = $497. + mn_msa_shelter_need_allowance: 497 + +- name: Case 2, person not flagged as shelter-need-eligible receives nothing. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 70 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 500 + mn_msa_shelter_need_eligible: false + marital_units: + marital_unit: + members: [person1] + tax_units: + tax_unit: + members: [person1] + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MN + output: + mn_msa_shelter_need_allowance: 0 diff --git a/policyengine_us/variables/gov/states/mn/dhs/msa/eligibility/mn_msa_categorically_eligible.py b/policyengine_us/variables/gov/states/mn/dhs/msa/eligibility/mn_msa_categorically_eligible.py new file mode 100644 index 00000000000..27f71d62d1b --- /dev/null +++ b/policyengine_us/variables/gov/states/mn/dhs/msa/eligibility/mn_msa_categorically_eligible.py @@ -0,0 +1,25 @@ +from policyengine_us.model_api import * + + +class mn_msa_categorically_eligible(Variable): + value_type = bool + entity = Person + label = "Minnesota Supplemental Aid categorically eligible" + definition_period = MONTH + defined_for = StateCode.MN + reference = ( + "https://www.revisor.mn.gov/statutes/cite/256D.44", + "https://www.house.mn.gov/hrd/pubs/pap_MSA.pdf#page=2", + ) + + def formula(person, period, parameters): + # Per Minn. Stat. § 256D.44 Subd. 1, MSA covers two paths: + # (a) currently receiving SSI (the SSI track), or + # (b) categorically SSI-eligible but income-ineligible for SSI, + # with countable income at or below the MSA assistance + # standard (the non-SSI excess-income track). + # Both paths require aged/blind/disabled status. The non-SSI + # track uses the higher state $10,000 resource cap, so we check + # ABD directly rather than is_ssi_eligible (which includes the + # federal $2,000 resource test). + return person("is_ssi_aged_blind_disabled", period.this_year) diff --git a/policyengine_us/variables/gov/states/mn/dhs/msa/eligibility/mn_msa_eligible_person.py b/policyengine_us/variables/gov/states/mn/dhs/msa/eligibility/mn_msa_eligible_person.py new file mode 100644 index 00000000000..9cb52a267f9 --- /dev/null +++ b/policyengine_us/variables/gov/states/mn/dhs/msa/eligibility/mn_msa_eligible_person.py @@ -0,0 +1,31 @@ +from policyengine_us.model_api import * + + +class mn_msa_eligible_person(Variable): + value_type = bool + entity = Person + label = "Eligible person for Minnesota Supplemental Aid" + definition_period = MONTH + defined_for = StateCode.MN + reference = ( + "https://www.revisor.mn.gov/statutes/cite/256D.44", + "https://www.house.mn.gov/hrd/pubs/pap_MSA.pdf#page=2", + ) + + def formula(person, period, parameters): + # Per Minn. Stat. § 256D.44 Subd. 1, MSA recipients must be + # categorically aged/blind/disabled, meet the resource and + # gross/net income tests, and reside in Minnesota with a + # qualifying immigration status (citizen or qualified noncitizen). + categorically_eligible = person("mn_msa_categorically_eligible", period) + resource_eligible = person("mn_msa_resource_eligible", period) + gross_income_eligible = person("mn_msa_gross_income_eligible", period) + net_income_eligible = person("mn_msa_net_income_eligible", period) + meets_immigration = person("is_citizen_or_legal_immigrant", period.this_year) + return ( + categorically_eligible + & resource_eligible + & gross_income_eligible + & net_income_eligible + & meets_immigration + ) diff --git a/policyengine_us/variables/gov/states/mn/dhs/msa/eligibility/mn_msa_gross_income_eligible.py b/policyengine_us/variables/gov/states/mn/dhs/msa/eligibility/mn_msa_gross_income_eligible.py new file mode 100644 index 00000000000..6af47d95393 --- /dev/null +++ b/policyengine_us/variables/gov/states/mn/dhs/msa/eligibility/mn_msa_gross_income_eligible.py @@ -0,0 +1,34 @@ +from policyengine_us.model_api import * + + +class mn_msa_gross_income_eligible(Variable): + value_type = bool + entity = Person + label = "Minnesota Supplemental Aid gross income eligible" + definition_period = MONTH + defined_for = StateCode.MN + reference = ( + "https://www.revisor.mn.gov/statutes/cite/256D.44", + "https://www.house.mn.gov/hrd/pubs/pap_MSA.pdf#page=2", + ) + + def formula(person, period, parameters): + # Per Minn. Stat. § 256D.44 Subd. 1, gross income may not exceed + # 300% of the federal SSI individual benefit rate for an + # individual or 600% for a couple. + p = parameters(period).gov.states.mn.dhs.msa.eligibility.income_limit + ssi_fbr = parameters(period).gov.ssa.ssi.amount.individual + joint_claim = person("ssi_claim_is_joint", period.this_year) + categorically_eligible = person("mn_msa_categorically_eligible", period) + both_eligible = ( + person.marital_unit.sum(categorically_eligible) == 2 + ) & joint_claim + gross = person("mn_msa_gross_income", period) + couple_gross = person.marital_unit.sum(gross) + income = where(both_eligible, couple_gross, gross) + cap = where( + both_eligible, + ssi_fbr * p.couple_fbr_multiplier, + ssi_fbr * p.individual_fbr_multiplier, + ) + return income <= cap diff --git a/policyengine_us/variables/gov/states/mn/dhs/msa/eligibility/mn_msa_net_income_eligible.py b/policyengine_us/variables/gov/states/mn/dhs/msa/eligibility/mn_msa_net_income_eligible.py new file mode 100644 index 00000000000..9709f386a58 --- /dev/null +++ b/policyengine_us/variables/gov/states/mn/dhs/msa/eligibility/mn_msa_net_income_eligible.py @@ -0,0 +1,21 @@ +from policyengine_us.model_api import * + + +class mn_msa_net_income_eligible(Variable): + value_type = bool + entity = Person + label = "Minnesota Supplemental Aid net income eligible" + definition_period = MONTH + defined_for = StateCode.MN + reference = ( + "https://www.revisor.mn.gov/statutes/cite/256D.44", + "https://www.house.mn.gov/hrd/pubs/pap_MSA.pdf#page=2", + ) + + def formula(person, period, parameters): + # Per Minn. Stat. § 256D.44 Subd. 1, MSA pays only to recipients + # whose countable income is at or below the assistance standard + # for their living arrangement. + countable_income = person("mn_msa_countable_income", period) + standard = person("mn_msa_assistance_standard", period) + return countable_income <= standard diff --git a/policyengine_us/variables/gov/states/mn/dhs/msa/eligibility/mn_msa_resource_eligible.py b/policyengine_us/variables/gov/states/mn/dhs/msa/eligibility/mn_msa_resource_eligible.py new file mode 100644 index 00000000000..d40afd53840 --- /dev/null +++ b/policyengine_us/variables/gov/states/mn/dhs/msa/eligibility/mn_msa_resource_eligible.py @@ -0,0 +1,24 @@ +from policyengine_us.model_api import * + + +class mn_msa_resource_eligible(Variable): + value_type = bool + entity = Person + label = "Minnesota Supplemental Aid resource eligible" + definition_period = MONTH + defined_for = StateCode.MN + reference = ( + "https://www.revisor.mn.gov/statutes/cite/256D.37", + "https://www.lawhelpmn.org/self-help-library/fact-sheet/minnesota-supplemental-aid-msa", + "https://www.house.mn.gov/hrd/pubs/pap_MSA.pdf#page=2", + ) + + def formula(person, period, parameters): + # Per Minn. Stat. § 256D.37: SSI track recipients (ssi > 0) already + # passed the federal SSI $2,000/$3,000 resource test. The non-SSI + # excess-income track uses a higher $10,000 personal-property cap. + p = parameters(period).gov.states.mn.dhs.msa.eligibility + receives_ssi = person("ssi", period) > 0 + countable_resources = person("ssi_countable_resources", period.this_year) + non_ssi_track_eligible = countable_resources <= p.asset_limit.non_ssi_track + return receives_ssi | non_ssi_track_eligible diff --git a/policyengine_us/variables/gov/states/mn/dhs/msa/income/mn_msa_countable_earned_income.py b/policyengine_us/variables/gov/states/mn/dhs/msa/income/mn_msa_countable_earned_income.py new file mode 100644 index 00000000000..36fc54b307b --- /dev/null +++ b/policyengine_us/variables/gov/states/mn/dhs/msa/income/mn_msa_countable_earned_income.py @@ -0,0 +1,26 @@ +from policyengine_us.model_api import * + + +class mn_msa_countable_earned_income(Variable): + value_type = float + entity = Person + label = "Minnesota Supplemental Aid countable earned income" + unit = USD + definition_period = MONTH + defined_for = StateCode.MN + reference = ( + "https://www.revisor.mn.gov/statutes/cite/256D.44", + "https://www.dhs.state.mn.us/main/groups/county_access/documents/pub/mndhs-073585.pdf#page=2", + ) + + def formula(person, period, parameters): + gross_earned = add( + person, + period, + [ + "ssi_earned_income", + "ssi_earned_income_deemed_from_ineligible_spouse", + ], + ) + disregard = person("mn_msa_earned_income_disregard", period) + return max_(gross_earned - disregard, 0) diff --git a/policyengine_us/variables/gov/states/mn/dhs/msa/income/mn_msa_countable_income.py b/policyengine_us/variables/gov/states/mn/dhs/msa/income/mn_msa_countable_income.py new file mode 100644 index 00000000000..66c5def4f9c --- /dev/null +++ b/policyengine_us/variables/gov/states/mn/dhs/msa/income/mn_msa_countable_income.py @@ -0,0 +1,19 @@ +from policyengine_us.model_api import * + + +class mn_msa_countable_income(Variable): + value_type = float + entity = Person + label = "Minnesota Supplemental Aid countable income" + unit = USD + definition_period = MONTH + defined_for = StateCode.MN + reference = ( + "https://www.revisor.mn.gov/statutes/cite/256D.44", + "https://www.dhs.state.mn.us/main/groups/county_access/documents/pub/mndhs-073585.pdf#page=2", + ) + + adds = [ + "mn_msa_countable_earned_income", + "mn_msa_countable_unearned_income", + ] diff --git a/policyengine_us/variables/gov/states/mn/dhs/msa/income/mn_msa_countable_unearned_income.py b/policyengine_us/variables/gov/states/mn/dhs/msa/income/mn_msa_countable_unearned_income.py new file mode 100644 index 00000000000..1b548ef90b1 --- /dev/null +++ b/policyengine_us/variables/gov/states/mn/dhs/msa/income/mn_msa_countable_unearned_income.py @@ -0,0 +1,30 @@ +from policyengine_us.model_api import * + + +class mn_msa_countable_unearned_income(Variable): + value_type = float + entity = Person + label = "Minnesota Supplemental Aid countable unearned income" + unit = USD + definition_period = MONTH + defined_for = StateCode.MN + reference = ( + "https://www.revisor.mn.gov/statutes/cite/256D.44", + "https://www.dhs.state.mn.us/main/groups/county_access/documents/pub/mndhs-073585.pdf#page=2", + ) + + def formula(person, period, parameters): + # Per MN DHS Combined Manual 0018.18, MSA does not apply a + # general unearned-income disregard separate from federal SSI's + # $20 general disregard; the federal disregard is consumed + # inside ssi_countable_income for the SSI track. MSA simply + # treats unearned income (including the deemed-spouse amount) + # as fully countable. + return add( + person, + period, + [ + "ssi_unearned_income", + "ssi_unearned_income_deemed_from_ineligible_spouse", + ], + ) diff --git a/policyengine_us/variables/gov/states/mn/dhs/msa/income/mn_msa_earned_income_disregard.py b/policyengine_us/variables/gov/states/mn/dhs/msa/income/mn_msa_earned_income_disregard.py new file mode 100644 index 00000000000..5f39f86436a --- /dev/null +++ b/policyengine_us/variables/gov/states/mn/dhs/msa/income/mn_msa_earned_income_disregard.py @@ -0,0 +1,33 @@ +from policyengine_us.model_api import * + + +class mn_msa_earned_income_disregard(Variable): + value_type = float + entity = Person + label = "Minnesota Supplemental Aid earned income disregard" + unit = USD + definition_period = MONTH + defined_for = StateCode.MN + reference = ( + "https://www.revisor.mn.gov/statutes/cite/256D.44", + "https://www.dhs.state.mn.us/main/groups/county_access/documents/pub/mndhs-073585.pdf#page=2", + ) + + def formula(person, period, parameters): + # Per MN DHS Combined Manual 0018.18, MSA's non-SSI track + # disregards the first $65 of earned income plus half the + # remainder. The federal $20 general-income disregard belongs + # to the SSI track's own countable-income calculation, not + # to MSA — see ssi_countable_income. + p = parameters(period).gov.states.mn.dhs.msa.disregard.earned + gross_earned = add( + person, + period, + [ + "ssi_earned_income", + "ssi_earned_income_deemed_from_ineligible_spouse", + ], + ) + initial_disregard = min_(gross_earned, p.initial) + remainder = max_(gross_earned - p.initial, 0) + return initial_disregard + remainder * p.rate diff --git a/policyengine_us/variables/gov/states/mn/dhs/msa/income/mn_msa_gross_income.py b/policyengine_us/variables/gov/states/mn/dhs/msa/income/mn_msa_gross_income.py new file mode 100644 index 00000000000..34dc80bd129 --- /dev/null +++ b/policyengine_us/variables/gov/states/mn/dhs/msa/income/mn_msa_gross_income.py @@ -0,0 +1,31 @@ +from policyengine_us.model_api import * + + +class mn_msa_gross_income(Variable): + value_type = float + entity = Person + label = "Minnesota Supplemental Aid gross income" + unit = USD + definition_period = MONTH + defined_for = StateCode.MN + reference = ( + "https://www.revisor.mn.gov/statutes/cite/256D.44", + "https://www.house.mn.gov/hrd/pubs/pap_MSA.pdf#page=2", + ) + + def formula(person, period, parameters): + # Per Minn. Stat. § 256D.44 Subd. 1, MSA's gross-income screen + # applies to total earned + unearned income (including SSI itself + # for the SSI track). Spousal deeming is handled via the SSI + # deemed-income variables. + return add( + person, + period, + [ + "ssi_earned_income", + "ssi_unearned_income", + "ssi", + "ssi_earned_income_deemed_from_ineligible_spouse", + "ssi_unearned_income_deemed_from_ineligible_spouse", + ], + ) diff --git a/policyengine_us/variables/gov/states/mn/dhs/msa/mn_msa.py b/policyengine_us/variables/gov/states/mn/dhs/msa/mn_msa.py new file mode 100644 index 00000000000..2bad4d616f8 --- /dev/null +++ b/policyengine_us/variables/gov/states/mn/dhs/msa/mn_msa.py @@ -0,0 +1,13 @@ +from policyengine_us.model_api import * + + +class mn_msa(Variable): + value_type = float + entity = SPMUnit + label = "Minnesota Supplemental Aid" + unit = USD + definition_period = MONTH + defined_for = StateCode.MN + reference = "https://www.revisor.mn.gov/statutes/cite/256D.44" + + adds = ["mn_msa_person"] diff --git a/policyengine_us/variables/gov/states/mn/dhs/msa/mn_msa_has_guardian.py b/policyengine_us/variables/gov/states/mn/dhs/msa/mn_msa_has_guardian.py new file mode 100644 index 00000000000..87de43fd885 --- /dev/null +++ b/policyengine_us/variables/gov/states/mn/dhs/msa/mn_msa_has_guardian.py @@ -0,0 +1,13 @@ +from policyengine_us.model_api import * + + +class mn_msa_has_guardian(Variable): + value_type = bool + entity = Person + label = "Whether the person has a court-appointed guardian or conservator under Minnesota Supplemental Aid" + definition_period = MONTH + defined_for = StateCode.MN + reference = ( + "https://www.revisor.mn.gov/statutes/cite/256D.44", + "https://www.ssa.gov/policy/docs/progdesc/ssi_st_asst/2011/mn.html", + ) diff --git a/policyengine_us/variables/gov/states/mn/dhs/msa/mn_msa_housing_assistance_eligible.py b/policyengine_us/variables/gov/states/mn/dhs/msa/mn_msa_housing_assistance_eligible.py new file mode 100644 index 00000000000..64f37004a08 --- /dev/null +++ b/policyengine_us/variables/gov/states/mn/dhs/msa/mn_msa_housing_assistance_eligible.py @@ -0,0 +1,15 @@ +from policyengine_us.model_api import * + + +class mn_msa_housing_assistance_eligible(Variable): + value_type = bool + entity = Person + label = ( + "Whether the person qualifies for Minnesota Supplemental Aid housing assistance" + ) + definition_period = MONTH + defined_for = StateCode.MN + reference = ( + "https://www.revisor.mn.gov/statutes/cite/256D.44", + "https://www.dhs.state.mn.us/main/idcplg?IdcService=GET_DYNAMIC_CONVERSION&dDocName=cm_002324&RevisionSelectionMethod=LatestReleased", + ) diff --git a/policyengine_us/variables/gov/states/mn/dhs/msa/mn_msa_in_group_residential.py b/policyengine_us/variables/gov/states/mn/dhs/msa/mn_msa_in_group_residential.py new file mode 100644 index 00000000000..222b9c2ad57 --- /dev/null +++ b/policyengine_us/variables/gov/states/mn/dhs/msa/mn_msa_in_group_residential.py @@ -0,0 +1,13 @@ +from policyengine_us.model_api import * + + +class mn_msa_in_group_residential(Variable): + value_type = bool + entity = Person + label = "Whether the person resides in Group Residential Housing under Minnesota Supplemental Aid" + definition_period = MONTH + defined_for = StateCode.MN + reference = ( + "https://www.revisor.mn.gov/statutes/cite/256I", + "https://www.dhs.state.mn.us/main/groups/county_access/documents/pub/mndhs-073585.pdf#page=4", + ) diff --git a/policyengine_us/variables/gov/states/mn/dhs/msa/mn_msa_lives_with_others.py b/policyengine_us/variables/gov/states/mn/dhs/msa/mn_msa_lives_with_others.py new file mode 100644 index 00000000000..7b44eeaa400 --- /dev/null +++ b/policyengine_us/variables/gov/states/mn/dhs/msa/mn_msa_lives_with_others.py @@ -0,0 +1,13 @@ +from policyengine_us.model_api import * + + +class mn_msa_lives_with_others(Variable): + value_type = bool + entity = Person + label = "Whether the person cohabits with persons outside the marital unit under Minnesota Supplemental Aid" + definition_period = MONTH + defined_for = StateCode.MN + reference = ( + "https://www.revisor.mn.gov/statutes/cite/256D.44", + "https://www.dhs.state.mn.us/main/groups/county_access/documents/pub/mndhs-073585.pdf#page=4", + ) diff --git a/policyengine_us/variables/gov/states/mn/dhs/msa/mn_msa_living_arrangement.py b/policyengine_us/variables/gov/states/mn/dhs/msa/mn_msa_living_arrangement.py new file mode 100644 index 00000000000..6208e33585c --- /dev/null +++ b/policyengine_us/variables/gov/states/mn/dhs/msa/mn_msa_living_arrangement.py @@ -0,0 +1,80 @@ +from policyengine_us.model_api import * + + +class MNMSALivingArrangement(Enum): + INDIVIDUAL_LIVING_ALONE = "Individual living alone" + INDIVIDUAL_LIVING_WITH_OTHERS = "Individual living with others" + COUPLE_LIVING_ALONE = "Couple living alone" + COUPLE_LIVING_WITH_OTHERS = "Couple living with others" + MEDICAID_FACILITY = "Medicaid facility" + NONE = "Not in a qualifying arrangement" + + +class mn_msa_living_arrangement(Variable): + value_type = Enum + entity = Person + label = "Minnesota Supplemental Aid living arrangement" + definition_period = MONTH + defined_for = StateCode.MN + possible_values = MNMSALivingArrangement + default_value = MNMSALivingArrangement.INDIVIDUAL_LIVING_ALONE + reference = ( + "https://www.revisor.mn.gov/statutes/cite/256D.44", + "https://www.dhs.state.mn.us/main/groups/county_access/documents/pub/mndhs-073585.pdf#page=4", + "https://www.house.mn.gov/hrd/pubs/pap_MSA.pdf#page=6", + ) + + def formula(person, period, parameters): + # Per Minn. Stat. § 256D.44 Subd. 2 and Combined Manual 0020.21, + # the MSA assistance standard is keyed off of whether the recipient + # is a single person or part of a married couple (both eligible) + # and whether they live alone or share a household with others. + # Recipients of HCBS waivers, GRH plans, or MSA Housing Assistance + # are treated as living alone regardless of cohabitation. + federal_arrangement = person("ssi_federal_living_arrangement", period.this_year) + FED = federal_arrangement.possible_values + in_medicaid_facility = person("is_in_medicaid_facility", period.this_year) | ( + federal_arrangement == FED.MEDICAL_TREATMENT_FACILITY + ) + + # Couple gating: both spouses categorically eligible (ABD), joint + # claim. Avoids circular dependency through the MSA→SSI computation + # by using is_ssi_aged_blind_disabled rather than mn_msa_eligible_person. + categorically_eligible = person("mn_msa_categorically_eligible", period) + joint_claim = person("ssi_claim_is_joint", period.this_year) + both_eligible = ( + person.marital_unit.sum(categorically_eligible) == 2 + ) & joint_claim + + treated_as_living_alone = person("mn_msa_treated_as_living_alone", period) + lives_with_others = ( + person("mn_msa_lives_with_others", period) & ~treated_as_living_alone + ) + + LA = MNMSALivingArrangement + couple_alone = both_eligible & ~lives_with_others + couple_with_others = both_eligible & lives_with_others + individual_with_others = ( + categorically_eligible & ~both_eligible & lives_with_others + ) + individual_alone = categorically_eligible & ~both_eligible & ~lives_with_others + + return select( + [ + ~categorically_eligible, + in_medicaid_facility, + couple_alone, + couple_with_others, + individual_with_others, + individual_alone, + ], + [ + LA.NONE, + LA.MEDICAID_FACILITY, + LA.COUPLE_LIVING_ALONE, + LA.COUPLE_LIVING_WITH_OTHERS, + LA.INDIVIDUAL_LIVING_WITH_OTHERS, + LA.INDIVIDUAL_LIVING_ALONE, + ], + default=LA.NONE, + ) diff --git a/policyengine_us/variables/gov/states/mn/dhs/msa/mn_msa_person.py b/policyengine_us/variables/gov/states/mn/dhs/msa/mn_msa_person.py new file mode 100644 index 00000000000..3caedaa723a --- /dev/null +++ b/policyengine_us/variables/gov/states/mn/dhs/msa/mn_msa_person.py @@ -0,0 +1,43 @@ +from policyengine_us.model_api import * + + +class mn_msa_person(Variable): + value_type = float + entity = Person + label = "Minnesota Supplemental Aid per-person amount" + unit = USD + definition_period = MONTH + defined_for = "mn_msa_eligible_person" + reference = ( + "https://www.revisor.mn.gov/statutes/cite/256D.44", + "https://www.house.mn.gov/hrd/pubs/pap_MSA.pdf#page=2", + "https://www.dhs.state.mn.us/main/groups/county_access/documents/pub/mndhs-073585.pdf#page=4", + ) + + def formula(person, period, parameters): + # Per Minn. Stat. § 256D.44 Subd. 1 and SSA 2011 baseline: + # for SSI-track recipients the federal SSI payment is deducted + # from the (combined federal+state) assistance standard, leaving + # the state portion. For non-SSI excess-income recipients there + # is no federal SSI deduction. Countable income further reduces + # the state benefit. Special-needs allowances add on top of the + # assistance-standard-derived base. + # The COUPLE_* assistance standards are couple totals, so split + # them 50/50 across the two eligible spouses. + # ssi is YEAR-defined with USD units, so accessing it from this + # MONTH formula auto-divides by 12. + arrangement = person("mn_msa_living_arrangement", period) + LA = arrangement.possible_values + is_couple_arrangement = (arrangement == LA.COUPLE_LIVING_ALONE) | ( + arrangement == LA.COUPLE_LIVING_WITH_OTHERS + ) + standard = person("mn_msa_assistance_standard", period) + per_person_standard = where(is_couple_arrangement, standard / 2, standard) + countable_income = person("mn_msa_countable_income", period) + federal_ssi = person("ssi", period) + receives_ssi = federal_ssi > 0 + ssi_track = max_(0, per_person_standard - federal_ssi - countable_income) + non_ssi_track = max_(0, per_person_standard - countable_income) + base = where(receives_ssi, ssi_track, non_ssi_track) + special_needs = person("mn_msa_special_needs_total", period) + return base + special_needs diff --git a/policyengine_us/variables/gov/states/mn/dhs/msa/mn_msa_shelter_need_eligible.py b/policyengine_us/variables/gov/states/mn/dhs/msa/mn_msa_shelter_need_eligible.py new file mode 100644 index 00000000000..c85a6f9a88f --- /dev/null +++ b/policyengine_us/variables/gov/states/mn/dhs/msa/mn_msa_shelter_need_eligible.py @@ -0,0 +1,13 @@ +from policyengine_us.model_api import * + + +class mn_msa_shelter_need_eligible(Variable): + value_type = bool + entity = Person + label = "Whether the person qualifies for the shelter-need special-needs allowance under Minnesota Supplemental Aid" + definition_period = MONTH + defined_for = StateCode.MN + reference = ( + "https://www.revisor.mn.gov/statutes/cite/256D.44", + "https://www.ssa.gov/policy/docs/progdesc/ssi_st_asst/2011/mn.html", + ) diff --git a/policyengine_us/variables/gov/states/mn/dhs/msa/mn_msa_treated_as_living_alone.py b/policyengine_us/variables/gov/states/mn/dhs/msa/mn_msa_treated_as_living_alone.py new file mode 100644 index 00000000000..8f2cec16584 --- /dev/null +++ b/policyengine_us/variables/gov/states/mn/dhs/msa/mn_msa_treated_as_living_alone.py @@ -0,0 +1,15 @@ +from policyengine_us.model_api import * + + +class mn_msa_treated_as_living_alone(Variable): + value_type = bool + entity = Person + label = ( + "Whether the person is treated as living alone under Minnesota Supplemental Aid" + ) + definition_period = MONTH + defined_for = StateCode.MN + reference = ( + "https://www.revisor.mn.gov/statutes/cite/256D.44", + "https://www.dhs.state.mn.us/main/groups/county_access/documents/pub/mndhs-073585.pdf#page=4", + ) diff --git a/policyengine_us/variables/gov/states/mn/dhs/msa/mn_msa_uses_representative_payee.py b/policyengine_us/variables/gov/states/mn/dhs/msa/mn_msa_uses_representative_payee.py new file mode 100644 index 00000000000..b0cea2214d4 --- /dev/null +++ b/policyengine_us/variables/gov/states/mn/dhs/msa/mn_msa_uses_representative_payee.py @@ -0,0 +1,13 @@ +from policyengine_us.model_api import * + + +class mn_msa_uses_representative_payee(Variable): + value_type = bool + entity = Person + label = "Whether the person uses a representative payee under Minnesota Supplemental Aid" + definition_period = MONTH + defined_for = StateCode.MN + reference = ( + "https://www.revisor.mn.gov/statutes/cite/256D.44", + "https://www.dhs.state.mn.us/main/groups/county_access/documents/pub/mndhs-073585.pdf#page=6", + ) diff --git a/policyengine_us/variables/gov/states/mn/dhs/msa/payment/mn_msa_assistance_standard.py b/policyengine_us/variables/gov/states/mn/dhs/msa/payment/mn_msa_assistance_standard.py new file mode 100644 index 00000000000..f90d4f6f7a4 --- /dev/null +++ b/policyengine_us/variables/gov/states/mn/dhs/msa/payment/mn_msa_assistance_standard.py @@ -0,0 +1,19 @@ +from policyengine_us.model_api import * + + +class mn_msa_assistance_standard(Variable): + value_type = float + entity = Person + label = "Minnesota Supplemental Aid assistance standard" + unit = USD + definition_period = MONTH + defined_for = StateCode.MN + reference = ( + "https://www.revisor.mn.gov/statutes/cite/256D.44", + "https://www.dhs.state.mn.us/main/groups/county_access/documents/pub/mndhs-073585.pdf#page=4", + ) + + def formula(person, period, parameters): + p = parameters(period).gov.states.mn.dhs.msa.assistance_standard + arrangement = person("mn_msa_living_arrangement", period) + return p.amount[arrangement] diff --git a/policyengine_us/variables/gov/states/mn/dhs/msa/payment/mn_msa_guardian_fee.py b/policyengine_us/variables/gov/states/mn/dhs/msa/payment/mn_msa_guardian_fee.py new file mode 100644 index 00000000000..2131ff143a9 --- /dev/null +++ b/policyengine_us/variables/gov/states/mn/dhs/msa/payment/mn_msa_guardian_fee.py @@ -0,0 +1,23 @@ +from policyengine_us.model_api import * + + +class mn_msa_guardian_fee(Variable): + value_type = float + entity = Person + label = "Minnesota Supplemental Aid guardian or conservator fee allowance" + unit = USD + definition_period = MONTH + defined_for = StateCode.MN + reference = ( + "https://www.revisor.mn.gov/statutes/cite/256D.44", + "https://www.ssa.gov/policy/docs/progdesc/ssi_st_asst/2011/mn.html", + ) + + def formula(person, period, parameters): + # Per SSA 2011 Minnesota Section 9.5: MSA pays the lesser of + # 5% of gross monthly income or $100 to recipients with a + # court-appointed guardian or conservator. + p = parameters(period).gov.states.mn.dhs.msa.special_needs.guardian_fee + has_guardian = person("mn_msa_has_guardian", period) + gross = person("mn_msa_gross_income", period) + return has_guardian * min_(gross * p.rate, p.max_amount) diff --git a/policyengine_us/variables/gov/states/mn/dhs/msa/payment/mn_msa_housing_assistance.py b/policyengine_us/variables/gov/states/mn/dhs/msa/payment/mn_msa_housing_assistance.py new file mode 100644 index 00000000000..0350d573add --- /dev/null +++ b/policyengine_us/variables/gov/states/mn/dhs/msa/payment/mn_msa_housing_assistance.py @@ -0,0 +1,25 @@ +from policyengine_us.model_api import * + + +class mn_msa_housing_assistance(Variable): + value_type = float + entity = Person + label = "Minnesota Supplemental Aid housing assistance allowance" + unit = USD + definition_period = MONTH + defined_for = StateCode.MN + reference = ( + "https://www.revisor.mn.gov/statutes/cite/256D.44", + "https://www.dhs.state.mn.us/main/idcplg?IdcService=GET_DYNAMIC_CONVERSION&dDocName=cm_002324&RevisionSelectionMethod=LatestReleased", + ) + + def formula(person, period, parameters): + # Per Combined Manual 0023.24: MSA Housing Assistance equals + # one half of the federal SSI individual benefit rate. Recipients + # are also treated as living alone for assistance-standard + # selection — set mn_msa_treated_as_living_alone alongside this + # eligibility input to capture the joint effect. + p = parameters(period).gov.states.mn.dhs.msa.housing_assistance + ssi_fbr = parameters(period).gov.ssa.ssi.amount.individual + eligible = person("mn_msa_housing_assistance_eligible", period) + return eligible * ssi_fbr * p.fbr_multiplier diff --git a/policyengine_us/variables/gov/states/mn/dhs/msa/payment/mn_msa_representative_payee_fee.py b/policyengine_us/variables/gov/states/mn/dhs/msa/payment/mn_msa_representative_payee_fee.py new file mode 100644 index 00000000000..4d5a7b49eca --- /dev/null +++ b/policyengine_us/variables/gov/states/mn/dhs/msa/payment/mn_msa_representative_payee_fee.py @@ -0,0 +1,24 @@ +from policyengine_us.model_api import * + + +class mn_msa_representative_payee_fee(Variable): + value_type = float + entity = Person + label = "Minnesota Supplemental Aid representative payee fee allowance" + unit = USD + definition_period = MONTH + defined_for = StateCode.MN + reference = ( + "https://www.revisor.mn.gov/statutes/cite/256D.44", + "https://www.dhs.state.mn.us/main/groups/county_access/documents/pub/mndhs-073585.pdf#page=6", + ) + + def formula(person, period, parameters): + # Per Minn. Stat. § 256D.44 Subd. 5 and Combined Manual 0023.21, + # MSA pays the SSA standard representative-payee fee to recipients + # who use a representative payee. The higher drug/alcohol-addiction + # fee is excluded — we don't track addiction-related payee status + # at the moment. + p = parameters(period).gov.states.mn.dhs.msa.special_needs + uses_payee = person("mn_msa_uses_representative_payee", period) + return uses_payee * p.representative_payee_fee.amount diff --git a/policyengine_us/variables/gov/states/mn/dhs/msa/payment/mn_msa_shelter_need_allowance.py b/policyengine_us/variables/gov/states/mn/dhs/msa/payment/mn_msa_shelter_need_allowance.py new file mode 100644 index 00000000000..05029caaf0a --- /dev/null +++ b/policyengine_us/variables/gov/states/mn/dhs/msa/payment/mn_msa_shelter_need_allowance.py @@ -0,0 +1,23 @@ +from policyengine_us.model_api import * + + +class mn_msa_shelter_need_allowance(Variable): + value_type = float + entity = Person + label = "Minnesota Supplemental Aid shelter-need special allowance" + unit = USD + definition_period = MONTH + defined_for = StateCode.MN + reference = ( + "https://www.revisor.mn.gov/statutes/cite/256D.44", + "https://www.ssa.gov/policy/docs/progdesc/ssi_st_asst/2011/mn.html", + ) + + def formula(person, period, parameters): + # Per Minn. Stat. § 256D.44 Subd. 5(g): the shelter-need + # allowance equals one half of the federal SSI individual + # benefit rate, adjusted annually on July 1. + p = parameters(period).gov.states.mn.dhs.msa.special_needs.shelter_need + ssi_fbr = parameters(period).gov.ssa.ssi.amount.individual + eligible = person("mn_msa_shelter_need_eligible", period) + return eligible * ssi_fbr * p.fbr_multiplier diff --git a/policyengine_us/variables/gov/states/mn/dhs/msa/payment/mn_msa_special_needs_total.py b/policyengine_us/variables/gov/states/mn/dhs/msa/payment/mn_msa_special_needs_total.py new file mode 100644 index 00000000000..2d60473c16d --- /dev/null +++ b/policyengine_us/variables/gov/states/mn/dhs/msa/payment/mn_msa_special_needs_total.py @@ -0,0 +1,21 @@ +from policyengine_us.model_api import * + + +class mn_msa_special_needs_total(Variable): + value_type = float + entity = Person + label = "Minnesota Supplemental Aid total special-needs allowance" + unit = USD + definition_period = MONTH + defined_for = StateCode.MN + reference = ( + "https://www.revisor.mn.gov/statutes/cite/256D.44", + "https://www.dhs.state.mn.us/main/groups/county_access/documents/pub/mndhs-073585.pdf#page=4", + ) + + adds = [ + "mn_msa_representative_payee_fee", + "mn_msa_guardian_fee", + "mn_msa_shelter_need_allowance", + "mn_msa_housing_assistance", + ] 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 0a16e130e8e..8438b96af52 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,6 +26,7 @@ def formula(spm_unit, period, parameters): "la_oss", # Louisiana benefits "ma_state_supplement", # Massachusetts benefits "mi_ssp", # Michigan benefits + "mn_msa", # Minnesota benefits # California programs. "ca_cvrp", # California Clean Vehicle Rebate Project. # Colorado programs. From 717d8b27023ab9c3cb12072e4eea01498daf56cd Mon Sep 17 00:00:00 2001 From: Ziming Date: Thu, 30 Apr 2026 21:18:35 -0400 Subject: [PATCH 03/17] Round 1 review-fix: address critical regulatory and reference issues MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Apply $20 general disregard on both SSI and non-SSI tracks (per CM 0018.18) - Avoid double-applying earned-income disregards on the SSI track (federal SSI already consumes them) - Add unit tests for previously-untested formula variables (gross_income, countable_unearned_income, earned_income_disregard) - Fix wrong PDF page anchors and replace repealed § 256D.37 / wrong Subd. 1 cites Co-Authored-By: Claude Opus 4.7 (1M context) --- .../mn/dhs/msa/disregard/earned/initial.yaml | 4 +- .../mn/dhs/msa/disregard/earned/rate.yaml | 4 +- .../states/mn/dhs/msa/disregard/general.yaml | 14 ++ .../asset_limit/non_ssi_track.yaml | 8 +- .../income_limit/couple_fbr_multiplier.yaml | 6 +- .../individual_fbr_multiplier.yaml | 6 +- .../representative_payee_fee/amount.yaml | 2 +- .../mn_msa_net_income_eligible.yaml | 6 +- .../mn_msa_countable_earned_income.yaml | 48 ++++--- .../msa/income/mn_msa_countable_income.yaml | 28 ++-- .../mn_msa_countable_unearned_income.yaml | 113 ++++++++++++++++ .../mn_msa_earned_income_disregard.yaml | 119 +++++++++++++++++ .../dhs/msa/income/mn_msa_gross_income.yaml | 123 ++++++++++++++++++ .../gov/states/mn/dhs/msa/integration.yaml | 8 +- .../gov/states/mn/dhs/msa/mn_msa_person.yaml | 4 +- .../income/mn_msa_countable_earned_income.py | 12 +- .../mn_msa_countable_unearned_income.py | 15 ++- .../income/mn_msa_earned_income_disregard.py | 25 +++- 18 files changed, 474 insertions(+), 71 deletions(-) create mode 100644 policyengine_us/parameters/gov/states/mn/dhs/msa/disregard/general.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/income/mn_msa_countable_unearned_income.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/income/mn_msa_earned_income_disregard.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/income/mn_msa_gross_income.yaml diff --git a/policyengine_us/parameters/gov/states/mn/dhs/msa/disregard/earned/initial.yaml b/policyengine_us/parameters/gov/states/mn/dhs/msa/disregard/earned/initial.yaml index af06ed0c9f1..3fee1de5377 100644 --- a/policyengine_us/parameters/gov/states/mn/dhs/msa/disregard/earned/initial.yaml +++ b/policyengine_us/parameters/gov/states/mn/dhs/msa/disregard/earned/initial.yaml @@ -10,5 +10,5 @@ metadata: reference: - title: MN DHS Combined Manual 0018.18 - Earned Income Disregards (01/2026) href: https://www.dhs.state.mn.us/main/groups/county_access/documents/pub/mndhs-073585.pdf#page=2 - - title: Minn. Stat. § 256D.44 Subd. 1 (Eligibility) - href: https://www.revisor.mn.gov/statutes/cite/256D.44 + - title: MN House Research Department - Minnesota Supplemental Aid (October 2024) + href: https://www.house.mn.gov/hrd/pubs/pap_MSA.pdf#page=3 diff --git a/policyengine_us/parameters/gov/states/mn/dhs/msa/disregard/earned/rate.yaml b/policyengine_us/parameters/gov/states/mn/dhs/msa/disregard/earned/rate.yaml index 668c4ff76b5..3b9980759c6 100644 --- a/policyengine_us/parameters/gov/states/mn/dhs/msa/disregard/earned/rate.yaml +++ b/policyengine_us/parameters/gov/states/mn/dhs/msa/disregard/earned/rate.yaml @@ -10,5 +10,5 @@ metadata: reference: - title: MN DHS Combined Manual 0018.18 - Earned Income Disregards (01/2026) href: https://www.dhs.state.mn.us/main/groups/county_access/documents/pub/mndhs-073585.pdf#page=2 - - title: Minn. Stat. § 256D.44 Subd. 1 (Eligibility) - href: https://www.revisor.mn.gov/statutes/cite/256D.44 + - title: MN House Research Department - Minnesota Supplemental Aid (October 2024) + href: https://www.house.mn.gov/hrd/pubs/pap_MSA.pdf#page=4 diff --git a/policyengine_us/parameters/gov/states/mn/dhs/msa/disregard/general.yaml b/policyengine_us/parameters/gov/states/mn/dhs/msa/disregard/general.yaml new file mode 100644 index 00000000000..4568f6fede6 --- /dev/null +++ b/policyengine_us/parameters/gov/states/mn/dhs/msa/disregard/general.yaml @@ -0,0 +1,14 @@ +description: Minnesota disregards this amount of general (unearned-first, then earned) income under the Minnesota Supplemental Aid program, mirroring the federal SSI $20 general income disregard. + +values: + 1989-01-01: 20 + +metadata: + unit: currency-USD + period: month + label: Minnesota Supplemental Aid general income disregard + reference: + - title: MN DHS Combined Manual 0018.18 - Earned Income Disregards (01/2026) + href: https://www.dhs.state.mn.us/main/groups/county_access/documents/pub/mndhs-073585.pdf#page=2 + - title: MN House Research Department - Minnesota Supplemental Aid (October 2024) + href: https://www.house.mn.gov/hrd/pubs/pap_MSA.pdf#page=3 diff --git a/policyengine_us/parameters/gov/states/mn/dhs/msa/eligibility/asset_limit/non_ssi_track.yaml b/policyengine_us/parameters/gov/states/mn/dhs/msa/eligibility/asset_limit/non_ssi_track.yaml index 60037161726..9b2a11c0163 100644 --- a/policyengine_us/parameters/gov/states/mn/dhs/msa/eligibility/asset_limit/non_ssi_track.yaml +++ b/policyengine_us/parameters/gov/states/mn/dhs/msa/eligibility/asset_limit/non_ssi_track.yaml @@ -8,9 +8,7 @@ metadata: period: month label: Minnesota Supplemental Aid non-SSI track asset limit reference: - - title: Minn. Stat. § 256D.37 (Resource limits) - href: https://www.revisor.mn.gov/statutes/cite/256D.37 - - title: LawHelpMN - Minnesota Supplemental Aid Fact Sheet - href: https://www.lawhelpmn.org/self-help-library/fact-sheet/minnesota-supplemental-aid-msa - title: MN House Research Department - Minnesota Supplemental Aid (October 2024) - href: https://www.house.mn.gov/hrd/pubs/pap_MSA.pdf#page=2 + href: https://www.house.mn.gov/hrd/pubs/pap_MSA.pdf#page=4 + - title: Minn. Stat. § 256D.44 + href: https://www.revisor.mn.gov/statutes/cite/256D.44 diff --git a/policyengine_us/parameters/gov/states/mn/dhs/msa/eligibility/income_limit/couple_fbr_multiplier.yaml b/policyengine_us/parameters/gov/states/mn/dhs/msa/eligibility/income_limit/couple_fbr_multiplier.yaml index e3729330356..2bf3ccaa4fc 100644 --- a/policyengine_us/parameters/gov/states/mn/dhs/msa/eligibility/income_limit/couple_fbr_multiplier.yaml +++ b/policyengine_us/parameters/gov/states/mn/dhs/msa/eligibility/income_limit/couple_fbr_multiplier.yaml @@ -8,7 +8,7 @@ metadata: period: month label: Minnesota Supplemental Aid couple gross income FBR multiplier reference: - - title: Minn. Stat. § 256D.44 Subd. 1 (Eligibility) - href: https://www.revisor.mn.gov/statutes/cite/256D.44 - title: MN House Research Department - Minnesota Supplemental Aid (October 2024) - href: https://www.house.mn.gov/hrd/pubs/pap_MSA.pdf#page=2 + href: https://www.house.mn.gov/hrd/pubs/pap_MSA.pdf#page=3 + - title: Minn. Stat. § 256D.44 + href: https://www.revisor.mn.gov/statutes/cite/256D.44 diff --git a/policyengine_us/parameters/gov/states/mn/dhs/msa/eligibility/income_limit/individual_fbr_multiplier.yaml b/policyengine_us/parameters/gov/states/mn/dhs/msa/eligibility/income_limit/individual_fbr_multiplier.yaml index 5f9879c5704..b9e1f549560 100644 --- a/policyengine_us/parameters/gov/states/mn/dhs/msa/eligibility/income_limit/individual_fbr_multiplier.yaml +++ b/policyengine_us/parameters/gov/states/mn/dhs/msa/eligibility/income_limit/individual_fbr_multiplier.yaml @@ -8,7 +8,7 @@ metadata: period: month label: Minnesota Supplemental Aid individual gross income FBR multiplier reference: - - title: Minn. Stat. § 256D.44 Subd. 1 (Eligibility) - href: https://www.revisor.mn.gov/statutes/cite/256D.44 - title: MN House Research Department - Minnesota Supplemental Aid (October 2024) - href: https://www.house.mn.gov/hrd/pubs/pap_MSA.pdf#page=2 + href: https://www.house.mn.gov/hrd/pubs/pap_MSA.pdf#page=3 + - title: Minn. Stat. § 256D.44 + href: https://www.revisor.mn.gov/statutes/cite/256D.44 diff --git a/policyengine_us/parameters/gov/states/mn/dhs/msa/special_needs/representative_payee_fee/amount.yaml b/policyengine_us/parameters/gov/states/mn/dhs/msa/special_needs/representative_payee_fee/amount.yaml index e34eb840e79..7c50c7546a7 100644 --- a/policyengine_us/parameters/gov/states/mn/dhs/msa/special_needs/representative_payee_fee/amount.yaml +++ b/policyengine_us/parameters/gov/states/mn/dhs/msa/special_needs/representative_payee_fee/amount.yaml @@ -9,6 +9,6 @@ metadata: label: Minnesota Supplemental Aid representative payee fee amount reference: - title: MN DHS Combined Manual 0023.21 - Representative Payee Services (01/2026) - href: https://www.dhs.state.mn.us/main/groups/county_access/documents/pub/mndhs-073585.pdf#page=6 + href: https://www.dhs.state.mn.us/main/groups/county_access/documents/pub/mndhs-073585.pdf#page=7 - title: Minn. Stat. § 256D.44 Subd. 5 (Special needs) href: https://www.revisor.mn.gov/statutes/cite/256D.44 diff --git a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/eligibility/mn_msa_net_income_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/eligibility/mn_msa_net_income_eligible.yaml index 731f886ea1d..d8bcd501b1b 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/eligibility/mn_msa_net_income_eligible.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/eligibility/mn_msa_net_income_eligible.yaml @@ -11,7 +11,7 @@ mn_msa_lives_with_others: false mn_msa_treated_as_living_alone: false ssi: 0 - ssi_unearned_income: 12_660 # $1,055/mo, exactly at INDIVIDUAL_LIVING_ALONE standard + ssi_unearned_income: 12_900 # $1,075/mo; minus $20 general = $1,055 countable marital_units: marital_unit: members: [person1] @@ -42,7 +42,7 @@ mn_msa_lives_with_others: false mn_msa_treated_as_living_alone: false ssi: 0 - ssi_unearned_income: 12_672 # $1,056/mo, $1 above the $1,055 standard + ssi_unearned_income: 12_912 # $1,076/mo; minus $20 general = $1,056 countable marital_units: marital_unit: members: [person1] @@ -73,7 +73,7 @@ mn_msa_lives_with_others: false mn_msa_treated_as_living_alone: false ssi: 0 - ssi_unearned_income: 12_648 # $1,054/mo + ssi_unearned_income: 12_888 # $1,074/mo; minus $20 general = $1,054 countable marital_units: marital_unit: members: [person1] diff --git a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/income/mn_msa_countable_earned_income.yaml b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/income/mn_msa_countable_earned_income.yaml index fcfcb69d79c..0aaf7b6af87 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/income/mn_msa_countable_earned_income.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/income/mn_msa_countable_earned_income.yaml @@ -1,4 +1,4 @@ -- name: Case 1, earnings below the $65 initial disregard yield zero countable earned. +- name: Case 1, SSI-track recipient with earnings has zero MSA countable earned (federal SSI already disregards). period: 2026-01 input: people: @@ -8,7 +8,7 @@ is_disabled: false immigration_status: CITIZEN ssi_countable_resources: 500 - ssi_earned_income: 600 # $50/mo (annual = $50 * 12) + ssi_earned_income: 3_000 # $250/mo marital_units: marital_unit: members: [person1] @@ -23,11 +23,12 @@ members: [person1] state_code: MN output: - # Earned $50, after $65 disregard = max(0, 50 - 65) = $0; no remainder to halve. + # SSI > 0; federal $20 + $65 + 1/2 disregards already in ssi_countable_income. mn_msa_countable_earned_income: 0 -- name: Case 2, earnings above $65 disregard apply $65 plus half remainder. +- name: Case 2, non-SSI track earnings above $20 + $65 disregard apply remaining and half. period: 2026-01 + absolute_error_margin: 0.01 input: people: person1: @@ -35,8 +36,10 @@ is_blind: false is_disabled: false immigration_status: CITIZEN - ssi_countable_resources: 500 + ssi_countable_resources: 5_000 # above $2k SSI cap, not on SSI track + ssi: 0 ssi_earned_income: 3_000 # $250/mo + ssi_unearned_income: 0 marital_units: marital_unit: members: [person1] @@ -51,11 +54,12 @@ members: [person1] state_code: MN output: - # Earned $250, less $65 = $185, halved = $92.50 countable. - mn_msa_countable_earned_income: 92.50 + # Non-SSI track, no unearned: leftover $20 + $65 = $85 flat; ($250 - $85)/2 = $82.50. + mn_msa_countable_earned_income: 82.50 -- name: Case 3, earnings exactly at $65 boundary yield zero countable. +- name: Case 3, non-SSI track earnings exactly at $20 + $65 boundary yield zero countable. period: 2026-01 + absolute_error_margin: 0.01 input: people: person1: @@ -63,8 +67,10 @@ is_blind: false is_disabled: false immigration_status: CITIZEN - ssi_countable_resources: 500 - ssi_earned_income: 780 # $65/mo + ssi_countable_resources: 5_000 + ssi: 0 + ssi_earned_income: 1_020 # $85/mo + ssi_unearned_income: 0 marital_units: marital_unit: members: [person1] @@ -79,7 +85,7 @@ members: [person1] state_code: MN output: - # Earned $65 - $65 = $0, half of $0 = $0. + # Non-SSI track: $85 - $85 = $0; half of $0 = $0. mn_msa_countable_earned_income: 0 - name: Case 4, zero earnings yield zero countable. @@ -111,7 +117,7 @@ # No earnings -> no countable earned income. mn_msa_countable_earned_income: 0 -- name: Case 5, earnings $1 above the $65 disregard yield half-of-remainder countable. +- name: Case 5, non-SSI track unearned income consumes the $20 general so only $65 disregards earned. period: 2026-01 absolute_error_margin: 0.01 input: @@ -121,8 +127,10 @@ is_blind: false is_disabled: false immigration_status: CITIZEN - ssi_countable_resources: 500 - ssi_earned_income: 792 # $66/mo, $1 above the $65 disregard + ssi_countable_resources: 5_000 + ssi: 0 + ssi_earned_income: 792 # $66/mo + ssi_unearned_income: 600 # $50/mo, exceeds $20 general marital_units: marital_unit: members: [person1] @@ -137,10 +145,10 @@ members: [person1] state_code: MN output: - # Earned $66 - $65 = $1; half = $0.50 countable. + # $20 fully consumed by unearned; only $65 applies to earned: ($66 - $65)/2 = $0.50. mn_msa_countable_earned_income: 0.50 -- name: Case 6, large earnings apply $65 plus half remainder. +- name: Case 6, non-SSI track large earnings apply $20 + $65 plus half remainder. period: 2026-01 absolute_error_margin: 0.01 input: @@ -150,8 +158,10 @@ is_blind: false is_disabled: false immigration_status: CITIZEN - ssi_countable_resources: 500 + ssi_countable_resources: 5_000 + ssi: 0 ssi_earned_income: 12_000 # $1,000/mo + ssi_unearned_income: 0 marital_units: marital_unit: members: [person1] @@ -166,5 +176,5 @@ members: [person1] state_code: MN output: - # Earned $1,000 - $65 = $935; halved = $467.50 countable. - mn_msa_countable_earned_income: 467.50 + # Non-SSI track, no unearned: ($1,000 - $85)/2 = $457.50. + mn_msa_countable_earned_income: 457.50 diff --git a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/income/mn_msa_countable_income.yaml b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/income/mn_msa_countable_income.yaml index 2349a411b2d..a894cb3c1d1 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/income/mn_msa_countable_income.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/income/mn_msa_countable_income.yaml @@ -1,4 +1,4 @@ -- name: Case 1, unearned income only adds directly to countable. +- name: Case 1, non-SSI track unearned income applies the $20 general disregard. period: 2026-01 input: people: @@ -7,7 +7,8 @@ is_blind: false is_disabled: false immigration_status: CITIZEN - ssi_countable_resources: 500 + ssi_countable_resources: 5_000 + ssi: 0 ssi_unearned_income: 7_200 # $600/mo ssi_earned_income: 0 marital_units: @@ -24,9 +25,10 @@ members: [person1] state_code: MN output: - mn_msa_countable_income: 600 + # $600 - $20 general disregard = $580 countable. + mn_msa_countable_income: 580 -- name: Case 2, earned plus unearned income post-disregard. +- name: Case 2, non-SSI track earned plus unearned income with the $20 + $65 + 1/2 federal SSI methodology. period: 2026-01 input: people: @@ -35,7 +37,8 @@ is_blind: false is_disabled: false immigration_status: CITIZEN - ssi_countable_resources: 500 + ssi_countable_resources: 5_000 + ssi: 0 ssi_unearned_income: 2_400 # $200/mo ssi_earned_income: 3_000 # $250/mo marital_units: @@ -52,10 +55,10 @@ members: [person1] state_code: MN output: - # Unearned $200 + countable earned (250 - 65)/2 = $92.50 -> $292.50 total. - mn_msa_countable_income: 292.50 + # Unearned $200 - $20 = $180; earned ($250 - $65)/2 = $92.50; total $272.50. + mn_msa_countable_income: 272.50 -- name: Case 3, negative net earnings floor at zero do not subtract from unearned. +- name: Case 3, non-SSI track unearned with negative earned does not subtract from unearned. period: 2026-01 input: people: @@ -64,9 +67,10 @@ is_blind: false is_disabled: false immigration_status: CITIZEN - ssi_countable_resources: 500 + ssi_countable_resources: 5_000 + ssi: 0 ssi_unearned_income: 6_000 # $500/mo - ssi_earned_income: 600 # $50/mo (below $65 disregard) + ssi_earned_income: 600 # $50/mo (below the rolled-over $85 disregard) marital_units: marital_unit: members: [person1] @@ -81,8 +85,8 @@ members: [person1] state_code: MN output: - # Unearned $500 + earned floored at $0 = $500 (no negative spillover). - mn_msa_countable_income: 500 + # Unearned $500 - $20 = $480; earned $50 floored to $0 after $65 disregard. + mn_msa_countable_income: 480 - name: Case 4, extreme negative self-employment does not produce negative countable. period: 2026-01 diff --git a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/income/mn_msa_countable_unearned_income.yaml b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/income/mn_msa_countable_unearned_income.yaml new file mode 100644 index 00000000000..7c218d2bad9 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/income/mn_msa_countable_unearned_income.yaml @@ -0,0 +1,113 @@ +- name: Case 1, unearned income above $20 has the general disregard applied. + period: 2026-01 + input: + people: + person1: + age: 70 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 5_000 + ssi: 0 + ssi_unearned_income: 6_000 # $500/mo + marital_units: + marital_unit: + members: [person1] + tax_units: + tax_unit: + members: [person1] + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MN + output: + # $500 - $20 general disregard = $480 countable unearned. + mn_msa_countable_unearned_income: 480 + +- name: Case 2, zero unearned income yields zero countable unearned. + period: 2026-01 + input: + people: + person1: + age: 70 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 500 + ssi_unearned_income: 0 + marital_units: + marital_unit: + members: [person1] + tax_units: + tax_unit: + members: [person1] + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MN + output: + mn_msa_countable_unearned_income: 0 + +- name: Case 3, unearned income below $20 floors at zero countable unearned. + period: 2026-01 + input: + people: + person1: + age: 70 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 5_000 + ssi: 0 + ssi_unearned_income: 120 # $10/mo, below the $20 general disregard + marital_units: + marital_unit: + members: [person1] + tax_units: + tax_unit: + members: [person1] + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MN + output: + # $10 - $20 floored at $0 (the leftover rolls into earned income). + mn_msa_countable_unearned_income: 0 + +- name: Case 4, deemed-from-ineligible-spouse unearned income aggregates with own unearned. + period: 2026-01 + input: + people: + person1: + age: 70 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 500 + ssi_unearned_income: 1_200 # $100/mo + ssi_unearned_income_deemed_from_ineligible_spouse: 2_400 # $200/mo deemed + marital_units: + marital_unit: + members: [person1] + tax_units: + tax_unit: + members: [person1] + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MN + output: + # ($100 + $200) - $20 general = $280 countable unearned. + mn_msa_countable_unearned_income: 280 diff --git a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/income/mn_msa_earned_income_disregard.yaml b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/income/mn_msa_earned_income_disregard.yaml new file mode 100644 index 00000000000..24691daf780 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/income/mn_msa_earned_income_disregard.yaml @@ -0,0 +1,119 @@ +- name: Case 1, earnings above $20 + $65 with no unearned apply $85 plus half remainder. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 70 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 5_000 + ssi: 0 + ssi_earned_income: 12_000 # $1,000/mo + ssi_unearned_income: 0 + marital_units: + marital_unit: + members: [person1] + tax_units: + tax_unit: + members: [person1] + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MN + output: + # No unearned -> $20 general rolls into earned: $85 flat + ($1,000 - $85) * 0.5 = $542.50. + mn_msa_earned_income_disregard: 542.50 + +- name: Case 2, earnings with unearned consuming the $20 general apply only $65 plus half remainder. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 70 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 5_000 + ssi: 0 + ssi_earned_income: 12_000 # $1,000/mo + ssi_unearned_income: 1_200 # $100/mo, exceeds $20 general + marital_units: + marital_unit: + members: [person1] + tax_units: + tax_unit: + members: [person1] + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MN + output: + # Unearned consumes $20 -> only $65 flat + ($1,000 - $65) * 0.5 = $532.50. + mn_msa_earned_income_disregard: 532.50 + +- name: Case 3, earnings below $65 disregard yield disregard equal to earnings. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 70 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 5_000 + ssi: 0 + ssi_earned_income: 600 # $50/mo + ssi_unearned_income: 600 # $50/mo, exceeds $20 general + marital_units: + marital_unit: + members: [person1] + tax_units: + tax_unit: + members: [person1] + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MN + output: + # Earnings $50 < flat $65; disregard caps at gross earnings: $50. + mn_msa_earned_income_disregard: 50 + +- name: Case 4, zero earnings yields zero disregard. + period: 2026-01 + input: + people: + person1: + age: 70 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 500 + ssi_earned_income: 0 + marital_units: + marital_unit: + members: [person1] + tax_units: + tax_unit: + members: [person1] + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MN + output: + mn_msa_earned_income_disregard: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/income/mn_msa_gross_income.yaml b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/income/mn_msa_gross_income.yaml new file mode 100644 index 00000000000..bce360d42a9 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/income/mn_msa_gross_income.yaml @@ -0,0 +1,123 @@ +- name: Case 1, SSI recipient gross income includes the federal SSI payment per House Research. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 70 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 500 + ssi: 11_928 # $994/mo + ssi_earned_income: 0 + ssi_unearned_income: 0 + marital_units: + marital_unit: + members: [person1] + tax_units: + tax_unit: + members: [person1] + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MN + output: + # Federal SSI $994 counts as gross unearned for the MSA gross test. + mn_msa_gross_income: 994 + +- name: Case 2, non-SSI track recipient gross income sums earned plus unearned only. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 70 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 5_000 + ssi: 0 + ssi_earned_income: 6_000 # $500/mo + ssi_unearned_income: 3_600 # $300/mo + marital_units: + marital_unit: + members: [person1] + tax_units: + tax_unit: + members: [person1] + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MN + output: + # Earned $500 + unearned $300 + SSI $0 = $800. + mn_msa_gross_income: 800 + +- name: Case 3, deemed-from-ineligible-spouse income aggregates into gross. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 70 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 500 + ssi: 11_928 # $994/mo + ssi_earned_income: 1_200 # $100/mo + ssi_unearned_income: 600 # $50/mo + ssi_earned_income_deemed_from_ineligible_spouse: 2_400 # $200/mo deemed + ssi_unearned_income_deemed_from_ineligible_spouse: 1_200 # $100/mo deemed + marital_units: + marital_unit: + members: [person1] + tax_units: + tax_unit: + members: [person1] + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MN + output: + # Own earned $100 + own unearned $50 + SSI $994 + deemed earned $200 + deemed unearned $100 = $1,444. + mn_msa_gross_income: 1_444 + +- name: Case 4, zero income across all components yields zero gross. + period: 2026-01 + input: + people: + person1: + age: 70 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 500 + ssi: 0 + ssi_earned_income: 0 + ssi_unearned_income: 0 + marital_units: + marital_unit: + members: [person1] + tax_units: + tax_unit: + members: [person1] + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MN + output: + mn_msa_gross_income: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/integration.yaml b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/integration.yaml index 00413006468..4e3a73bec15 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/integration.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/integration.yaml @@ -122,11 +122,11 @@ output: mn_msa_living_arrangement: INDIVIDUAL_LIVING_ALONE mn_msa_assistance_standard: 1_055 - mn_msa_countable_income: 1_000 + mn_msa_countable_income: 980 mn_msa_eligible_person: true - # Need $1,055 - countable $1,000 (no federal SSI to subtract) = $55. - mn_msa_person: 55 - mn_msa: 55 + # Standard $1,055 - countable ($1,000 - $20 general) = $1,055 - $980 = $75. + mn_msa_person: 75 + mn_msa: 75 - name: Case 4, Medicaid facility recipient receives PNA-only state portion. period: 2026-01 diff --git a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/mn_msa_person.yaml b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/mn_msa_person.yaml index b0785cffaa2..59057ad024f 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/mn_msa_person.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/mn_msa_person.yaml @@ -60,8 +60,8 @@ members: [person1] state_code: MN output: - # Need standard $1,055 - countable $1,000 (no federal SSI to subtract) = $55. - mn_msa_person: 55 + # Standard $1,055 - countable ($1,000 - $20 general) = $1,055 - $980 = $75. + mn_msa_person: 75 - name: Case 3, Medicaid facility recipient receives PNA-only state portion. period: 2026-01 diff --git a/policyengine_us/variables/gov/states/mn/dhs/msa/income/mn_msa_countable_earned_income.py b/policyengine_us/variables/gov/states/mn/dhs/msa/income/mn_msa_countable_earned_income.py index 36fc54b307b..81e2658204e 100644 --- a/policyengine_us/variables/gov/states/mn/dhs/msa/income/mn_msa_countable_earned_income.py +++ b/policyengine_us/variables/gov/states/mn/dhs/msa/income/mn_msa_countable_earned_income.py @@ -14,6 +14,14 @@ class mn_msa_countable_earned_income(Variable): ) def formula(person, period, parameters): + # Per MN DHS Combined Manual 0018.18: "For SSI recipients, no + # county action is required. For non-SSI recipients due to + # excess income, disregard the 1st $65 of earned income plus + # half of the remaining earned income." For SSI-track recipients + # the federal $20 + $65 + 1/2 disregards are already consumed + # inside ssi_countable_income, so MSA-side earned income is + # treated as fully disregarded (returned as 0) to avoid + # double-counting. gross_earned = add( person, period, @@ -23,4 +31,6 @@ def formula(person, period, parameters): ], ) disregard = person("mn_msa_earned_income_disregard", period) - return max_(gross_earned - disregard, 0) + non_ssi_track_countable = max_(gross_earned - disregard, 0) + receives_ssi = person("ssi", period) > 0 + return where(receives_ssi, 0, non_ssi_track_countable) diff --git a/policyengine_us/variables/gov/states/mn/dhs/msa/income/mn_msa_countable_unearned_income.py b/policyengine_us/variables/gov/states/mn/dhs/msa/income/mn_msa_countable_unearned_income.py index 1b548ef90b1..ef24bd7c1cc 100644 --- a/policyengine_us/variables/gov/states/mn/dhs/msa/income/mn_msa_countable_unearned_income.py +++ b/policyengine_us/variables/gov/states/mn/dhs/msa/income/mn_msa_countable_unearned_income.py @@ -11,16 +11,15 @@ class mn_msa_countable_unearned_income(Variable): reference = ( "https://www.revisor.mn.gov/statutes/cite/256D.44", "https://www.dhs.state.mn.us/main/groups/county_access/documents/pub/mndhs-073585.pdf#page=2", + "https://www.house.mn.gov/hrd/pubs/pap_MSA.pdf#page=3", ) def formula(person, period, parameters): - # Per MN DHS Combined Manual 0018.18, MSA does not apply a - # general unearned-income disregard separate from federal SSI's - # $20 general disregard; the federal disregard is consumed - # inside ssi_countable_income for the SSI track. MSA simply - # treats unearned income (including the deemed-spouse amount) - # as fully countable. - return add( + # MSA inherits the federal SSI $20 general income disregard. The + # disregard applies first to unearned income; any unused remainder + # rolls over to earned income (handled in + # mn_msa_countable_earned_income). + gross_unearned = add( person, period, [ @@ -28,3 +27,5 @@ def formula(person, period, parameters): "ssi_unearned_income_deemed_from_ineligible_spouse", ], ) + general = parameters(period).gov.states.mn.dhs.msa.disregard.general + return max_(gross_unearned - general, 0) diff --git a/policyengine_us/variables/gov/states/mn/dhs/msa/income/mn_msa_earned_income_disregard.py b/policyengine_us/variables/gov/states/mn/dhs/msa/income/mn_msa_earned_income_disregard.py index 5f39f86436a..84d6af9c0a7 100644 --- a/policyengine_us/variables/gov/states/mn/dhs/msa/income/mn_msa_earned_income_disregard.py +++ b/policyengine_us/variables/gov/states/mn/dhs/msa/income/mn_msa_earned_income_disregard.py @@ -16,10 +16,19 @@ class mn_msa_earned_income_disregard(Variable): def formula(person, period, parameters): # Per MN DHS Combined Manual 0018.18, MSA's non-SSI track # disregards the first $65 of earned income plus half the - # remainder. The federal $20 general-income disregard belongs - # to the SSI track's own countable-income calculation, not - # to MSA — see ssi_countable_income. - p = parameters(period).gov.states.mn.dhs.msa.disregard.earned + # remainder. The federal $20 general disregard also applies on + # the non-SSI track (per House Research and CM 0018.18); it + # consumes unearned income first and any leftover rolls into + # this earned-income calculation. + p = parameters(period).gov.states.mn.dhs.msa.disregard + gross_unearned = add( + person, + period, + [ + "ssi_unearned_income", + "ssi_unearned_income_deemed_from_ineligible_spouse", + ], + ) gross_earned = add( person, period, @@ -28,6 +37,8 @@ def formula(person, period, parameters): "ssi_earned_income_deemed_from_ineligible_spouse", ], ) - initial_disregard = min_(gross_earned, p.initial) - remainder = max_(gross_earned - p.initial, 0) - return initial_disregard + remainder * p.rate + leftover_general = max_(p.general - gross_unearned, 0) + flat = p.earned.initial + leftover_general + flat_disregard = min_(gross_earned, flat) + remainder = max_(gross_earned - flat, 0) + return flat_disregard + remainder * p.earned.rate From 3038b5e5ce080cca1aacebb8882e08dc10e01e71 Mon Sep 17 00:00:00 2001 From: Ziming Date: Thu, 30 Apr 2026 21:34:58 -0400 Subject: [PATCH 04/17] Add lessons from Minnesota MSA implementation Co-Authored-By: Claude Opus 4.7 (1M context) --- lessons/agent-lessons.md | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 lessons/agent-lessons.md diff --git a/lessons/agent-lessons.md b/lessons/agent-lessons.md new file mode 100644 index 00000000000..9bd24e4d098 --- /dev/null +++ b/lessons/agent-lessons.md @@ -0,0 +1,27 @@ +# Agent Lessons Learned + +Accumulated from /encode-policy-v2 and /backdate-program runs across all contributors. +Loaded by implementation agents on future runs. + + +## New Lessons from Minnesota MSA (2026-04-30) + +### FORMULA +- When a state SSP grant formula subtracts the federal benefit separately (e.g., `standard - federal_ssi - countable_income`), applying the $20 general disregard inside the countable-income variable does NOT reduce the federal-benefit portion; the disregard must be applied at the grant-calculation step against the combined `(federal_benefit + other_unearned)`, not against `other_unearned` alone. Otherwise the SSI track under-grants by $20/mo for the dominant cohort with no other unearned income. +- When a state SSP grant formula stacks federal-SSI-style disregards (e.g., $20 general + $65 + 1/2 of remainder), gate the earned-disregard branch on `~receives_ssi`; the federal SSI calculation has already absorbed the earned-side disregards, so reapplying them on the state side double-counts. +- When the federal SSI methodology rolls leftover general-disregard from unearned to earned (`leftover_general = max_(20 - unearned, 0)` added to earned-disregard flat), document explicitly whether the state regulation endorses this rollover; state manuals often only restrict the earned-only disregards and are silent on whether the general $20 rolls — pick a defensible interpretation and add a code comment citing the basis. + +### TEST +- For SSP track-specific formulas, always include at least one test case where an SSI recipient also has non-zero non-SSI unearned income (e.g., pension, child support, Social Security); SSI-only test cases mask bugs in disregard handling because the countable-unearned variable correctly returns zero when federal SSI consumed everything, hiding whether the formula works for mixed-income SSI recipients. +- Verify SSP empirical math against the SSA "State Assistance Programs for SSI Recipients" Table 1 state-portion column: compute `combined - federal_FBR` and compare to the listed state portion; a $20 discrepancy in either direction is the signature of a missing-or-double-applied general income disregard at the grant-calculation step. +- When backdating an SSP to multiple eras (e.g., 2011, 2024, 2026), include at least one test case per era; era-specific parameter entries with no matching test silently mis-extrapolate values from adjacent eras and are invisible to the typical "2024 or 2026" test cluster. +- When a parameter introduces a new disregard (e.g., $20 general) into an existing formula, sweep ALL pre-existing test files that reference the affected variable and rebase expected outputs; stale expected values from before the disregard was added cause cascading test failures that look like formula bugs but are actually outdated fixtures. + +### REFERENCE +- For multi-era assistance-standard parameters (e.g., 2011 / 2024 / 2026), each era often has a different authoritative source (SSA factsheet for old era, House Research / state manual for current era); since YAML doesn't support date-keyed references, list multiple references and use the title field to indicate which source covers which era (e.g., `"SSA Table 1 ($20 value only, 2011)"`). +- When a state regulation manual section is cited for a parameter but the section's text does not actually authorize that value (e.g., CM 0018.18 covers earned disregards only, not the $20 general disregard), drop the manual cite and use the secondary explanatory source (e.g., House Research) as primary; do not bundle a non-authoritative cite alongside the real authority. +- The SSA "State Assistance Programs for SSI Recipients" series (last published 2011) is hosted on SSA.gov which blocks automated access (403/Akamai); when relying on this source, present it at the user-checkpoint phase for manual download rather than letting document-collector agents repeatedly fail and proceed with incomplete data. + +### WORKFLOW +- For SSP programs with grandfathered or closed-cohort categories (e.g., pre-1994 couple eligibility), explicitly present the full-vs-reduced enum scope as a Phase 2 user decision rather than implementing all categories by default; closed cohorts are real regulatory features but rarely affect the modeled population, so the user should choose whether the implementation cost is justified. +- Coverage reports must be regenerated after each round of fixes, not relied on from earlier rounds; a report that still references a pre-fix variable name (e.g., `is_ssi_eligible | (ssi > 0)` when the code now uses `is_ssi_aged_blind_disabled`) wastes review effort and triggers false flags. From 19142e95fbc5b159e7efc5d11b8bd132b26f0b91 Mon Sep 17 00:00:00 2001 From: Ziming Date: Fri, 1 May 2026 10:45:42 -0400 Subject: [PATCH 05/17] Fix C8 SSI-track $20 disregard and simplify living-arrangement modeling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Apply $20 general disregard against (federal SSI + raw unearned) for SSI-track recipients in living-alone / living-with-others arrangements (Minn. Stat. § 256D.44 Subd. 1; House Research Oct 2024 p.3). Carve out Medicaid facility (FLA-D), where the $30 federal SSI is a strict personal-needs cap and no $20 disregard applies (SSA 2011 Table 1). - Convert mn_msa_living_arrangement to a bare-input enum and add mn_msa_payment_category formula override that maps the federal medical- treatment facility code to MEDICAID_FACILITY (Maine SSP pattern). - Delete redundant input bools mn_msa_lives_with_others, mn_msa_treated_as_living_alone, and mn_msa_in_group_residential. - Gate mn_msa_special_needs_total so MSA Housing Assistance and Shelter Need are mutually exclusive (both authorized under § 256D.44 Subd. 5(g); CM 0023.24 gives Housing Assistance precedence). - Verify Shelter Need formula source against Minn. Stat. § 256D.44 Subd. 5(g)(1): "one-half of the maximum federal Supplemental Security Income payment amount for a single individual" — ½ × FBR confirmed. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../shelter_need/fbr_multiplier.yaml | 2 +- .../eligibility/mn_msa_eligible_person.yaml | 8 - .../mn_msa_net_income_eligible.yaml | 6 - .../gov/states/mn/dhs/msa/integration.yaml | 74 +++---- .../mn/dhs/msa/mn_msa_living_arrangement.yaml | 194 +----------------- .../gov/states/mn/dhs/msa/mn_msa_person.yaml | 45 ++-- .../payment/mn_msa_assistance_standard.yaml | 93 +-------- .../msa/payment/mn_msa_payment_category.yaml | 42 ++++ .../mn/dhs/msa/mn_msa_in_group_residential.py | 13 -- .../mn/dhs/msa/mn_msa_lives_with_others.py | 13 -- .../mn/dhs/msa/mn_msa_living_arrangement.py | 55 ----- .../gov/states/mn/dhs/msa/mn_msa_person.py | 48 +++-- .../dhs/msa/mn_msa_treated_as_living_alone.py | 15 -- .../msa/payment/mn_msa_assistance_standard.py | 2 +- .../msa/payment/mn_msa_housing_assistance.py | 5 +- .../msa/payment/mn_msa_payment_category.py | 35 ++++ .../msa/payment/mn_msa_special_needs_total.py | 20 +- 17 files changed, 187 insertions(+), 483 deletions(-) create mode 100644 policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/payment/mn_msa_payment_category.yaml delete mode 100644 policyengine_us/variables/gov/states/mn/dhs/msa/mn_msa_in_group_residential.py delete mode 100644 policyengine_us/variables/gov/states/mn/dhs/msa/mn_msa_lives_with_others.py delete mode 100644 policyengine_us/variables/gov/states/mn/dhs/msa/mn_msa_treated_as_living_alone.py create mode 100644 policyengine_us/variables/gov/states/mn/dhs/msa/payment/mn_msa_payment_category.py diff --git a/policyengine_us/parameters/gov/states/mn/dhs/msa/special_needs/shelter_need/fbr_multiplier.yaml b/policyengine_us/parameters/gov/states/mn/dhs/msa/special_needs/shelter_need/fbr_multiplier.yaml index ef4e84a25af..340e5e96797 100644 --- a/policyengine_us/parameters/gov/states/mn/dhs/msa/special_needs/shelter_need/fbr_multiplier.yaml +++ b/policyengine_us/parameters/gov/states/mn/dhs/msa/special_needs/shelter_need/fbr_multiplier.yaml @@ -1,4 +1,4 @@ -description: Minnesota provides this multiple of the federal Supplemental Security Income individual benefit rate as the shelter need special-needs allowance under the Minnesota Supplemental Aid program. +description: Minnesota provides this multiple of the federal Supplemental Security Income individual benefit rate as the shelter need special-needs allowance under the Minnesota Supplemental Aid program. Per Minn. Stat. § 256D.44 Subd. 5(g)(1), the allowance equals one-half of the maximum federal SSI payment amount for a single individual in effect on July 1 each year. values: 1989-01-01: 0.5 diff --git a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/eligibility/mn_msa_eligible_person.yaml b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/eligibility/mn_msa_eligible_person.yaml index 4d4959cf3f1..729620b29d8 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/eligibility/mn_msa_eligible_person.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/eligibility/mn_msa_eligible_person.yaml @@ -8,8 +8,6 @@ is_disabled: false immigration_status: CITIZEN ssi_countable_resources: 1_000 - mn_msa_lives_with_others: false - mn_msa_treated_as_living_alone: false ssi: 11_928 # $994/mo employment_income: 0 marital_units: @@ -38,8 +36,6 @@ is_disabled: false immigration_status: CITIZEN ssi_countable_resources: 5_000 # below non-SSI $10k cap, above SSI $2k cap - mn_msa_lives_with_others: false - mn_msa_treated_as_living_alone: false ssi: 0 ssi_unearned_income: 12_000 # $1,000/mo unearned (above SSI FBR but below MSA standard) marital_units: @@ -70,8 +66,6 @@ is_disabled: false immigration_status: CITIZEN ssi_countable_resources: 500 - mn_msa_lives_with_others: false - mn_msa_treated_as_living_alone: false ssi: 0 marital_units: marital_unit: @@ -99,8 +93,6 @@ is_disabled: false immigration_status: CITIZEN ssi_countable_resources: 500 - mn_msa_lives_with_others: false - mn_msa_treated_as_living_alone: false ssi: 0 ssi_unearned_income: 24_000 # $2,000/mo > $1,055 standard marital_units: diff --git a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/eligibility/mn_msa_net_income_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/eligibility/mn_msa_net_income_eligible.yaml index d8bcd501b1b..b53bd853307 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/eligibility/mn_msa_net_income_eligible.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/eligibility/mn_msa_net_income_eligible.yaml @@ -8,8 +8,6 @@ is_disabled: false immigration_status: CITIZEN ssi_countable_resources: 5_000 - mn_msa_lives_with_others: false - mn_msa_treated_as_living_alone: false ssi: 0 ssi_unearned_income: 12_900 # $1,075/mo; minus $20 general = $1,055 countable marital_units: @@ -39,8 +37,6 @@ is_disabled: false immigration_status: CITIZEN ssi_countable_resources: 5_000 - mn_msa_lives_with_others: false - mn_msa_treated_as_living_alone: false ssi: 0 ssi_unearned_income: 12_912 # $1,076/mo; minus $20 general = $1,056 countable marital_units: @@ -70,8 +66,6 @@ is_disabled: false immigration_status: CITIZEN ssi_countable_resources: 5_000 - mn_msa_lives_with_others: false - mn_msa_treated_as_living_alone: false ssi: 0 ssi_unearned_income: 12_888 # $1,074/mo; minus $20 general = $1,054 countable marital_units: diff --git a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/integration.yaml b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/integration.yaml index 4e3a73bec15..665c3f82530 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/integration.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/integration.yaml @@ -9,8 +9,6 @@ is_disabled: false immigration_status: CITIZEN ssi_countable_resources: 1_000 - mn_msa_lives_with_others: false - mn_msa_treated_as_living_alone: false ssi: 11_928 # $994/mo ssi_earned_income: 0 ssi_unearned_income: 0 @@ -33,9 +31,9 @@ mn_msa_assistance_standard: 1_055 mn_msa_countable_income: 0 mn_msa_eligible_person: true - # Need $1,055 - federal SSI $994 - countable $0 = $61. - mn_msa_person: 61 - mn_msa: 61 + # Need $1,055 - max($994 SSI - $20 general, 0) - countable $0 = $81. + mn_msa_person: 81 + mn_msa: 81 - name: Case 2, SSI couple living with others gets the couple-rate gating. period: 2026-01 @@ -48,8 +46,7 @@ is_disabled: false immigration_status: CITIZEN ssi_countable_resources: 800 - mn_msa_lives_with_others: true - mn_msa_treated_as_living_alone: false + mn_msa_living_arrangement: COUPLE_LIVING_WITH_OTHERS ssi_claim_is_joint: true ssi: 8_946 # $745.50/mo (couple FBR $1,491 / 2) ssi_earned_income: 0 @@ -60,8 +57,7 @@ is_disabled: false immigration_status: CITIZEN ssi_countable_resources: 800 - mn_msa_lives_with_others: true - mn_msa_treated_as_living_alone: false + mn_msa_living_arrangement: COUPLE_LIVING_WITH_OTHERS ssi_claim_is_joint: true ssi: 8_946 ssi_earned_income: 0 @@ -100,8 +96,6 @@ is_disabled: false immigration_status: CITIZEN ssi_countable_resources: 5_000 # above $2k SSI cap, below $10k non-SSI cap - mn_msa_lives_with_others: false - mn_msa_treated_as_living_alone: false ssi: 0 ssi_earned_income: 0 ssi_unearned_income: 12_000 # $1,000/mo @@ -141,8 +135,6 @@ ssi_countable_resources: 200 ssi_lives_in_medical_treatment_facility: true ssi_medicaid_pays_majority_of_care: true - mn_msa_lives_with_others: false - mn_msa_treated_as_living_alone: false ssi: 360 # $30/mo FLA-D ssi_earned_income: 0 ssi_unearned_income: 0 @@ -160,7 +152,10 @@ members: [person1] state_code: MN output: - mn_msa_living_arrangement: MEDICAID_FACILITY + # The bare-input mn_msa_living_arrangement defaults to INDIVIDUAL_LIVING_ALONE; + # the federal medical-facility status is detected by mn_msa_payment_category, + # which the assistance_standard formula reads. + mn_msa_payment_category: MEDICAID_FACILITY mn_msa_assistance_standard: 162 mn_msa_countable_income: 0 mn_msa_eligible_person: true @@ -179,8 +174,6 @@ is_disabled: true immigration_status: CITIZEN ssi_countable_resources: 500 - mn_msa_lives_with_others: false - mn_msa_treated_as_living_alone: false mn_msa_housing_assistance_eligible: true ssi: 11_928 ssi_earned_income: 0 @@ -203,9 +196,9 @@ mn_msa_assistance_standard: 1_055 mn_msa_housing_assistance: 497 mn_msa_eligible_person: true - # Need $1,055 + housing add-on $497 = $1,552; less SSI $994 - countable $0 = $558. - mn_msa_person: 558 - mn_msa: 558 + # Base: $1,055 - max($994 SSI - $20, 0) = $81; plus $497 housing add-on = $578. + mn_msa_person: 578 + mn_msa: 578 - name: Case 6, negative countable income does not inflate benefit. period: 2026-01 @@ -218,8 +211,6 @@ is_disabled: false immigration_status: CITIZEN ssi_countable_resources: 500 - mn_msa_lives_with_others: false - mn_msa_treated_as_living_alone: false ssi: 11_928 self_employment_income: -60_000_000 # extreme negative marital_units: @@ -237,10 +228,11 @@ state_code: MN output: # Countable income components are floored at 0; benefit cannot exceed need - SSI. + # $1,055 - max($994 SSI - $20 general, 0) = $1,055 - $974 = $81. mn_msa_assistance_standard: 1_055 mn_msa_countable_income: 0 - mn_msa_person: 61 - mn_msa: 61 + mn_msa_person: 81 + mn_msa: 81 - name: Case 7, non-ABD adult is ineligible. period: 2026-01 @@ -253,8 +245,6 @@ is_disabled: false immigration_status: CITIZEN ssi_countable_resources: 500 - mn_msa_lives_with_others: false - mn_msa_treated_as_living_alone: false ssi: 0 marital_units: marital_unit: @@ -286,8 +276,6 @@ is_disabled: false immigration_status: CITIZEN ssi_countable_resources: 1_000 - mn_msa_lives_with_others: false - mn_msa_treated_as_living_alone: false ssi: 11_316 # $943/mo (2024 federal individual FBR) ssi_earned_income: 0 ssi_unearned_income: 0 @@ -308,9 +296,9 @@ mn_msa_living_arrangement: INDIVIDUAL_LIVING_ALONE mn_msa_assistance_standard: 1_004 mn_msa_eligible_person: true - # Need $1,004 - federal SSI $943 - countable $0 = $61. - mn_msa_person: 61 - mn_msa: 61 + # Need $1,004 - max($943 SSI - $20 general, 0) = $1,004 - $923 = $81. + mn_msa_person: 81 + mn_msa: 81 - name: Case 9, asymmetric eligibility couple — only one spouse SSI-eligible. period: 2026-01 @@ -323,8 +311,6 @@ is_disabled: false immigration_status: CITIZEN ssi_countable_resources: 800 - mn_msa_lives_with_others: false - mn_msa_treated_as_living_alone: false ssi_claim_is_joint: false ssi: 11_928 # eligible spouse, full FBR ssi_earned_income: 0 @@ -335,8 +321,6 @@ is_disabled: false immigration_status: CITIZEN ssi_countable_resources: 800 - mn_msa_lives_with_others: false - mn_msa_treated_as_living_alone: false ssi_claim_is_joint: false ssi: 0 # ineligible spouse, not aged/blind/disabled marital_units: @@ -354,12 +338,14 @@ state_code: MN output: # Only one spouse is SSI-eligible -> individual rate, not couple rate. - mn_msa_living_arrangement: [INDIVIDUAL_LIVING_ALONE, NONE] + # Both spouses default to INDIVIDUAL_LIVING_ALONE; ineligible spouse is + # zeroed out by the mn_msa_eligible_person filter. + mn_msa_living_arrangement: [INDIVIDUAL_LIVING_ALONE, INDIVIDUAL_LIVING_ALONE] mn_msa_eligible_person: [true, false] - # person1: $1,055 - $994 - $0 = $61. + # person1: $1,055 - max($994 SSI - $20 general, 0) = $1,055 - $974 = $81. # person2: not categorically eligible. - mn_msa_person: [61, 0] - mn_msa: 61 + mn_msa_person: [81, 0] + mn_msa: 81 - name: Case 10, recipient with shelter-need allowance add-on. period: 2026-01 @@ -372,8 +358,6 @@ is_disabled: false immigration_status: CITIZEN ssi_countable_resources: 500 - mn_msa_lives_with_others: false - mn_msa_treated_as_living_alone: false mn_msa_shelter_need_eligible: true ssi: 11_928 ssi_earned_income: 0 @@ -395,9 +379,9 @@ mn_msa_assistance_standard: 1_055 mn_msa_shelter_need_allowance: 497 # 0.5 * $994 mn_msa_eligible_person: true - # Base: $1,055 - $994 - $0 = $61; plus $497 shelter-need add-on = $558. - mn_msa_person: 558 - mn_msa: 558 + # Base: $1,055 - max($994 SSI - $20, 0) = $81; plus $497 shelter-need add-on = $578. + mn_msa_person: 578 + mn_msa: 578 - name: Case 11, recipient with representative payee fee and guardian fee combined. period: 2026-01 @@ -410,8 +394,6 @@ is_disabled: false immigration_status: CITIZEN ssi_countable_resources: 500 - mn_msa_lives_with_others: false - mn_msa_treated_as_living_alone: false mn_msa_uses_representative_payee: true mn_msa_has_guardian: true ssi: 11_928 # $994/mo federal SSI @@ -451,8 +433,6 @@ is_disabled: false immigration_status: UNDOCUMENTED ssi_countable_resources: 500 - mn_msa_lives_with_others: false - mn_msa_treated_as_living_alone: false ssi: 0 ssi_unearned_income: 0 marital_units: diff --git a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/mn_msa_living_arrangement.yaml b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/mn_msa_living_arrangement.yaml index 435831ec00d..27afb28f4f7 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/mn_msa_living_arrangement.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/mn_msa_living_arrangement.yaml @@ -1,26 +1,9 @@ -- name: Case 1, single SSI-eligible person living alone. +- name: Case 1, default value is INDIVIDUAL_LIVING_ALONE. period: 2026-01 input: people: person1: age: 70 - is_blind: false - is_disabled: false - immigration_status: CITIZEN - ssi_countable_resources: 1_000 - mn_msa_lives_with_others: false - mn_msa_treated_as_living_alone: false - ssi: 11_928 # $994/mo - employment_income: 0 - marital_units: - marital_unit: - members: [person1] - tax_units: - tax_unit: - members: [person1] - spm_units: - spm_unit: - members: [person1] households: household: members: [person1] @@ -28,29 +11,13 @@ output: mn_msa_living_arrangement: INDIVIDUAL_LIVING_ALONE -- name: Case 2, single SSI-eligible person living with others. +- name: Case 2, user-provided INDIVIDUAL_LIVING_WITH_OTHERS passes through. period: 2026-01 input: people: person1: age: 70 - is_blind: false - is_disabled: false - immigration_status: CITIZEN - ssi_countable_resources: 1_000 - mn_msa_lives_with_others: true - mn_msa_treated_as_living_alone: false - ssi: 11_928 - employment_income: 0 - marital_units: - marital_unit: - members: [person1] - tax_units: - tax_unit: - members: [person1] - spm_units: - spm_unit: - members: [person1] + mn_msa_living_arrangement: INDIVIDUAL_LIVING_WITH_OTHERS households: household: members: [person1] @@ -58,173 +25,30 @@ output: mn_msa_living_arrangement: INDIVIDUAL_LIVING_WITH_OTHERS -- name: Case 3, married SSI-eligible couple living alone joint claim. - period: 2026-01 - input: - people: - person1: - age: 70 - is_blind: false - is_disabled: false - immigration_status: CITIZEN - ssi_countable_resources: 800 - mn_msa_lives_with_others: false - mn_msa_treated_as_living_alone: false - ssi_claim_is_joint: true - ssi: 8_946 # half of couple FBR $1,491 - person2: - age: 68 - is_blind: false - is_disabled: false - immigration_status: CITIZEN - ssi_countable_resources: 800 - mn_msa_lives_with_others: false - mn_msa_treated_as_living_alone: false - ssi_claim_is_joint: true - ssi: 8_946 - 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: MN - output: - mn_msa_living_arrangement: [COUPLE_LIVING_ALONE, COUPLE_LIVING_ALONE] - -- name: Case 4, married SSI-eligible couple living with others joint claim. +- name: Case 3, user-provided COUPLE_LIVING_ALONE passes through. period: 2026-01 input: people: person1: - age: 72 - is_blind: false - is_disabled: false - immigration_status: CITIZEN - ssi_countable_resources: 800 - mn_msa_lives_with_others: true - mn_msa_treated_as_living_alone: false - ssi_claim_is_joint: true - ssi: 8_946 - person2: age: 70 - is_blind: false - is_disabled: false - immigration_status: CITIZEN - ssi_countable_resources: 800 - mn_msa_lives_with_others: true - mn_msa_treated_as_living_alone: false - ssi_claim_is_joint: true - ssi: 8_946 - marital_units: - marital_unit: - members: [person1, person2] - tax_units: - tax_unit: - members: [person1, person2] - spm_units: - spm_unit: - members: [person1, person2] + mn_msa_living_arrangement: COUPLE_LIVING_ALONE households: household: - members: [person1, person2] + members: [person1] state_code: MN output: - mn_msa_living_arrangement: [COUPLE_LIVING_WITH_OTHERS, COUPLE_LIVING_WITH_OTHERS] + mn_msa_living_arrangement: COUPLE_LIVING_ALONE -- name: Case 5, person in Medicaid facility. +- name: Case 4, user-provided MEDICAID_FACILITY passes through. period: 2026-01 input: people: person1: age: 80 - is_blind: false - is_disabled: false - immigration_status: CITIZEN - ssi_countable_resources: 200 - ssi_lives_in_medical_treatment_facility: true - ssi_medicaid_pays_majority_of_care: true - mn_msa_lives_with_others: false - mn_msa_treated_as_living_alone: false - ssi: 360 # $30/mo FLA-D - marital_units: - marital_unit: - members: [person1] - tax_units: - tax_unit: - members: [person1] - spm_units: - spm_unit: - members: [person1] + mn_msa_living_arrangement: MEDICAID_FACILITY households: household: members: [person1] state_code: MN output: mn_msa_living_arrangement: MEDICAID_FACILITY - -- name: Case 6, HCBS treated-as-living-alone overrides cohabitation. - period: 2026-01 - input: - people: - person1: - age: 60 - is_blind: false - is_disabled: true - immigration_status: CITIZEN - ssi_countable_resources: 500 - mn_msa_lives_with_others: true - mn_msa_treated_as_living_alone: true - ssi: 11_928 - marital_units: - marital_unit: - members: [person1] - tax_units: - tax_unit: - members: [person1] - spm_units: - spm_unit: - members: [person1] - households: - household: - members: [person1] - state_code: MN - output: - # Treated-as-living-alone forces INDIVIDUAL_LIVING_ALONE despite cohabitation. - mn_msa_living_arrangement: INDIVIDUAL_LIVING_ALONE - -- name: Case 7, non-categorically-eligible person defaults to NONE. - period: 2026-01 - input: - people: - person1: - age: 40 - is_blind: false - is_disabled: false - immigration_status: CITIZEN - ssi_countable_resources: 500 - mn_msa_lives_with_others: false - mn_msa_treated_as_living_alone: false - ssi: 0 - marital_units: - marital_unit: - members: [person1] - tax_units: - tax_unit: - members: [person1] - spm_units: - spm_unit: - members: [person1] - households: - household: - members: [person1] - state_code: MN - output: - # Not aged/blind/disabled -> not categorically eligible -> NONE. - mn_msa_living_arrangement: NONE diff --git a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/mn_msa_person.yaml b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/mn_msa_person.yaml index 59057ad024f..12da9c29136 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/mn_msa_person.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/mn_msa_person.yaml @@ -9,8 +9,6 @@ is_disabled: false immigration_status: CITIZEN ssi_countable_resources: 500 - mn_msa_lives_with_others: false - mn_msa_treated_as_living_alone: false ssi: 11_928 # $994/mo ssi_earned_income: 0 ssi_unearned_income: 0 @@ -28,8 +26,8 @@ members: [person1] state_code: MN output: - # Need standard $1,055 - federal SSI $994 - countable income $0 = $61. - mn_msa_person: 61 + # Need standard $1,055 - max($994 SSI - $20 general, 0) - countable $0 = $81. + mn_msa_person: 81 - name: Case 2, non-SSI track aged person with $1,000/mo unearned income living alone. period: 2026-01 @@ -42,8 +40,6 @@ is_disabled: false immigration_status: CITIZEN ssi_countable_resources: 5_000 - mn_msa_lives_with_others: false - mn_msa_treated_as_living_alone: false ssi: 0 ssi_unearned_income: 12_000 # $1,000/mo marital_units: @@ -76,8 +72,6 @@ ssi_countable_resources: 200 ssi_lives_in_medical_treatment_facility: true ssi_medicaid_pays_majority_of_care: true - mn_msa_lives_with_others: false - mn_msa_treated_as_living_alone: false ssi: 360 # $30/mo FLA-D ssi_earned_income: 0 ssi_unearned_income: 0 @@ -109,8 +103,7 @@ is_disabled: false immigration_status: CITIZEN ssi_countable_resources: 800 - mn_msa_lives_with_others: false - mn_msa_treated_as_living_alone: false + mn_msa_living_arrangement: COUPLE_LIVING_ALONE ssi_claim_is_joint: true ssi: 8_946 # $745.50/mo (couple FBR $1,491 / 2) ssi_earned_income: 0 @@ -121,8 +114,7 @@ is_disabled: false immigration_status: CITIZEN ssi_countable_resources: 800 - mn_msa_lives_with_others: false - mn_msa_treated_as_living_alone: false + mn_msa_living_arrangement: COUPLE_LIVING_ALONE ssi_claim_is_joint: true ssi: 8_946 ssi_earned_income: 0 @@ -141,8 +133,8 @@ members: [person1, person2] state_code: MN output: - # Per person: $1,582 / 2 = $791 standard - SSI $745.50 - countable $0 = $45.50. - mn_msa_person: [45.50, 45.50] + # Per person: $1,582 / 2 = $791 standard - max($745.50 SSI - $20, 0) = $791 - $725.50 = $65.50. + mn_msa_person: [65.50, 65.50] - name: Case 5, asymmetric eligibility couple — only one spouse SSI-eligible. period: 2026-01 @@ -155,8 +147,6 @@ is_disabled: false immigration_status: CITIZEN ssi_countable_resources: 800 - mn_msa_lives_with_others: false - mn_msa_treated_as_living_alone: false ssi_claim_is_joint: false ssi: 11_928 # eligible spouse, full FBR ssi_earned_income: 0 @@ -167,8 +157,6 @@ is_disabled: false immigration_status: CITIZEN ssi_countable_resources: 800 - mn_msa_lives_with_others: false - mn_msa_treated_as_living_alone: false ssi_claim_is_joint: false ssi: 0 # ineligible spouse, not aged/blind/disabled marital_units: @@ -185,9 +173,10 @@ members: [person1, person2] state_code: MN output: - # Couple gate fails -> individual rate for eligible spouse: $1,055 - $994 - $0 = $61. + # Eligible spouse defaults to INDIVIDUAL_LIVING_ALONE: + # $1,055 - max($994 SSI - $20, 0) = $1,055 - $974 = $81. # Ineligible spouse zeroed by mn_msa_eligible_person filter. - mn_msa_person: [61, 0] + mn_msa_person: [81, 0] - name: Case 6, 2024 individual living alone with SSI receipt. period: 2024-01 @@ -200,8 +189,6 @@ is_disabled: false immigration_status: CITIZEN ssi_countable_resources: 500 - mn_msa_lives_with_others: false - mn_msa_treated_as_living_alone: false ssi: 11_316 # $943/mo (2024 federal individual FBR) ssi_earned_income: 0 ssi_unearned_income: 0 @@ -219,10 +206,10 @@ members: [person1] state_code: MN output: - # 2024: $1,004 standard - $943 SSI - $0 countable = $61. - mn_msa_person: 61 + # 2024: $1,004 standard - max($943 SSI - $20, 0) = $1,004 - $923 = $81. + mn_msa_person: 81 -- name: Case 7, marital unit of size 1 with ssi_claim_is_joint=true cannot trigger couple gate. +- name: Case 7, single-person marital unit defaults to individual rate. period: 2026-01 absolute_error_margin: 0.01 input: @@ -233,9 +220,6 @@ is_disabled: false immigration_status: CITIZEN ssi_countable_resources: 500 - mn_msa_lives_with_others: false - mn_msa_treated_as_living_alone: false - ssi_claim_is_joint: true # spurious; marital_unit.sum cannot reach 2 ssi: 11_928 # $994/mo ssi_earned_income: 0 ssi_unearned_income: 0 @@ -253,6 +237,7 @@ members: [person1] state_code: MN output: - # Couple gate unreachable for size-1 marital unit -> individual rate. + # Default INDIVIDUAL_LIVING_ALONE: + # $1,055 - max($994 SSI - $20, 0) = $1,055 - $974 = $81. mn_msa_living_arrangement: INDIVIDUAL_LIVING_ALONE - mn_msa_person: 61 + mn_msa_person: 81 diff --git a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/payment/mn_msa_assistance_standard.yaml b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/payment/mn_msa_assistance_standard.yaml index 7fc91021caa..1f6aa5a6606 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/payment/mn_msa_assistance_standard.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/payment/mn_msa_assistance_standard.yaml @@ -4,22 +4,11 @@ people: person1: age: 70 - is_blind: false - is_disabled: false - immigration_status: CITIZEN ssi_countable_resources: 500 - mn_msa_lives_with_others: false - mn_msa_treated_as_living_alone: false ssi: 11_928 marital_units: marital_unit: members: [person1] - tax_units: - tax_unit: - members: [person1] - spm_units: - spm_unit: - members: [person1] households: household: members: [person1] @@ -33,22 +22,12 @@ people: person1: age: 70 - is_blind: false - is_disabled: false - immigration_status: CITIZEN ssi_countable_resources: 500 - mn_msa_lives_with_others: true - mn_msa_treated_as_living_alone: false + mn_msa_living_arrangement: INDIVIDUAL_LIVING_WITH_OTHERS ssi: 11_928 marital_units: marital_unit: members: [person1] - tax_units: - tax_unit: - members: [person1] - spm_units: - spm_unit: - members: [person1] households: household: members: [person1] @@ -62,33 +41,19 @@ people: person1: age: 70 - is_blind: false - is_disabled: false - immigration_status: CITIZEN ssi_countable_resources: 800 - mn_msa_lives_with_others: false - mn_msa_treated_as_living_alone: false + mn_msa_living_arrangement: COUPLE_LIVING_ALONE ssi_claim_is_joint: true ssi: 8_946 person2: age: 68 - is_blind: false - is_disabled: false - immigration_status: CITIZEN ssi_countable_resources: 800 - mn_msa_lives_with_others: false - mn_msa_treated_as_living_alone: false + mn_msa_living_arrangement: COUPLE_LIVING_ALONE ssi_claim_is_joint: true ssi: 8_946 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] @@ -102,33 +67,19 @@ people: person1: age: 72 - is_blind: false - is_disabled: false - immigration_status: CITIZEN ssi_countable_resources: 800 - mn_msa_lives_with_others: true - mn_msa_treated_as_living_alone: false + mn_msa_living_arrangement: COUPLE_LIVING_WITH_OTHERS ssi_claim_is_joint: true ssi: 8_946 person2: age: 70 - is_blind: false - is_disabled: false - immigration_status: CITIZEN ssi_countable_resources: 800 - mn_msa_lives_with_others: true - mn_msa_treated_as_living_alone: false + mn_msa_living_arrangement: COUPLE_LIVING_WITH_OTHERS ssi_claim_is_joint: true ssi: 8_946 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] @@ -142,24 +93,13 @@ people: person1: age: 80 - is_blind: false - is_disabled: false - immigration_status: CITIZEN ssi_countable_resources: 200 ssi_lives_in_medical_treatment_facility: true ssi_medicaid_pays_majority_of_care: true - mn_msa_lives_with_others: false - mn_msa_treated_as_living_alone: false ssi: 360 marital_units: marital_unit: members: [person1] - tax_units: - tax_unit: - members: [person1] - spm_units: - spm_unit: - members: [person1] households: household: members: [person1] @@ -173,22 +113,12 @@ people: person1: age: 40 - is_blind: false - is_disabled: false - immigration_status: CITIZEN ssi_countable_resources: 500 - mn_msa_lives_with_others: false - mn_msa_treated_as_living_alone: false + mn_msa_living_arrangement: NONE ssi: 0 marital_units: marital_unit: members: [person1] - tax_units: - tax_unit: - members: [person1] - spm_units: - spm_unit: - members: [person1] households: household: members: [person1] @@ -202,22 +132,11 @@ people: person1: age: 70 - is_blind: false - is_disabled: false - immigration_status: CITIZEN ssi_countable_resources: 500 - mn_msa_lives_with_others: false - mn_msa_treated_as_living_alone: false ssi: 11_316 # $943/mo for 2024 marital_units: marital_unit: members: [person1] - tax_units: - tax_unit: - members: [person1] - spm_units: - spm_unit: - members: [person1] households: household: members: [person1] diff --git a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/payment/mn_msa_payment_category.yaml b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/payment/mn_msa_payment_category.yaml new file mode 100644 index 00000000000..5d290d0b7ec --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/payment/mn_msa_payment_category.yaml @@ -0,0 +1,42 @@ +- name: Case 1, default category mirrors the bare-input default. + period: 2026-01 + input: + people: + person1: + age: 70 + households: + household: + members: [person1] + state_code: MN + output: + mn_msa_payment_category: INDIVIDUAL_LIVING_ALONE + +- name: Case 2, user-provided arrangement passes through when not in a Medicaid facility. + period: 2026-01 + input: + people: + person1: + age: 70 + mn_msa_living_arrangement: INDIVIDUAL_LIVING_WITH_OTHERS + households: + household: + members: [person1] + state_code: MN + output: + mn_msa_payment_category: INDIVIDUAL_LIVING_WITH_OTHERS + +- name: Case 3, federal Medicaid-facility status overrides the input arrangement. + period: 2026-01 + input: + people: + person1: + age: 80 + mn_msa_living_arrangement: INDIVIDUAL_LIVING_ALONE + ssi_lives_in_medical_treatment_facility: true + ssi_medicaid_pays_majority_of_care: true + households: + household: + members: [person1] + state_code: MN + output: + mn_msa_payment_category: MEDICAID_FACILITY diff --git a/policyengine_us/variables/gov/states/mn/dhs/msa/mn_msa_in_group_residential.py b/policyengine_us/variables/gov/states/mn/dhs/msa/mn_msa_in_group_residential.py deleted file mode 100644 index 222b9c2ad57..00000000000 --- a/policyengine_us/variables/gov/states/mn/dhs/msa/mn_msa_in_group_residential.py +++ /dev/null @@ -1,13 +0,0 @@ -from policyengine_us.model_api import * - - -class mn_msa_in_group_residential(Variable): - value_type = bool - entity = Person - label = "Whether the person resides in Group Residential Housing under Minnesota Supplemental Aid" - definition_period = MONTH - defined_for = StateCode.MN - reference = ( - "https://www.revisor.mn.gov/statutes/cite/256I", - "https://www.dhs.state.mn.us/main/groups/county_access/documents/pub/mndhs-073585.pdf#page=4", - ) diff --git a/policyengine_us/variables/gov/states/mn/dhs/msa/mn_msa_lives_with_others.py b/policyengine_us/variables/gov/states/mn/dhs/msa/mn_msa_lives_with_others.py deleted file mode 100644 index 7b44eeaa400..00000000000 --- a/policyengine_us/variables/gov/states/mn/dhs/msa/mn_msa_lives_with_others.py +++ /dev/null @@ -1,13 +0,0 @@ -from policyengine_us.model_api import * - - -class mn_msa_lives_with_others(Variable): - value_type = bool - entity = Person - label = "Whether the person cohabits with persons outside the marital unit under Minnesota Supplemental Aid" - definition_period = MONTH - defined_for = StateCode.MN - reference = ( - "https://www.revisor.mn.gov/statutes/cite/256D.44", - "https://www.dhs.state.mn.us/main/groups/county_access/documents/pub/mndhs-073585.pdf#page=4", - ) diff --git a/policyengine_us/variables/gov/states/mn/dhs/msa/mn_msa_living_arrangement.py b/policyengine_us/variables/gov/states/mn/dhs/msa/mn_msa_living_arrangement.py index 6208e33585c..103d442bf43 100644 --- a/policyengine_us/variables/gov/states/mn/dhs/msa/mn_msa_living_arrangement.py +++ b/policyengine_us/variables/gov/states/mn/dhs/msa/mn_msa_living_arrangement.py @@ -23,58 +23,3 @@ class mn_msa_living_arrangement(Variable): "https://www.dhs.state.mn.us/main/groups/county_access/documents/pub/mndhs-073585.pdf#page=4", "https://www.house.mn.gov/hrd/pubs/pap_MSA.pdf#page=6", ) - - def formula(person, period, parameters): - # Per Minn. Stat. § 256D.44 Subd. 2 and Combined Manual 0020.21, - # the MSA assistance standard is keyed off of whether the recipient - # is a single person or part of a married couple (both eligible) - # and whether they live alone or share a household with others. - # Recipients of HCBS waivers, GRH plans, or MSA Housing Assistance - # are treated as living alone regardless of cohabitation. - federal_arrangement = person("ssi_federal_living_arrangement", period.this_year) - FED = federal_arrangement.possible_values - in_medicaid_facility = person("is_in_medicaid_facility", period.this_year) | ( - federal_arrangement == FED.MEDICAL_TREATMENT_FACILITY - ) - - # Couple gating: both spouses categorically eligible (ABD), joint - # claim. Avoids circular dependency through the MSA→SSI computation - # by using is_ssi_aged_blind_disabled rather than mn_msa_eligible_person. - categorically_eligible = person("mn_msa_categorically_eligible", period) - joint_claim = person("ssi_claim_is_joint", period.this_year) - both_eligible = ( - person.marital_unit.sum(categorically_eligible) == 2 - ) & joint_claim - - treated_as_living_alone = person("mn_msa_treated_as_living_alone", period) - lives_with_others = ( - person("mn_msa_lives_with_others", period) & ~treated_as_living_alone - ) - - LA = MNMSALivingArrangement - couple_alone = both_eligible & ~lives_with_others - couple_with_others = both_eligible & lives_with_others - individual_with_others = ( - categorically_eligible & ~both_eligible & lives_with_others - ) - individual_alone = categorically_eligible & ~both_eligible & ~lives_with_others - - return select( - [ - ~categorically_eligible, - in_medicaid_facility, - couple_alone, - couple_with_others, - individual_with_others, - individual_alone, - ], - [ - LA.NONE, - LA.MEDICAID_FACILITY, - LA.COUPLE_LIVING_ALONE, - LA.COUPLE_LIVING_WITH_OTHERS, - LA.INDIVIDUAL_LIVING_WITH_OTHERS, - LA.INDIVIDUAL_LIVING_ALONE, - ], - default=LA.NONE, - ) diff --git a/policyengine_us/variables/gov/states/mn/dhs/msa/mn_msa_person.py b/policyengine_us/variables/gov/states/mn/dhs/msa/mn_msa_person.py index 3caedaa723a..e22eb0b41b5 100644 --- a/policyengine_us/variables/gov/states/mn/dhs/msa/mn_msa_person.py +++ b/policyengine_us/variables/gov/states/mn/dhs/msa/mn_msa_person.py @@ -15,28 +15,48 @@ class mn_msa_person(Variable): ) def formula(person, period, parameters): - # Per Minn. Stat. § 256D.44 Subd. 1 and SSA 2011 baseline: - # for SSI-track recipients the federal SSI payment is deducted - # from the (combined federal+state) assistance standard, leaving - # the state portion. For non-SSI excess-income recipients there - # is no federal SSI deduction. Countable income further reduces - # the state benefit. Special-needs allowances add on top of the - # assistance-standard-derived base. - # The COUPLE_* assistance standards are couple totals, so split - # them 50/50 across the two eligible spouses. - # ssi is YEAR-defined with USD units, so accessing it from this - # MONTH formula auto-divides by 12. - arrangement = person("mn_msa_living_arrangement", period) + # Per Minn. Stat. § 256D.44 Subd. 1 and House Research (Oct 2024) p.3: + # for SSI-track recipients, the county counts the federal SSI grant as + # gross unearned income and then allows for a $20 general income + # disregard against the combined (federal SSI + other unearned). The + # federal SSI calculation already absorbed earned disregards, so MSA + # does not re-apply $65 + 1/2 to earned for the SSI track. + # For non-SSI excess-income recipients, there is no federal SSI; the + # countable_income variable applies the $20 + $65 + 1/2 disregards + # following the federal SSI methodology. + # The COUPLE_* assistance standards are couple totals, so split them + # 50/50 across the two eligible spouses. ssi is YEAR-defined with USD + # units, so accessing it from this MONTH formula auto-divides by 12. + # Empirical anchor: SSA 2011 Table 1 living-alone individual reports + # state portion = $81 = $735 standard - max($674 SSI - $20, 0). + arrangement = person("mn_msa_payment_category", period) LA = arrangement.possible_values is_couple_arrangement = (arrangement == LA.COUPLE_LIVING_ALONE) | ( arrangement == LA.COUPLE_LIVING_WITH_OTHERS ) standard = person("mn_msa_assistance_standard", period) per_person_standard = where(is_couple_arrangement, standard / 2, standard) - countable_income = person("mn_msa_countable_income", period) federal_ssi = person("ssi", period) receives_ssi = federal_ssi > 0 - ssi_track = max_(0, per_person_standard - federal_ssi - countable_income) + p = parameters(period).gov.states.mn.dhs.msa.disregard + raw_unearned = add( + person, + period, + [ + "ssi_unearned_income", + "ssi_unearned_income_deemed_from_ineligible_spouse", + ], + ) + # The $20 disregard applies to FLA-A (living alone) and FLA-B (with + # others) recipients but NOT to FLA-D (Medicaid facility) recipients, + # whose $30 federal SSI is a strict personal-needs cap. SSA 2011 + # Table 1: Medicaid-facility individual state portion = $59 = $89 - + # $30 with no $20 added back. + is_medicaid_facility = arrangement == LA.MEDICAID_FACILITY + disregard = where(is_medicaid_facility, 0, p.general) + ssi_track_countable = max_(federal_ssi + raw_unearned - disregard, 0) + ssi_track = max_(0, per_person_standard - ssi_track_countable) + countable_income = person("mn_msa_countable_income", period) non_ssi_track = max_(0, per_person_standard - countable_income) base = where(receives_ssi, ssi_track, non_ssi_track) special_needs = person("mn_msa_special_needs_total", period) diff --git a/policyengine_us/variables/gov/states/mn/dhs/msa/mn_msa_treated_as_living_alone.py b/policyengine_us/variables/gov/states/mn/dhs/msa/mn_msa_treated_as_living_alone.py deleted file mode 100644 index 8f2cec16584..00000000000 --- a/policyengine_us/variables/gov/states/mn/dhs/msa/mn_msa_treated_as_living_alone.py +++ /dev/null @@ -1,15 +0,0 @@ -from policyengine_us.model_api import * - - -class mn_msa_treated_as_living_alone(Variable): - value_type = bool - entity = Person - label = ( - "Whether the person is treated as living alone under Minnesota Supplemental Aid" - ) - definition_period = MONTH - defined_for = StateCode.MN - reference = ( - "https://www.revisor.mn.gov/statutes/cite/256D.44", - "https://www.dhs.state.mn.us/main/groups/county_access/documents/pub/mndhs-073585.pdf#page=4", - ) diff --git a/policyengine_us/variables/gov/states/mn/dhs/msa/payment/mn_msa_assistance_standard.py b/policyengine_us/variables/gov/states/mn/dhs/msa/payment/mn_msa_assistance_standard.py index f90d4f6f7a4..774077755b9 100644 --- a/policyengine_us/variables/gov/states/mn/dhs/msa/payment/mn_msa_assistance_standard.py +++ b/policyengine_us/variables/gov/states/mn/dhs/msa/payment/mn_msa_assistance_standard.py @@ -15,5 +15,5 @@ class mn_msa_assistance_standard(Variable): def formula(person, period, parameters): p = parameters(period).gov.states.mn.dhs.msa.assistance_standard - arrangement = person("mn_msa_living_arrangement", period) + arrangement = person("mn_msa_payment_category", period) return p.amount[arrangement] diff --git a/policyengine_us/variables/gov/states/mn/dhs/msa/payment/mn_msa_housing_assistance.py b/policyengine_us/variables/gov/states/mn/dhs/msa/payment/mn_msa_housing_assistance.py index 0350d573add..f6e324604c1 100644 --- a/policyengine_us/variables/gov/states/mn/dhs/msa/payment/mn_msa_housing_assistance.py +++ b/policyengine_us/variables/gov/states/mn/dhs/msa/payment/mn_msa_housing_assistance.py @@ -17,8 +17,9 @@ def formula(person, period, parameters): # Per Combined Manual 0023.24: MSA Housing Assistance equals # one half of the federal SSI individual benefit rate. Recipients # are also treated as living alone for assistance-standard - # selection — set mn_msa_treated_as_living_alone alongside this - # eligibility input to capture the joint effect. + # selection — set mn_msa_living_arrangement to + # INDIVIDUAL_LIVING_ALONE alongside this eligibility input to + # capture the joint effect. p = parameters(period).gov.states.mn.dhs.msa.housing_assistance ssi_fbr = parameters(period).gov.ssa.ssi.amount.individual eligible = person("mn_msa_housing_assistance_eligible", period) diff --git a/policyengine_us/variables/gov/states/mn/dhs/msa/payment/mn_msa_payment_category.py b/policyengine_us/variables/gov/states/mn/dhs/msa/payment/mn_msa_payment_category.py new file mode 100644 index 00000000000..fb635175741 --- /dev/null +++ b/policyengine_us/variables/gov/states/mn/dhs/msa/payment/mn_msa_payment_category.py @@ -0,0 +1,35 @@ +from policyengine_us.model_api import * +from policyengine_us.variables.gov.states.mn.dhs.msa.mn_msa_living_arrangement import ( + MNMSALivingArrangement, +) + + +class mn_msa_payment_category(Variable): + value_type = Enum + entity = Person + label = "Minnesota Supplemental Aid payment category" + definition_period = MONTH + defined_for = StateCode.MN + possible_values = MNMSALivingArrangement + default_value = MNMSALivingArrangement.INDIVIDUAL_LIVING_ALONE + reference = ( + "https://www.revisor.mn.gov/statutes/cite/256D.44", + "https://www.dhs.state.mn.us/main/groups/county_access/documents/pub/mndhs-073585.pdf#page=4", + ) + + def formula(person, period, parameters): + # Per Minn. Stat. § 256D.44 Subd. 2 and Combined Manual 0020.21, + # recipients receiving Medicaid-financed institutional care are paid + # under the FLA-D personal-needs cap regardless of their otherwise + # reported MSA living arrangement. + federal_arrangement = person("ssi_federal_living_arrangement", period.this_year) + federal_values = federal_arrangement.possible_values + in_medicaid_facility = person("is_in_medicaid_facility", period.this_year) | ( + federal_arrangement == federal_values.MEDICAL_TREATMENT_FACILITY + ) + living_arrangement = person("mn_msa_living_arrangement", period) + return where( + in_medicaid_facility, + MNMSALivingArrangement.MEDICAID_FACILITY, + living_arrangement, + ) diff --git a/policyengine_us/variables/gov/states/mn/dhs/msa/payment/mn_msa_special_needs_total.py b/policyengine_us/variables/gov/states/mn/dhs/msa/payment/mn_msa_special_needs_total.py index 2d60473c16d..0a2d036438a 100644 --- a/policyengine_us/variables/gov/states/mn/dhs/msa/payment/mn_msa_special_needs_total.py +++ b/policyengine_us/variables/gov/states/mn/dhs/msa/payment/mn_msa_special_needs_total.py @@ -13,9 +13,17 @@ class mn_msa_special_needs_total(Variable): "https://www.dhs.state.mn.us/main/groups/county_access/documents/pub/mndhs-073585.pdf#page=4", ) - adds = [ - "mn_msa_representative_payee_fee", - "mn_msa_guardian_fee", - "mn_msa_shelter_need_allowance", - "mn_msa_housing_assistance", - ] + def formula(person, period, parameters): + # Representative-payee and guardian fees stack with all other + # special-needs items. The shelter-need allowance (Minn. Stat. + # § 256D.44 Subd. 5(g)) and MSA Housing Assistance (Combined + # Manual 0023.24) are mutually exclusive — they serve different + # housing situations and a recipient cannot receive both for the + # same arrangement. When both eligibility flags are set, MSA + # Housing Assistance takes precedence as the stricter program. + rep_payee_fee = person("mn_msa_representative_payee_fee", period) + guardian_fee = person("mn_msa_guardian_fee", period) + shelter_need = person("mn_msa_shelter_need_allowance", period) + housing_assistance = person("mn_msa_housing_assistance", period) + housing_aid = where(housing_assistance > 0, housing_assistance, shelter_need) + return rep_payee_fee + guardian_fee + housing_aid From cd1c14a93ebfa2b578d95e077238279469503bac Mon Sep 17 00:00:00 2001 From: Ziming Date: Fri, 1 May 2026 11:04:45 -0400 Subject: [PATCH 06/17] Collapse Shelter Need into MSA Housing Assistance (single Subd. 5(g) variable) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both variables encoded the same formula (½ × federal SSI individual FBR) under Minn. Stat. § 256D.44 Subd. 5(g). They represented two eligibility pathways into the same statutory benefit: - Institution-relocation / HCBS-waiver / shelter > 40% income (Subd. 5(g)(1)) - Chronic homelessness / housing stabilization (CM 0023.24) Keep `mn_msa_housing_assistance` as the surviving variable; drop the parallel `mn_msa_shelter_need_allowance`. Mutual-exclusion gating in `mn_msa_special_needs_total` is no longer needed and reverts to a clean `adds = [...]` summation. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../housing_assistance/fbr_multiplier.yaml | 6 +- .../shelter_need/fbr_multiplier.yaml | 14 ----- .../gov/states/mn/dhs/msa/integration.yaml | 8 +-- .../mn_msa_shelter_need_allowance.yaml | 56 ------------------- .../msa/mn_msa_housing_assistance_eligible.py | 10 +++- .../dhs/msa/mn_msa_shelter_need_eligible.py | 13 ----- .../msa/payment/mn_msa_housing_assistance.py | 17 +++--- .../payment/mn_msa_shelter_need_allowance.py | 23 -------- .../msa/payment/mn_msa_special_needs_total.py | 20 ++----- 9 files changed, 29 insertions(+), 138 deletions(-) delete mode 100644 policyengine_us/parameters/gov/states/mn/dhs/msa/special_needs/shelter_need/fbr_multiplier.yaml delete mode 100644 policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/payment/mn_msa_shelter_need_allowance.yaml delete mode 100644 policyengine_us/variables/gov/states/mn/dhs/msa/mn_msa_shelter_need_eligible.py delete mode 100644 policyengine_us/variables/gov/states/mn/dhs/msa/payment/mn_msa_shelter_need_allowance.py diff --git a/policyengine_us/parameters/gov/states/mn/dhs/msa/housing_assistance/fbr_multiplier.yaml b/policyengine_us/parameters/gov/states/mn/dhs/msa/housing_assistance/fbr_multiplier.yaml index 3b878324c8e..9bf3da8fcdf 100644 --- a/policyengine_us/parameters/gov/states/mn/dhs/msa/housing_assistance/fbr_multiplier.yaml +++ b/policyengine_us/parameters/gov/states/mn/dhs/msa/housing_assistance/fbr_multiplier.yaml @@ -1,4 +1,4 @@ -description: Minnesota provides housing assistance at this multiple of the federal Supplemental Security Income individual benefit rate under the Minnesota Supplemental Aid program. +description: Minnesota provides this multiple of the federal Supplemental Security Income individual benefit rate as the housing allowance under the Minnesota Supplemental Aid program. Per Minn. Stat. § 256D.44 Subd. 5(g)(1), the allowance equals one-half of the maximum federal SSI payment amount for a single individual in effect on July 1 each year. The same amount is paid regardless of which Subd. 5(g) eligibility pathway qualifies the recipient (institution-relocation / HCBS-waiver / shelter > 40% income, or chronic homelessness / housing stabilization). values: 1989-01-01: 0.5 @@ -6,9 +6,9 @@ values: metadata: unit: /1 period: month - label: Minnesota Supplemental Aid housing assistance FBR multiplier + label: Minnesota Supplemental Aid housing allowance FBR multiplier reference: - - title: Minn. Stat. § 256D.44 Subd. 5 (Special needs) + - title: Minn. Stat. § 256D.44 Subd. 5(g)(1) (Shelter needs special allowance) href: https://www.revisor.mn.gov/statutes/cite/256D.44 - title: MN DHS Combined Manual 0023.24 - MSA Housing Assistance href: https://www.dhs.state.mn.us/main/idcplg?IdcService=GET_DYNAMIC_CONVERSION&dDocName=cm_002324&RevisionSelectionMethod=LatestReleased diff --git a/policyengine_us/parameters/gov/states/mn/dhs/msa/special_needs/shelter_need/fbr_multiplier.yaml b/policyengine_us/parameters/gov/states/mn/dhs/msa/special_needs/shelter_need/fbr_multiplier.yaml deleted file mode 100644 index 340e5e96797..00000000000 --- a/policyengine_us/parameters/gov/states/mn/dhs/msa/special_needs/shelter_need/fbr_multiplier.yaml +++ /dev/null @@ -1,14 +0,0 @@ -description: Minnesota provides this multiple of the federal Supplemental Security Income individual benefit rate as the shelter need special-needs allowance under the Minnesota Supplemental Aid program. Per Minn. Stat. § 256D.44 Subd. 5(g)(1), the allowance equals one-half of the maximum federal SSI payment amount for a single individual in effect on July 1 each year. - -values: - 1989-01-01: 0.5 - -metadata: - unit: /1 - period: month - label: Minnesota Supplemental Aid shelter need FBR multiplier - reference: - - title: Minn. Stat. § 256D.44 Subd. 5(g) (Shelter needs special allowance) - href: https://www.revisor.mn.gov/statutes/cite/256D.44 - - title: SSA State Assistance Programs for SSI Recipients, January 2011 - Minnesota, Section 9.5 - href: https://www.ssa.gov/policy/docs/progdesc/ssi_st_asst/2011/mn.html diff --git a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/integration.yaml b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/integration.yaml index 665c3f82530..7b8f3a173b8 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/integration.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/integration.yaml @@ -347,7 +347,7 @@ mn_msa_person: [81, 0] mn_msa: 81 -- name: Case 10, recipient with shelter-need allowance add-on. +- name: Case 10, recipient with housing allowance add-on via Subd. 5(g)(1) pathway. period: 2026-01 absolute_error_margin: 0.01 input: @@ -358,7 +358,7 @@ is_disabled: false immigration_status: CITIZEN ssi_countable_resources: 500 - mn_msa_shelter_need_eligible: true + mn_msa_housing_assistance_eligible: true ssi: 11_928 ssi_earned_income: 0 ssi_unearned_income: 0 @@ -377,9 +377,9 @@ state_code: MN output: mn_msa_assistance_standard: 1_055 - mn_msa_shelter_need_allowance: 497 # 0.5 * $994 + mn_msa_housing_assistance: 497 # 0.5 * $994 mn_msa_eligible_person: true - # Base: $1,055 - max($994 SSI - $20, 0) = $81; plus $497 shelter-need add-on = $578. + # Base: $1,055 - max($994 SSI - $20, 0) = $81; plus $497 housing add-on = $578. mn_msa_person: 578 mn_msa: 578 diff --git a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/payment/mn_msa_shelter_need_allowance.yaml b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/payment/mn_msa_shelter_need_allowance.yaml deleted file mode 100644 index f452f0ceed8..00000000000 --- a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/payment/mn_msa_shelter_need_allowance.yaml +++ /dev/null @@ -1,56 +0,0 @@ -- name: Case 1, shelter need eligible person receives half of SSI individual FBR in 2026. - period: 2026-01 - absolute_error_margin: 0.01 - input: - people: - person1: - age: 70 - is_blind: false - is_disabled: false - immigration_status: CITIZEN - ssi_countable_resources: 500 - mn_msa_shelter_need_eligible: true - marital_units: - marital_unit: - members: [person1] - tax_units: - tax_unit: - members: [person1] - spm_units: - spm_unit: - members: [person1] - households: - household: - members: [person1] - state_code: MN - output: - # 0.5 * $994 (2026 individual FBR) = $497. - mn_msa_shelter_need_allowance: 497 - -- name: Case 2, person not flagged as shelter-need-eligible receives nothing. - period: 2026-01 - absolute_error_margin: 0.01 - input: - people: - person1: - age: 70 - is_blind: false - is_disabled: false - immigration_status: CITIZEN - ssi_countable_resources: 500 - mn_msa_shelter_need_eligible: false - marital_units: - marital_unit: - members: [person1] - tax_units: - tax_unit: - members: [person1] - spm_units: - spm_unit: - members: [person1] - households: - household: - members: [person1] - state_code: MN - output: - mn_msa_shelter_need_allowance: 0 diff --git a/policyengine_us/variables/gov/states/mn/dhs/msa/mn_msa_housing_assistance_eligible.py b/policyengine_us/variables/gov/states/mn/dhs/msa/mn_msa_housing_assistance_eligible.py index 64f37004a08..ec70076882a 100644 --- a/policyengine_us/variables/gov/states/mn/dhs/msa/mn_msa_housing_assistance_eligible.py +++ b/policyengine_us/variables/gov/states/mn/dhs/msa/mn_msa_housing_assistance_eligible.py @@ -4,12 +4,16 @@ class mn_msa_housing_assistance_eligible(Variable): value_type = bool entity = Person - label = ( - "Whether the person qualifies for Minnesota Supplemental Aid housing assistance" - ) + label = "Eligible for the Minnesota Supplemental Aid housing allowance under § 256D.44 Subd. 5(g)" definition_period = MONTH defined_for = StateCode.MN reference = ( "https://www.revisor.mn.gov/statutes/cite/256D.44", "https://www.dhs.state.mn.us/main/idcplg?IdcService=GET_DYNAMIC_CONVERSION&dDocName=cm_002324&RevisionSelectionMethod=LatestReleased", ) + # Set to true if the person qualifies via EITHER pathway: + # (1) Subd. 5(g)(1) — institution-relocation, HCBS-waiver + # eligible, or shelter cost > 40% of income. + # (2) CM 0023.24 — chronic homelessness or housing + # stabilization. + # The benefit amount is the same (1/2 x FBR) under either route. diff --git a/policyengine_us/variables/gov/states/mn/dhs/msa/mn_msa_shelter_need_eligible.py b/policyengine_us/variables/gov/states/mn/dhs/msa/mn_msa_shelter_need_eligible.py deleted file mode 100644 index c85a6f9a88f..00000000000 --- a/policyengine_us/variables/gov/states/mn/dhs/msa/mn_msa_shelter_need_eligible.py +++ /dev/null @@ -1,13 +0,0 @@ -from policyengine_us.model_api import * - - -class mn_msa_shelter_need_eligible(Variable): - value_type = bool - entity = Person - label = "Whether the person qualifies for the shelter-need special-needs allowance under Minnesota Supplemental Aid" - definition_period = MONTH - defined_for = StateCode.MN - reference = ( - "https://www.revisor.mn.gov/statutes/cite/256D.44", - "https://www.ssa.gov/policy/docs/progdesc/ssi_st_asst/2011/mn.html", - ) diff --git a/policyengine_us/variables/gov/states/mn/dhs/msa/payment/mn_msa_housing_assistance.py b/policyengine_us/variables/gov/states/mn/dhs/msa/payment/mn_msa_housing_assistance.py index f6e324604c1..77278159e1c 100644 --- a/policyengine_us/variables/gov/states/mn/dhs/msa/payment/mn_msa_housing_assistance.py +++ b/policyengine_us/variables/gov/states/mn/dhs/msa/payment/mn_msa_housing_assistance.py @@ -4,7 +4,7 @@ class mn_msa_housing_assistance(Variable): value_type = float entity = Person - label = "Minnesota Supplemental Aid housing assistance allowance" + label = "Minnesota Supplemental Aid housing allowance under § 256D.44 Subd. 5(g)" unit = USD definition_period = MONTH defined_for = StateCode.MN @@ -14,12 +14,15 @@ class mn_msa_housing_assistance(Variable): ) def formula(person, period, parameters): - # Per Combined Manual 0023.24: MSA Housing Assistance equals - # one half of the federal SSI individual benefit rate. Recipients - # are also treated as living alone for assistance-standard - # selection — set mn_msa_living_arrangement to - # INDIVIDUAL_LIVING_ALONE alongside this eligibility input to - # capture the joint effect. + # Minn. Stat. § 256D.44 Subd. 5(g) authorizes a single housing + # allowance equal to one-half of the federal SSI individual + # benefit rate. Recipients qualify via either pathway: + # (1) Subd. 5(g)(1): institution-relocation, HCBS-waiver + # eligible, or shelter cost > 40% of income (formerly + # modeled as the Shelter Need allowance). + # (2) CM 0023.24: chronic homelessness / housing + # stabilization (the original MSA Housing Assistance). + # The amount is the same (1/2 x FBR) regardless of pathway. p = parameters(period).gov.states.mn.dhs.msa.housing_assistance ssi_fbr = parameters(period).gov.ssa.ssi.amount.individual eligible = person("mn_msa_housing_assistance_eligible", period) diff --git a/policyengine_us/variables/gov/states/mn/dhs/msa/payment/mn_msa_shelter_need_allowance.py b/policyengine_us/variables/gov/states/mn/dhs/msa/payment/mn_msa_shelter_need_allowance.py deleted file mode 100644 index 05029caaf0a..00000000000 --- a/policyengine_us/variables/gov/states/mn/dhs/msa/payment/mn_msa_shelter_need_allowance.py +++ /dev/null @@ -1,23 +0,0 @@ -from policyengine_us.model_api import * - - -class mn_msa_shelter_need_allowance(Variable): - value_type = float - entity = Person - label = "Minnesota Supplemental Aid shelter-need special allowance" - unit = USD - definition_period = MONTH - defined_for = StateCode.MN - reference = ( - "https://www.revisor.mn.gov/statutes/cite/256D.44", - "https://www.ssa.gov/policy/docs/progdesc/ssi_st_asst/2011/mn.html", - ) - - def formula(person, period, parameters): - # Per Minn. Stat. § 256D.44 Subd. 5(g): the shelter-need - # allowance equals one half of the federal SSI individual - # benefit rate, adjusted annually on July 1. - p = parameters(period).gov.states.mn.dhs.msa.special_needs.shelter_need - ssi_fbr = parameters(period).gov.ssa.ssi.amount.individual - eligible = person("mn_msa_shelter_need_eligible", period) - return eligible * ssi_fbr * p.fbr_multiplier diff --git a/policyengine_us/variables/gov/states/mn/dhs/msa/payment/mn_msa_special_needs_total.py b/policyengine_us/variables/gov/states/mn/dhs/msa/payment/mn_msa_special_needs_total.py index 0a2d036438a..e83f90fde0b 100644 --- a/policyengine_us/variables/gov/states/mn/dhs/msa/payment/mn_msa_special_needs_total.py +++ b/policyengine_us/variables/gov/states/mn/dhs/msa/payment/mn_msa_special_needs_total.py @@ -12,18 +12,8 @@ class mn_msa_special_needs_total(Variable): "https://www.revisor.mn.gov/statutes/cite/256D.44", "https://www.dhs.state.mn.us/main/groups/county_access/documents/pub/mndhs-073585.pdf#page=4", ) - - def formula(person, period, parameters): - # Representative-payee and guardian fees stack with all other - # special-needs items. The shelter-need allowance (Minn. Stat. - # § 256D.44 Subd. 5(g)) and MSA Housing Assistance (Combined - # Manual 0023.24) are mutually exclusive — they serve different - # housing situations and a recipient cannot receive both for the - # same arrangement. When both eligibility flags are set, MSA - # Housing Assistance takes precedence as the stricter program. - rep_payee_fee = person("mn_msa_representative_payee_fee", period) - guardian_fee = person("mn_msa_guardian_fee", period) - shelter_need = person("mn_msa_shelter_need_allowance", period) - housing_assistance = person("mn_msa_housing_assistance", period) - housing_aid = where(housing_assistance > 0, housing_assistance, shelter_need) - return rep_payee_fee + guardian_fee + housing_aid + adds = [ + "mn_msa_representative_payee_fee", + "mn_msa_guardian_fee", + "mn_msa_housing_assistance", + ] From 86a1376704f9a04cbb1cd14a9d376ca3d12da93a Mon Sep 17 00:00:00 2001 From: Ziming Date: Fri, 1 May 2026 11:39:29 -0400 Subject: [PATCH 07/17] Fix three P2 issues in MN MSA: couple disregards and parent deeming P2.1: For joint SSI couples in COUPLE_* arrangements, the $20 general disregard is now applied once to combined couple income (halved per spouse) rather than twice. 2026 couple living alone now correctly receives $111/mo (was $131/mo, overpaying by $20). P2.2: For non-SSI track couples, the $20 general disregard and $65 earned- income disregard are now halved per spouse so they sum to the correct once-per-couple totals. P2.3: Include `ssi_unearned_income_deemed_from_ineligible_parent` in MSA gross income, countable unearned, raw unearned, and earned-disregard rollover so blind/disabled child applicants with high parental income correctly fail the gross/net income screens. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../eligibility/mn_msa_eligible_person.yaml | 32 +++++++++++++++ .../mn_msa_countable_unearned_income.yaml | 40 +++++++++++++++++++ .../gov/states/mn/dhs/msa/mn_msa_person.yaml | 6 ++- .../mn_msa_countable_unearned_income.py | 12 +++++- .../income/mn_msa_earned_income_disregard.py | 19 ++++++++- .../mn/dhs/msa/income/mn_msa_gross_income.py | 1 + .../gov/states/mn/dhs/msa/mn_msa_person.py | 7 +++- 7 files changed, 111 insertions(+), 6 deletions(-) diff --git a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/eligibility/mn_msa_eligible_person.yaml b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/eligibility/mn_msa_eligible_person.yaml index 729620b29d8..4156290a5cc 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/eligibility/mn_msa_eligible_person.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/eligibility/mn_msa_eligible_person.yaml @@ -83,6 +83,38 @@ output: mn_msa_eligible_person: false +- name: Case 5, P2.3 child applicant with high parent-deemed income fails gross income test. + period: 2026-01 + input: + people: + person1: + age: 10 + is_blind: false + is_disabled: true + immigration_status: CITIZEN + ssi_countable_resources: 500 + ssi: 0 + ssi_unearned_income: 0 + ssi_unearned_income_deemed_from_ineligible_parent: 60_000 # $5,000/mo + marital_units: + marital_unit: + members: [person1] + tax_units: + tax_unit: + members: [person1] + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MN + output: + # Parent-deemed $5,000/mo > 300% individual FBR (~$2,982/mo in 2026) + # -> gross income test fails. (P2.3: parent-deemed unearned now flows + # into MSA gross/countable income for child applicants.) + mn_msa_eligible_person: false + - name: Case 4, income above MSA standard is ineligible. period: 2026-01 input: diff --git a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/income/mn_msa_countable_unearned_income.yaml b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/income/mn_msa_countable_unearned_income.yaml index 7c218d2bad9..e538fccb7c4 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/income/mn_msa_countable_unearned_income.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/income/mn_msa_countable_unearned_income.yaml @@ -83,6 +83,46 @@ # $10 - $20 floored at $0 (the leftover rolls into earned income). mn_msa_countable_unearned_income: 0 +- name: Case 5, P2.2 couple living alone splits the $20 general disregard 50/50 across spouses. + period: 2026-01 + input: + people: + person1: + age: 70 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 1_000 + mn_msa_living_arrangement: COUPLE_LIVING_ALONE + ssi: 0 + ssi_unearned_income: 9_660 # $805/mo each + person2: + age: 68 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 1_000 + mn_msa_living_arrangement: COUPLE_LIVING_ALONE + ssi: 0 + ssi_unearned_income: 9_660 # $805/mo each + 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: MN + output: + # Per spouse: $805 unearned - $20/2 = $795 countable. Sums to combined + # $1,590 (= $1,610 gross - $20 once-per-couple disregard). + mn_msa_countable_unearned_income: [795, 795] + - name: Case 4, deemed-from-ineligible-spouse unearned income aggregates with own unearned. period: 2026-01 input: diff --git a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/mn_msa_person.yaml b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/mn_msa_person.yaml index 12da9c29136..df0f6c57e48 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/mn_msa_person.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/mn_msa_person.yaml @@ -133,8 +133,10 @@ members: [person1, person2] state_code: MN output: - # Per person: $1,582 / 2 = $791 standard - max($745.50 SSI - $20, 0) = $791 - $725.50 = $65.50. - mn_msa_person: [65.50, 65.50] + # Per person: $1,582 / 2 = $791 standard - max($745.50 SSI - $20/2, 0) + # = $791 - $735.50 = $55.50. Couple total $111. (P2.1: $20 disregard + # is applied once per couple, allocated half to each spouse.) + mn_msa_person: [55.50, 55.50] - name: Case 5, asymmetric eligibility couple — only one spouse SSI-eligible. period: 2026-01 diff --git a/policyengine_us/variables/gov/states/mn/dhs/msa/income/mn_msa_countable_unearned_income.py b/policyengine_us/variables/gov/states/mn/dhs/msa/income/mn_msa_countable_unearned_income.py index ef24bd7c1cc..c7592155aa6 100644 --- a/policyengine_us/variables/gov/states/mn/dhs/msa/income/mn_msa_countable_unearned_income.py +++ b/policyengine_us/variables/gov/states/mn/dhs/msa/income/mn_msa_countable_unearned_income.py @@ -25,7 +25,17 @@ def formula(person, period, parameters): [ "ssi_unearned_income", "ssi_unearned_income_deemed_from_ineligible_spouse", + "ssi_unearned_income_deemed_from_ineligible_parent", ], ) general = parameters(period).gov.states.mn.dhs.msa.disregard.general - return max_(gross_unearned - general, 0) + # The COUPLE_* assistance standards are couple totals split 50/50 + # onto each spouse, so the $20 general disregard is also applied + # once per couple — half to each spouse. + arrangement = person("mn_msa_payment_category", period) + LA = arrangement.possible_values + is_couple_arrangement = (arrangement == LA.COUPLE_LIVING_ALONE) | ( + arrangement == LA.COUPLE_LIVING_WITH_OTHERS + ) + per_person_general = where(is_couple_arrangement, general / 2, general) + return max_(gross_unearned - per_person_general, 0) diff --git a/policyengine_us/variables/gov/states/mn/dhs/msa/income/mn_msa_earned_income_disregard.py b/policyengine_us/variables/gov/states/mn/dhs/msa/income/mn_msa_earned_income_disregard.py index 84d6af9c0a7..41a1c0d02c4 100644 --- a/policyengine_us/variables/gov/states/mn/dhs/msa/income/mn_msa_earned_income_disregard.py +++ b/policyengine_us/variables/gov/states/mn/dhs/msa/income/mn_msa_earned_income_disregard.py @@ -27,6 +27,7 @@ def formula(person, period, parameters): [ "ssi_unearned_income", "ssi_unearned_income_deemed_from_ineligible_spouse", + "ssi_unearned_income_deemed_from_ineligible_parent", ], ) gross_earned = add( @@ -37,8 +38,22 @@ def formula(person, period, parameters): "ssi_earned_income_deemed_from_ineligible_spouse", ], ) - leftover_general = max_(p.general - gross_unearned, 0) - flat = p.earned.initial + leftover_general + # The COUPLE_* assistance standards are couple totals split 50/50 + # onto each spouse, so the $20 general and $65 earned-initial + # disregards are also applied once per couple — half to each + # spouse. The 1/2 leftover-earned rate applies to per-spouse gross + # earned and is unchanged. + arrangement = person("mn_msa_payment_category", period) + LA = arrangement.possible_values + is_couple_arrangement = (arrangement == LA.COUPLE_LIVING_ALONE) | ( + arrangement == LA.COUPLE_LIVING_WITH_OTHERS + ) + per_person_general = where(is_couple_arrangement, p.general / 2, p.general) + per_person_earned_initial = where( + is_couple_arrangement, p.earned.initial / 2, p.earned.initial + ) + leftover_general = max_(per_person_general - gross_unearned, 0) + flat = per_person_earned_initial + leftover_general flat_disregard = min_(gross_earned, flat) remainder = max_(gross_earned - flat, 0) return flat_disregard + remainder * p.earned.rate diff --git a/policyengine_us/variables/gov/states/mn/dhs/msa/income/mn_msa_gross_income.py b/policyengine_us/variables/gov/states/mn/dhs/msa/income/mn_msa_gross_income.py index 34dc80bd129..ddbb0b6f524 100644 --- a/policyengine_us/variables/gov/states/mn/dhs/msa/income/mn_msa_gross_income.py +++ b/policyengine_us/variables/gov/states/mn/dhs/msa/income/mn_msa_gross_income.py @@ -27,5 +27,6 @@ def formula(person, period, parameters): "ssi", "ssi_earned_income_deemed_from_ineligible_spouse", "ssi_unearned_income_deemed_from_ineligible_spouse", + "ssi_unearned_income_deemed_from_ineligible_parent", ], ) diff --git a/policyengine_us/variables/gov/states/mn/dhs/msa/mn_msa_person.py b/policyengine_us/variables/gov/states/mn/dhs/msa/mn_msa_person.py index e22eb0b41b5..322e685b88f 100644 --- a/policyengine_us/variables/gov/states/mn/dhs/msa/mn_msa_person.py +++ b/policyengine_us/variables/gov/states/mn/dhs/msa/mn_msa_person.py @@ -45,6 +45,7 @@ def formula(person, period, parameters): [ "ssi_unearned_income", "ssi_unearned_income_deemed_from_ineligible_spouse", + "ssi_unearned_income_deemed_from_ineligible_parent", ], ) # The $20 disregard applies to FLA-A (living alone) and FLA-B (with @@ -54,7 +55,11 @@ def formula(person, period, parameters): # $30 with no $20 added back. is_medicaid_facility = arrangement == LA.MEDICAID_FACILITY disregard = where(is_medicaid_facility, 0, p.general) - ssi_track_countable = max_(federal_ssi + raw_unearned - disregard, 0) + # For couples, the $20 disregard is applied once to combined couple + # income; allocate half to each spouse so the per-person formulas sum + # to the correct couple total. + per_person_disregard = where(is_couple_arrangement, disregard / 2, disregard) + ssi_track_countable = max_(federal_ssi + raw_unearned - per_person_disregard, 0) ssi_track = max_(0, per_person_standard - ssi_track_countable) countable_income = person("mn_msa_countable_income", period) non_ssi_track = max_(0, per_person_standard - countable_income) From 4308909f46e89bd9490f587188e41e4be3b26b64 Mon Sep 17 00:00:00 2001 From: Ziming Date: Sun, 3 May 2026 19:35:55 -0400 Subject: [PATCH 08/17] Remove lessons/agent-lessons.md from PR This file belongs in local memory and/or the policyengine-claude plugin repo, not in the policyengine-us code repo. Co-Authored-By: Claude Opus 4.7 (1M context) --- lessons/agent-lessons.md | 27 --------------------------- 1 file changed, 27 deletions(-) delete mode 100644 lessons/agent-lessons.md diff --git a/lessons/agent-lessons.md b/lessons/agent-lessons.md deleted file mode 100644 index 9bd24e4d098..00000000000 --- a/lessons/agent-lessons.md +++ /dev/null @@ -1,27 +0,0 @@ -# Agent Lessons Learned - -Accumulated from /encode-policy-v2 and /backdate-program runs across all contributors. -Loaded by implementation agents on future runs. - - -## New Lessons from Minnesota MSA (2026-04-30) - -### FORMULA -- When a state SSP grant formula subtracts the federal benefit separately (e.g., `standard - federal_ssi - countable_income`), applying the $20 general disregard inside the countable-income variable does NOT reduce the federal-benefit portion; the disregard must be applied at the grant-calculation step against the combined `(federal_benefit + other_unearned)`, not against `other_unearned` alone. Otherwise the SSI track under-grants by $20/mo for the dominant cohort with no other unearned income. -- When a state SSP grant formula stacks federal-SSI-style disregards (e.g., $20 general + $65 + 1/2 of remainder), gate the earned-disregard branch on `~receives_ssi`; the federal SSI calculation has already absorbed the earned-side disregards, so reapplying them on the state side double-counts. -- When the federal SSI methodology rolls leftover general-disregard from unearned to earned (`leftover_general = max_(20 - unearned, 0)` added to earned-disregard flat), document explicitly whether the state regulation endorses this rollover; state manuals often only restrict the earned-only disregards and are silent on whether the general $20 rolls — pick a defensible interpretation and add a code comment citing the basis. - -### TEST -- For SSP track-specific formulas, always include at least one test case where an SSI recipient also has non-zero non-SSI unearned income (e.g., pension, child support, Social Security); SSI-only test cases mask bugs in disregard handling because the countable-unearned variable correctly returns zero when federal SSI consumed everything, hiding whether the formula works for mixed-income SSI recipients. -- Verify SSP empirical math against the SSA "State Assistance Programs for SSI Recipients" Table 1 state-portion column: compute `combined - federal_FBR` and compare to the listed state portion; a $20 discrepancy in either direction is the signature of a missing-or-double-applied general income disregard at the grant-calculation step. -- When backdating an SSP to multiple eras (e.g., 2011, 2024, 2026), include at least one test case per era; era-specific parameter entries with no matching test silently mis-extrapolate values from adjacent eras and are invisible to the typical "2024 or 2026" test cluster. -- When a parameter introduces a new disregard (e.g., $20 general) into an existing formula, sweep ALL pre-existing test files that reference the affected variable and rebase expected outputs; stale expected values from before the disregard was added cause cascading test failures that look like formula bugs but are actually outdated fixtures. - -### REFERENCE -- For multi-era assistance-standard parameters (e.g., 2011 / 2024 / 2026), each era often has a different authoritative source (SSA factsheet for old era, House Research / state manual for current era); since YAML doesn't support date-keyed references, list multiple references and use the title field to indicate which source covers which era (e.g., `"SSA Table 1 ($20 value only, 2011)"`). -- When a state regulation manual section is cited for a parameter but the section's text does not actually authorize that value (e.g., CM 0018.18 covers earned disregards only, not the $20 general disregard), drop the manual cite and use the secondary explanatory source (e.g., House Research) as primary; do not bundle a non-authoritative cite alongside the real authority. -- The SSA "State Assistance Programs for SSI Recipients" series (last published 2011) is hosted on SSA.gov which blocks automated access (403/Akamai); when relying on this source, present it at the user-checkpoint phase for manual download rather than letting document-collector agents repeatedly fail and proceed with incomplete data. - -### WORKFLOW -- For SSP programs with grandfathered or closed-cohort categories (e.g., pre-1994 couple eligibility), explicitly present the full-vs-reduced enum scope as a Phase 2 user decision rather than implementing all categories by default; closed cohorts are real regulatory features but rarely affect the modeled population, so the user should choose whether the implementation cost is justified. -- Coverage reports must be regenerated after each round of fixes, not relied on from earlier rounds; a report that still references a pre-fix variable name (e.g., `is_ssi_eligible | (ssi > 0)` when the code now uses `is_ssi_aged_blind_disabled`) wastes review effort and triggers false flags. From 1fe39a5a5ab9bc21a6b1f104d79eb3750d952eb2 Mon Sep 17 00:00:00 2001 From: Ziming Date: Sun, 3 May 2026 21:31:45 -0400 Subject: [PATCH 09/17] fix --- .../mn_msa_categorically_eligible.yaml | 103 ------------------ .../eligibility/mn_msa_eligible_person.yaml | 59 ++++++++++ .../mn_msa_net_income_eligible.yaml | 39 +++++++ .../dhs/msa/income/mn_msa_gross_income.yaml | 31 ++++++ .../gov/states/mn/dhs/msa/integration.yaml | 1 - .../gov/states/mn/dhs/msa/mn_msa_person.yaml | 66 +++++++++++ .../msa/payment/mn_msa_payment_category.yaml | 38 +++++++ .../payment/mn_msa_special_needs_total.yaml | 67 ++++++++++++ .../mn_msa_categorically_eligible.py | 25 ----- .../msa/eligibility/mn_msa_eligible_person.py | 11 +- .../mn_msa_gross_income_eligible.py | 2 +- .../eligibility/mn_msa_net_income_eligible.py | 12 +- .../mn/dhs/msa/income/mn_msa_gross_income.py | 12 +- .../gov/states/mn/dhs/msa/mn_msa_person.py | 7 +- .../msa/mn_msa_uses_representative_payee.py | 2 +- .../msa/payment/mn_msa_payment_category.py | 35 +++++- .../mn_msa_representative_payee_fee.py | 7 +- .../msa/payment/mn_msa_special_needs_total.py | 19 +++- 18 files changed, 383 insertions(+), 153 deletions(-) delete mode 100644 policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/eligibility/mn_msa_categorically_eligible.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/payment/mn_msa_special_needs_total.yaml delete mode 100644 policyengine_us/variables/gov/states/mn/dhs/msa/eligibility/mn_msa_categorically_eligible.py diff --git a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/eligibility/mn_msa_categorically_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/eligibility/mn_msa_categorically_eligible.yaml deleted file mode 100644 index b97268d7c6f..00000000000 --- a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/eligibility/mn_msa_categorically_eligible.yaml +++ /dev/null @@ -1,103 +0,0 @@ -- name: Case 1, aged person is categorically eligible. - period: 2026-01 - input: - people: - person1: - age: 70 - is_blind: false - is_disabled: false - immigration_status: CITIZEN - ssi_countable_resources: 1_000 - marital_units: - marital_unit: - members: [person1] - tax_units: - tax_unit: - members: [person1] - spm_units: - spm_unit: - members: [person1] - households: - household: - members: [person1] - state_code: MN - output: - mn_msa_categorically_eligible: true - -- name: Case 2, blind child is categorically eligible. - period: 2026-01 - input: - people: - person1: - age: 10 - is_blind: true - is_disabled: false - immigration_status: CITIZEN - ssi_countable_resources: 500 - marital_units: - marital_unit: - members: [person1] - tax_units: - tax_unit: - members: [person1] - spm_units: - spm_unit: - members: [person1] - households: - household: - members: [person1] - state_code: MN - output: - mn_msa_categorically_eligible: true - -- name: Case 3, disabled adult is categorically eligible. - period: 2026-01 - input: - people: - person1: - age: 40 - is_blind: false - is_disabled: true - immigration_status: CITIZEN - ssi_countable_resources: 500 - marital_units: - marital_unit: - members: [person1] - tax_units: - tax_unit: - members: [person1] - spm_units: - spm_unit: - members: [person1] - households: - household: - members: [person1] - state_code: MN - output: - mn_msa_categorically_eligible: true - -- name: Case 4, non-aged non-blind non-disabled adult is not categorically eligible. - period: 2026-01 - input: - people: - person1: - age: 40 - is_blind: false - is_disabled: false - immigration_status: CITIZEN - ssi_countable_resources: 500 - marital_units: - marital_unit: - members: [person1] - tax_units: - tax_unit: - members: [person1] - spm_units: - spm_unit: - members: [person1] - households: - household: - members: [person1] - state_code: MN - output: - mn_msa_categorically_eligible: false diff --git a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/eligibility/mn_msa_eligible_person.yaml b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/eligibility/mn_msa_eligible_person.yaml index 4156290a5cc..713aba752a0 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/eligibility/mn_msa_eligible_person.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/eligibility/mn_msa_eligible_person.yaml @@ -143,3 +143,62 @@ output: # Countable $2,000 > $1,055 standard -> not income eligible. mn_msa_eligible_person: false + +- name: Case 6, non-qualifying living arrangement is ineligible. + period: 2026-01 + input: + people: + person1: + age: 70 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + mn_msa_living_arrangement: NONE + mn_msa_uses_representative_payee: true + ssi_countable_resources: 500 + ssi: 0 + ssi_unearned_income: 0 + marital_units: + marital_unit: + members: [person1] + tax_units: + tax_unit: + members: [person1] + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MN + output: + mn_msa_eligible_person: false + mn_msa_person: 0 + +- name: Case 7, disabled child who is not blind is ineligible. + period: 2026-01 + input: + people: + person1: + age: 10 + is_blind: false + is_disabled: true + immigration_status: CITIZEN + ssi_countable_resources: 500 + ssi: 0 + ssi_unearned_income: 0 + marital_units: + marital_unit: + members: [person1] + tax_units: + tax_unit: + members: [person1] + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MN + output: + mn_msa_eligible_person: false diff --git a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/eligibility/mn_msa_net_income_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/eligibility/mn_msa_net_income_eligible.yaml index b53bd853307..40d84e66ccf 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/eligibility/mn_msa_net_income_eligible.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/eligibility/mn_msa_net_income_eligible.yaml @@ -84,3 +84,42 @@ output: # Countable $1,054 <= $1,055 standard -> eligible. mn_msa_net_income_eligible: true + +- name: Case 4, couple countable income above the couple standard is not net-income eligible. + period: 2026-01 + input: + people: + person1: + age: 70 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + mn_msa_living_arrangement: COUPLE_LIVING_ALONE + ssi_countable_resources: 5_000 + ssi: 0 + ssi_unearned_income: 9_618 # $801.50/mo; minus $10 half-disregard = $791.50 + person2: + age: 70 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + mn_msa_living_arrangement: COUPLE_LIVING_ALONE + ssi_countable_resources: 5_000 + ssi: 0 + ssi_unearned_income: 9_618 # $801.50/mo; minus $10 half-disregard = $791.50 + 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: MN + output: + # Couple countable income $1,583 > $1,582 couple standard. + mn_msa_net_income_eligible: [false, false] diff --git a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/income/mn_msa_gross_income.yaml b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/income/mn_msa_gross_income.yaml index bce360d42a9..03a7770d101 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/income/mn_msa_gross_income.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/income/mn_msa_gross_income.yaml @@ -121,3 +121,34 @@ state_code: MN output: mn_msa_gross_income: 0 + +- name: Case 5, reduced SSI payment still counts the applicable federal benefit rate. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 70 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 500 + ssi: 6_000 # $500/mo actual payment after federal reductions + ssi_earned_income: 0 + ssi_unearned_income: 0 + marital_units: + marital_unit: + members: [person1] + tax_units: + tax_unit: + members: [person1] + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MN + output: + # MSA counts the applicable $994 SSI FBR, not the reduced $500 SSI payment. + mn_msa_gross_income: 994 diff --git a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/integration.yaml b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/integration.yaml index 7b8f3a173b8..c4c9fa49709 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/integration.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/integration.yaml @@ -260,7 +260,6 @@ members: [person1] state_code: MN output: - mn_msa_categorically_eligible: false mn_msa_eligible_person: false mn_msa_person: 0 mn_msa: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/mn_msa_person.yaml b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/mn_msa_person.yaml index df0f6c57e48..67406cf6038 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/mn_msa_person.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/mn_msa_person.yaml @@ -243,3 +243,69 @@ # $1,055 - max($994 SSI - $20, 0) = $1,055 - $974 = $81. mn_msa_living_arrangement: INDIVIDUAL_LIVING_ALONE mn_msa_person: 81 + +- name: Case 8, SSI-track amount uses the federal benefit rate when actual SSI is reduced. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 70 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 500 + ssi: 6_000 # $500/mo actual payment after federal reductions + ssi_earned_income: 0 + ssi_unearned_income: 0 + marital_units: + marital_unit: + members: [person1] + tax_units: + tax_unit: + members: [person1] + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MN + output: + # MSA counts the $994 FBR: $1,055 - max($994 - $20, 0) = $81. + mn_msa_person: 81 + +- name: Case 9, housing-assistance eligibility applies the living-alone standard. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 50 + is_blind: false + is_disabled: true + immigration_status: CITIZEN + mn_msa_living_arrangement: INDIVIDUAL_LIVING_WITH_OTHERS + mn_msa_housing_assistance_eligible: true + ssi_countable_resources: 500 + ssi: 11_928 + ssi_earned_income: 0 + ssi_unearned_income: 0 + marital_units: + marital_unit: + members: [person1] + tax_units: + tax_unit: + members: [person1] + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MN + output: + # Housing assistance switches the base standard from $755.33 to $1,055. + # Base $81 plus housing allowance $497 = $578. + mn_msa_payment_category: INDIVIDUAL_LIVING_ALONE + mn_msa_person: 578 diff --git a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/payment/mn_msa_payment_category.yaml b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/payment/mn_msa_payment_category.yaml index 5d290d0b7ec..9b5b2d9c921 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/payment/mn_msa_payment_category.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/payment/mn_msa_payment_category.yaml @@ -40,3 +40,41 @@ state_code: MN output: mn_msa_payment_category: MEDICAID_FACILITY + +- name: Case 4, housing-assistance eligibility applies individual living-alone category. + period: 2026-01 + input: + people: + person1: + age: 50 + is_disabled: true + mn_msa_living_arrangement: INDIVIDUAL_LIVING_WITH_OTHERS + mn_msa_housing_assistance_eligible: true + households: + household: + members: [person1] + state_code: MN + output: + mn_msa_payment_category: INDIVIDUAL_LIVING_ALONE + +- name: Case 5, housing-assistance eligibility applies couple living-alone category. + period: 2026-01 + input: + people: + person1: + age: 70 + mn_msa_living_arrangement: COUPLE_LIVING_WITH_OTHERS + mn_msa_housing_assistance_eligible: true + person2: + age: 70 + mn_msa_living_arrangement: COUPLE_LIVING_WITH_OTHERS + mn_msa_housing_assistance_eligible: false + marital_units: + marital_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: MN + output: + mn_msa_payment_category: [COUPLE_LIVING_ALONE, COUPLE_LIVING_ALONE] diff --git a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/payment/mn_msa_special_needs_total.yaml b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/payment/mn_msa_special_needs_total.yaml new file mode 100644 index 00000000000..b469b8590a4 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/payment/mn_msa_special_needs_total.yaml @@ -0,0 +1,67 @@ +- name: Case 1, facility resident receives only representative payee special need. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 80 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 500 + ssi_lives_in_medical_treatment_facility: true + ssi_medicaid_pays_majority_of_care: true + mn_msa_uses_representative_payee: true + mn_msa_has_guardian: true + mn_msa_housing_assistance_eligible: true + ssi: 360 + ssi_unearned_income: 24_000 + marital_units: + marital_unit: + members: [person1] + tax_units: + tax_unit: + members: [person1] + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MN + output: + # Guardian and housing special needs are unavailable in facilities; + # representative payee services remain allowed. + mn_msa_special_needs_total: 57 + +- name: Case 2, community resident receives all modeled ongoing special needs. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 70 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 500 + mn_msa_uses_representative_payee: true + mn_msa_has_guardian: true + mn_msa_housing_assistance_eligible: true + ssi_unearned_income: 24_000 + marital_units: + marital_unit: + members: [person1] + tax_units: + tax_unit: + members: [person1] + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MN + output: + # Representative payee $57 + guardian $100 + housing $497. + mn_msa_special_needs_total: 654 diff --git a/policyengine_us/variables/gov/states/mn/dhs/msa/eligibility/mn_msa_categorically_eligible.py b/policyengine_us/variables/gov/states/mn/dhs/msa/eligibility/mn_msa_categorically_eligible.py deleted file mode 100644 index 27f71d62d1b..00000000000 --- a/policyengine_us/variables/gov/states/mn/dhs/msa/eligibility/mn_msa_categorically_eligible.py +++ /dev/null @@ -1,25 +0,0 @@ -from policyengine_us.model_api import * - - -class mn_msa_categorically_eligible(Variable): - value_type = bool - entity = Person - label = "Minnesota Supplemental Aid categorically eligible" - definition_period = MONTH - defined_for = StateCode.MN - reference = ( - "https://www.revisor.mn.gov/statutes/cite/256D.44", - "https://www.house.mn.gov/hrd/pubs/pap_MSA.pdf#page=2", - ) - - def formula(person, period, parameters): - # Per Minn. Stat. § 256D.44 Subd. 1, MSA covers two paths: - # (a) currently receiving SSI (the SSI track), or - # (b) categorically SSI-eligible but income-ineligible for SSI, - # with countable income at or below the MSA assistance - # standard (the non-SSI excess-income track). - # Both paths require aged/blind/disabled status. The non-SSI - # track uses the higher state $10,000 resource cap, so we check - # ABD directly rather than is_ssi_eligible (which includes the - # federal $2,000 resource test). - return person("is_ssi_aged_blind_disabled", period.this_year) diff --git a/policyengine_us/variables/gov/states/mn/dhs/msa/eligibility/mn_msa_eligible_person.py b/policyengine_us/variables/gov/states/mn/dhs/msa/eligibility/mn_msa_eligible_person.py index 9cb52a267f9..0dc292d89f9 100644 --- a/policyengine_us/variables/gov/states/mn/dhs/msa/eligibility/mn_msa_eligible_person.py +++ b/policyengine_us/variables/gov/states/mn/dhs/msa/eligibility/mn_msa_eligible_person.py @@ -17,13 +17,20 @@ def formula(person, period, parameters): # categorically aged/blind/disabled, meet the resource and # gross/net income tests, and reside in Minnesota with a # qualifying immigration status (citizen or qualified noncitizen). - categorically_eligible = person("mn_msa_categorically_eligible", period) + arrangement = person("mn_msa_payment_category", period) + qualifies_arrangement = arrangement != arrangement.possible_values.NONE + age = person("age", period.this_year) + aged = person("is_ssi_aged", period.this_year) + blind = person("is_blind", period.this_year) + disabled_adult = (age >= 18) & person("is_ssi_disabled", period.this_year) + categorically_eligible = aged | blind | disabled_adult resource_eligible = person("mn_msa_resource_eligible", period) gross_income_eligible = person("mn_msa_gross_income_eligible", period) net_income_eligible = person("mn_msa_net_income_eligible", period) meets_immigration = person("is_citizen_or_legal_immigrant", period.this_year) return ( - categorically_eligible + qualifies_arrangement + & categorically_eligible & resource_eligible & gross_income_eligible & net_income_eligible diff --git a/policyengine_us/variables/gov/states/mn/dhs/msa/eligibility/mn_msa_gross_income_eligible.py b/policyengine_us/variables/gov/states/mn/dhs/msa/eligibility/mn_msa_gross_income_eligible.py index 6af47d95393..d4a78782bda 100644 --- a/policyengine_us/variables/gov/states/mn/dhs/msa/eligibility/mn_msa_gross_income_eligible.py +++ b/policyengine_us/variables/gov/states/mn/dhs/msa/eligibility/mn_msa_gross_income_eligible.py @@ -19,7 +19,7 @@ def formula(person, period, parameters): p = parameters(period).gov.states.mn.dhs.msa.eligibility.income_limit ssi_fbr = parameters(period).gov.ssa.ssi.amount.individual joint_claim = person("ssi_claim_is_joint", period.this_year) - categorically_eligible = person("mn_msa_categorically_eligible", period) + categorically_eligible = person("is_ssi_aged_blind_disabled", period.this_year) both_eligible = ( person.marital_unit.sum(categorically_eligible) == 2 ) & joint_claim diff --git a/policyengine_us/variables/gov/states/mn/dhs/msa/eligibility/mn_msa_net_income_eligible.py b/policyengine_us/variables/gov/states/mn/dhs/msa/eligibility/mn_msa_net_income_eligible.py index 9709f386a58..f2f8665d2b1 100644 --- a/policyengine_us/variables/gov/states/mn/dhs/msa/eligibility/mn_msa_net_income_eligible.py +++ b/policyengine_us/variables/gov/states/mn/dhs/msa/eligibility/mn_msa_net_income_eligible.py @@ -16,6 +16,16 @@ def formula(person, period, parameters): # Per Minn. Stat. § 256D.44 Subd. 1, MSA pays only to recipients # whose countable income is at or below the assistance standard # for their living arrangement. + arrangement = person("mn_msa_payment_category", period) + LA = arrangement.possible_values + is_couple_arrangement = (arrangement == LA.COUPLE_LIVING_ALONE) | ( + arrangement == LA.COUPLE_LIVING_WITH_OTHERS + ) countable_income = person("mn_msa_countable_income", period) + income = where( + is_couple_arrangement, + person.marital_unit.sum(countable_income), + countable_income, + ) standard = person("mn_msa_assistance_standard", period) - return countable_income <= standard + return income <= standard diff --git a/policyengine_us/variables/gov/states/mn/dhs/msa/income/mn_msa_gross_income.py b/policyengine_us/variables/gov/states/mn/dhs/msa/income/mn_msa_gross_income.py index ddbb0b6f524..94aa9abba74 100644 --- a/policyengine_us/variables/gov/states/mn/dhs/msa/income/mn_msa_gross_income.py +++ b/policyengine_us/variables/gov/states/mn/dhs/msa/income/mn_msa_gross_income.py @@ -15,18 +15,20 @@ class mn_msa_gross_income(Variable): def formula(person, period, parameters): # Per Minn. Stat. § 256D.44 Subd. 1, MSA's gross-income screen - # applies to total earned + unearned income (including SSI itself - # for the SSI track). Spousal deeming is handled via the SSI - # deemed-income variables. - return add( + # applies to total earned + unearned income. For SSI recipients, + # the county counts the applicable federal SSI benefit rate, not + # the post-income SSI payment. + income = add( person, period, [ "ssi_earned_income", "ssi_unearned_income", - "ssi", "ssi_earned_income_deemed_from_ineligible_spouse", "ssi_unearned_income_deemed_from_ineligible_spouse", "ssi_unearned_income_deemed_from_ineligible_parent", ], ) + ssi = person("ssi", period) + ssi_fbr = person("ssi_amount_if_eligible", period) + return income + where(ssi > 0, ssi_fbr, ssi) diff --git a/policyengine_us/variables/gov/states/mn/dhs/msa/mn_msa_person.py b/policyengine_us/variables/gov/states/mn/dhs/msa/mn_msa_person.py index 322e685b88f..28378f5bc4f 100644 --- a/policyengine_us/variables/gov/states/mn/dhs/msa/mn_msa_person.py +++ b/policyengine_us/variables/gov/states/mn/dhs/msa/mn_msa_person.py @@ -36,8 +36,9 @@ def formula(person, period, parameters): ) standard = person("mn_msa_assistance_standard", period) per_person_standard = where(is_couple_arrangement, standard / 2, standard) - federal_ssi = person("ssi", period) - receives_ssi = federal_ssi > 0 + ssi = person("ssi", period) + receives_ssi = ssi > 0 + ssi_fbr = person("ssi_amount_if_eligible", period) p = parameters(period).gov.states.mn.dhs.msa.disregard raw_unearned = add( person, @@ -59,7 +60,7 @@ def formula(person, period, parameters): # income; allocate half to each spouse so the per-person formulas sum # to the correct couple total. per_person_disregard = where(is_couple_arrangement, disregard / 2, disregard) - ssi_track_countable = max_(federal_ssi + raw_unearned - per_person_disregard, 0) + ssi_track_countable = max_(ssi_fbr + raw_unearned - per_person_disregard, 0) ssi_track = max_(0, per_person_standard - ssi_track_countable) countable_income = person("mn_msa_countable_income", period) non_ssi_track = max_(0, per_person_standard - countable_income) diff --git a/policyengine_us/variables/gov/states/mn/dhs/msa/mn_msa_uses_representative_payee.py b/policyengine_us/variables/gov/states/mn/dhs/msa/mn_msa_uses_representative_payee.py index b0cea2214d4..2ad2c0aa6f5 100644 --- a/policyengine_us/variables/gov/states/mn/dhs/msa/mn_msa_uses_representative_payee.py +++ b/policyengine_us/variables/gov/states/mn/dhs/msa/mn_msa_uses_representative_payee.py @@ -9,5 +9,5 @@ class mn_msa_uses_representative_payee(Variable): defined_for = StateCode.MN reference = ( "https://www.revisor.mn.gov/statutes/cite/256D.44", - "https://www.dhs.state.mn.us/main/groups/county_access/documents/pub/mndhs-073585.pdf#page=6", + "https://www.dhs.state.mn.us/main/groups/county_access/documents/pub/mndhs-073585.pdf#page=7", ) diff --git a/policyengine_us/variables/gov/states/mn/dhs/msa/payment/mn_msa_payment_category.py b/policyengine_us/variables/gov/states/mn/dhs/msa/payment/mn_msa_payment_category.py index fb635175741..d0f68b24b8f 100644 --- a/policyengine_us/variables/gov/states/mn/dhs/msa/payment/mn_msa_payment_category.py +++ b/policyengine_us/variables/gov/states/mn/dhs/msa/payment/mn_msa_payment_category.py @@ -28,8 +28,39 @@ def formula(person, period, parameters): federal_arrangement == federal_values.MEDICAL_TREATMENT_FACILITY ) living_arrangement = person("mn_msa_living_arrangement", period) + LA = MNMSALivingArrangement + housing_assistance_eligible = person( + "mn_msa_housing_assistance_eligible", period + ) + is_couple_arrangement = (living_arrangement == LA.COUPLE_LIVING_ALONE) | ( + living_arrangement == LA.COUPLE_LIVING_WITH_OTHERS + ) + couple_housing_assistance_eligible = ( + person.marital_unit.sum(housing_assistance_eligible) > 0 + ) + apply_housing_standard = where( + is_couple_arrangement, + couple_housing_assistance_eligible, + housing_assistance_eligible, + ) + housing_standard_arrangement = select( + [ + living_arrangement == LA.INDIVIDUAL_LIVING_WITH_OTHERS, + living_arrangement == LA.COUPLE_LIVING_WITH_OTHERS, + ], + [ + LA.INDIVIDUAL_LIVING_ALONE, + LA.COUPLE_LIVING_ALONE, + ], + default=living_arrangement, + ) + community_arrangement = where( + apply_housing_standard, + housing_standard_arrangement, + living_arrangement, + ) return where( in_medicaid_facility, - MNMSALivingArrangement.MEDICAID_FACILITY, - living_arrangement, + LA.MEDICAID_FACILITY, + community_arrangement, ) diff --git a/policyengine_us/variables/gov/states/mn/dhs/msa/payment/mn_msa_representative_payee_fee.py b/policyengine_us/variables/gov/states/mn/dhs/msa/payment/mn_msa_representative_payee_fee.py index 4d5a7b49eca..d0c14a5450f 100644 --- a/policyengine_us/variables/gov/states/mn/dhs/msa/payment/mn_msa_representative_payee_fee.py +++ b/policyengine_us/variables/gov/states/mn/dhs/msa/payment/mn_msa_representative_payee_fee.py @@ -10,15 +10,14 @@ class mn_msa_representative_payee_fee(Variable): defined_for = StateCode.MN reference = ( "https://www.revisor.mn.gov/statutes/cite/256D.44", - "https://www.dhs.state.mn.us/main/groups/county_access/documents/pub/mndhs-073585.pdf#page=6", + "https://www.dhs.state.mn.us/main/groups/county_access/documents/pub/mndhs-073585.pdf#page=7", ) def formula(person, period, parameters): # Per Minn. Stat. § 256D.44 Subd. 5 and Combined Manual 0023.21, # MSA pays the SSA standard representative-payee fee to recipients - # who use a representative payee. The higher drug/alcohol-addiction - # fee is excluded — we don't track addiction-related payee status - # at the moment. + # who use a representative payee. This input defaults to false; we + # do not model detailed representative-payee eligibility. p = parameters(period).gov.states.mn.dhs.msa.special_needs uses_payee = person("mn_msa_uses_representative_payee", period) return uses_payee * p.representative_payee_fee.amount diff --git a/policyengine_us/variables/gov/states/mn/dhs/msa/payment/mn_msa_special_needs_total.py b/policyengine_us/variables/gov/states/mn/dhs/msa/payment/mn_msa_special_needs_total.py index e83f90fde0b..76f4a8f3220 100644 --- a/policyengine_us/variables/gov/states/mn/dhs/msa/payment/mn_msa_special_needs_total.py +++ b/policyengine_us/variables/gov/states/mn/dhs/msa/payment/mn_msa_special_needs_total.py @@ -12,8 +12,17 @@ class mn_msa_special_needs_total(Variable): "https://www.revisor.mn.gov/statutes/cite/256D.44", "https://www.dhs.state.mn.us/main/groups/county_access/documents/pub/mndhs-073585.pdf#page=4", ) - adds = [ - "mn_msa_representative_payee_fee", - "mn_msa_guardian_fee", - "mn_msa_housing_assistance", - ] + + def formula(person, period, parameters): + arrangement = person("mn_msa_payment_category", period) + in_facility = arrangement == arrangement.possible_values.MEDICAID_FACILITY + representative_payee = person("mn_msa_representative_payee_fee", period) + community_special_needs = add( + person, + period, + [ + "mn_msa_guardian_fee", + "mn_msa_housing_assistance", + ], + ) + return representative_payee + where(in_facility, 0, community_special_needs) From 87e17cffd5b1f11c473b9b5b58d650ac64510146 Mon Sep 17 00:00:00 2001 From: Ziming Date: Sun, 3 May 2026 21:47:02 -0400 Subject: [PATCH 10/17] Model MSA housing assistance eligibility --- .../gov/states/mn/dhs/msa/integration.yaml | 6 +- .../mn_msa_housing_assistance_eligible.yaml | 78 +++++++++++++++++++ .../gov/states/mn/dhs/msa/mn_msa_person.yaml | 3 +- .../payment/mn_msa_housing_assistance.yaml | 10 ++- .../msa/payment/mn_msa_payment_category.yaml | 10 ++- .../payment/mn_msa_special_needs_total.yaml | 6 +- .../msa/mn_msa_housing_assistance_eligible.py | 38 +++++++-- ...msa_housing_assistance_pathway_eligible.py | 16 ++++ 8 files changed, 150 insertions(+), 17 deletions(-) create mode 100644 policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/mn_msa_housing_assistance_eligible.yaml create mode 100644 policyengine_us/variables/gov/states/mn/dhs/msa/mn_msa_housing_assistance_pathway_eligible.py diff --git a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/integration.yaml b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/integration.yaml index c4c9fa49709..3a67f9b104e 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/integration.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/integration.yaml @@ -174,7 +174,8 @@ is_disabled: true immigration_status: CITIZEN ssi_countable_resources: 500 - mn_msa_housing_assistance_eligible: true + mn_msa_housing_assistance_pathway_eligible: true + pre_subsidy_rent: 6_000 ssi: 11_928 ssi_earned_income: 0 ssi_unearned_income: 0 @@ -357,7 +358,8 @@ is_disabled: false immigration_status: CITIZEN ssi_countable_resources: 500 - mn_msa_housing_assistance_eligible: true + mn_msa_housing_assistance_pathway_eligible: true + pre_subsidy_rent: 6_000 ssi: 11_928 ssi_earned_income: 0 ssi_unearned_income: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/mn_msa_housing_assistance_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/mn_msa_housing_assistance_eligible.yaml new file mode 100644 index 00000000000..2508641fc38 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/mn_msa_housing_assistance_eligible.yaml @@ -0,0 +1,78 @@ +- name: Case 1, pathway-eligible person with high shelter costs qualifies. + period: 2026-01 + input: + people: + person1: + age: 70 + immigration_status: CITIZEN + mn_msa_housing_assistance_pathway_eligible: true + pre_subsidy_rent: 6_000 + ssi: 11_928 + marital_units: + marital_unit: + members: [person1] + tax_units: + tax_unit: + members: [person1] + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MN + output: + mn_msa_housing_assistance_eligible: true + +- name: Case 2, shelter costs at or below 40 percent of gross income do not qualify. + period: 2026-01 + input: + people: + person1: + age: 70 + immigration_status: CITIZEN + mn_msa_housing_assistance_pathway_eligible: true + pre_subsidy_rent: 4_764 + ssi: 11_928 + marital_units: + marital_unit: + members: [person1] + tax_units: + tax_unit: + members: [person1] + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MN + output: + mn_msa_housing_assistance_eligible: false + +- name: Case 3, current housing assistance prevents eligibility. + period: 2026-01 + input: + people: + person1: + age: 70 + immigration_status: CITIZEN + mn_msa_housing_assistance_pathway_eligible: true + pre_subsidy_rent: 6_000 + ssi: 11_928 + marital_units: + marital_unit: + members: [person1] + tax_units: + tax_unit: + members: [person1] + spm_units: + spm_unit: + members: [person1] + receives_housing_assistance: true + households: + household: + members: [person1] + state_code: MN + output: + mn_msa_housing_assistance_eligible: false diff --git a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/mn_msa_person.yaml b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/mn_msa_person.yaml index 67406cf6038..a48333fd3c1 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/mn_msa_person.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/mn_msa_person.yaml @@ -286,7 +286,8 @@ is_disabled: true immigration_status: CITIZEN mn_msa_living_arrangement: INDIVIDUAL_LIVING_WITH_OTHERS - mn_msa_housing_assistance_eligible: true + mn_msa_housing_assistance_pathway_eligible: true + pre_subsidy_rent: 6_000 ssi_countable_resources: 500 ssi: 11_928 ssi_earned_income: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/payment/mn_msa_housing_assistance.yaml b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/payment/mn_msa_housing_assistance.yaml index e34350abbbe..5f254011fae 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/payment/mn_msa_housing_assistance.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/payment/mn_msa_housing_assistance.yaml @@ -9,7 +9,9 @@ is_disabled: true immigration_status: CITIZEN ssi_countable_resources: 500 - mn_msa_housing_assistance_eligible: true + mn_msa_housing_assistance_pathway_eligible: true + pre_subsidy_rent: 6_000 + ssi: 11_928 marital_units: marital_unit: members: [person1] @@ -27,7 +29,7 @@ # 0.5 * $994 (2026 individual FBR) = $497. mn_msa_housing_assistance: 497 -- name: Case 2, person not flagged as housing-assistance-eligible receives nothing. +- name: Case 2, person not pathway-eligible receives nothing. period: 2026-01 absolute_error_margin: 0.01 input: @@ -38,7 +40,9 @@ is_disabled: true immigration_status: CITIZEN ssi_countable_resources: 500 - mn_msa_housing_assistance_eligible: false + mn_msa_housing_assistance_pathway_eligible: false + pre_subsidy_rent: 6_000 + ssi: 11_928 marital_units: marital_unit: members: [person1] diff --git a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/payment/mn_msa_payment_category.yaml b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/payment/mn_msa_payment_category.yaml index 9b5b2d9c921..896159f3e6c 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/payment/mn_msa_payment_category.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/payment/mn_msa_payment_category.yaml @@ -49,7 +49,9 @@ age: 50 is_disabled: true mn_msa_living_arrangement: INDIVIDUAL_LIVING_WITH_OTHERS - mn_msa_housing_assistance_eligible: true + mn_msa_housing_assistance_pathway_eligible: true + pre_subsidy_rent: 6_000 + ssi: 11_928 households: household: members: [person1] @@ -64,11 +66,13 @@ person1: age: 70 mn_msa_living_arrangement: COUPLE_LIVING_WITH_OTHERS - mn_msa_housing_assistance_eligible: true + mn_msa_housing_assistance_pathway_eligible: true + pre_subsidy_rent: 6_000 + ssi: 11_928 person2: age: 70 mn_msa_living_arrangement: COUPLE_LIVING_WITH_OTHERS - mn_msa_housing_assistance_eligible: false + mn_msa_housing_assistance_pathway_eligible: false marital_units: marital_unit: members: [person1, person2] diff --git a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/payment/mn_msa_special_needs_total.yaml b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/payment/mn_msa_special_needs_total.yaml index b469b8590a4..16ff8738bc5 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/payment/mn_msa_special_needs_total.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/payment/mn_msa_special_needs_total.yaml @@ -13,7 +13,8 @@ ssi_medicaid_pays_majority_of_care: true mn_msa_uses_representative_payee: true mn_msa_has_guardian: true - mn_msa_housing_assistance_eligible: true + mn_msa_housing_assistance_pathway_eligible: true + pre_subsidy_rent: 18_000 ssi: 360 ssi_unearned_income: 24_000 marital_units: @@ -47,7 +48,8 @@ ssi_countable_resources: 500 mn_msa_uses_representative_payee: true mn_msa_has_guardian: true - mn_msa_housing_assistance_eligible: true + mn_msa_housing_assistance_pathway_eligible: true + pre_subsidy_rent: 12_000 ssi_unearned_income: 24_000 marital_units: marital_unit: diff --git a/policyengine_us/variables/gov/states/mn/dhs/msa/mn_msa_housing_assistance_eligible.py b/policyengine_us/variables/gov/states/mn/dhs/msa/mn_msa_housing_assistance_eligible.py index ec70076882a..a3108fb55e1 100644 --- a/policyengine_us/variables/gov/states/mn/dhs/msa/mn_msa_housing_assistance_eligible.py +++ b/policyengine_us/variables/gov/states/mn/dhs/msa/mn_msa_housing_assistance_eligible.py @@ -1,4 +1,7 @@ from policyengine_us.model_api import * +from policyengine_us.variables.gov.states.mn.dhs.msa.mn_msa_living_arrangement import ( + MNMSALivingArrangement, +) class mn_msa_housing_assistance_eligible(Variable): @@ -11,9 +14,32 @@ class mn_msa_housing_assistance_eligible(Variable): "https://www.revisor.mn.gov/statutes/cite/256D.44", "https://www.dhs.state.mn.us/main/idcplg?IdcService=GET_DYNAMIC_CONVERSION&dDocName=cm_002324&RevisionSelectionMethod=LatestReleased", ) - # Set to true if the person qualifies via EITHER pathway: - # (1) Subd. 5(g)(1) — institution-relocation, HCBS-waiver - # eligible, or shelter cost > 40% of income. - # (2) CM 0023.24 — chronic homelessness or housing - # stabilization. - # The benefit amount is the same (1/2 x FBR) under either route. + + def formula(person, period, parameters): + pathway_eligible = person("mn_msa_housing_assistance_pathway_eligible", period) + living_arrangement = person("mn_msa_living_arrangement", period) + LA = MNMSALivingArrangement + is_couple_arrangement = (living_arrangement == LA.COUPLE_LIVING_ALONE) | ( + living_arrangement == LA.COUPLE_LIVING_WITH_OTHERS + ) + + rent = person("rent", period) + utility_expense = person.spm_unit("utility_expense", period) + shelter_cost = where( + is_couple_arrangement, + person.marital_unit.sum(rent) + utility_expense, + rent + utility_expense, + ) + + gross_income = person("mn_msa_gross_income", period) + assistance_unit_income = where( + is_couple_arrangement, + person.marital_unit.sum(gross_income), + gross_income, + ) + high_shelter_cost = shelter_cost > 0.4 * assistance_unit_income + receives_housing_assistance = person.spm_unit( + "receives_housing_assistance", period + ) + + return pathway_eligible & high_shelter_cost & ~receives_housing_assistance diff --git a/policyengine_us/variables/gov/states/mn/dhs/msa/mn_msa_housing_assistance_pathway_eligible.py b/policyengine_us/variables/gov/states/mn/dhs/msa/mn_msa_housing_assistance_pathway_eligible.py new file mode 100644 index 00000000000..55807122b13 --- /dev/null +++ b/policyengine_us/variables/gov/states/mn/dhs/msa/mn_msa_housing_assistance_pathway_eligible.py @@ -0,0 +1,16 @@ +from policyengine_us.model_api import * + + +class mn_msa_housing_assistance_pathway_eligible(Variable): + value_type = bool + entity = Person + label = ( + "Meets a non-financial Minnesota Supplemental Aid housing assistance pathway" + ) + documentation = "Whether the person meets one of the non-financial pathways for MSA Housing Assistance, such as transition from an institution, HCBS waiver eligibility, chronic homelessness, or housing stabilization." + definition_period = MONTH + defined_for = StateCode.MN + reference = ( + "https://www.revisor.mn.gov/statutes/cite/256D.44", + "https://www.dhs.state.mn.us/main/idcplg?IdcService=GET_DYNAMIC_CONVERSION&dDocName=cm_002324&RevisionSelectionMethod=LatestReleased", + ) From 86cf8e5d0df04be52450f7cb3c10e7a554585a87 Mon Sep 17 00:00:00 2001 From: Ziming Date: Sun, 3 May 2026 22:16:34 -0400 Subject: [PATCH 11/17] Apply skill-driven cleanup and fix asymmetric-couple disregard bug MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix the per-spouse floor that wasted half the $20 / $65 disregards when only one spouse in a COUPLE_LIVING_* arrangement had income. Aggregate countable income, earned-income disregard, and SSI/non-SSI supplements at the marital_unit level, then split 50/50 across spouses. Apply policyengine-parameter-patterns and policyengine-variable-patterns skills: switch dimensionless ratios to period: year, rewrite parameter descriptions to the [State] [verb] this amount under the [Full Program Name] program template, replace lawhelpmn.org and incorrect CM 0018.18 references with primary statute / 20 CFR § 416.1124(c)(12), tighten subdivision citations, backdate the general disregard to MSA's 1974 program effective date, add a 1974 zero-floor for the rep payee fee. Drop the forbidden documentation field on mn_msa_housing_assistance_pathway_eligible and remove the file entirely. Defer the § 256D.44 Subd. 5(g) financial-eligibility test (40 percent shelter cost, concurrent housing subsidy check) to a follow-up PR — mn_msa_housing_assistance_eligible is now a bare bool input with a TODO comment. Trim multi-paragraph comment blocks across mn_msa_person.py and the income variables, simplify mn_msa_eligible_person to a single combined return, and replace marital_unit.sum > 0 with marital_unit.any in mn_msa_payment_category. Renumber out-of-order Case 5 in mn_msa_eligible_person.yaml and add positive-immigration (LPR), negative-immigration (UNDOCUMENTED), asymmetric-couple SSI track, asymmetric-couple non-SSI track, SSI-track-with-extra-unearned, and negative-self-employment cap tests. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../dhs/msa/assistance_standard/amount.yaml | 2 +- .../mn/dhs/msa/disregard/earned/initial.yaml | 2 +- .../mn/dhs/msa/disregard/earned/rate.yaml | 4 +- .../states/mn/dhs/msa/disregard/general.yaml | 10 +- .../asset_limit/non_ssi_track.yaml | 10 +- .../income_limit/couple_fbr_multiplier.yaml | 10 +- .../individual_fbr_multiplier.yaml | 10 +- .../housing_assistance/fbr_multiplier.yaml | 6 +- .../guardian_fee/max_amount.yaml | 2 +- .../msa/special_needs/guardian_fee/rate.yaml | 6 +- .../representative_payee_fee/amount.yaml | 5 +- .../eligibility/mn_msa_eligible_person.yaml | 84 ++++++++-- .../gov/states/mn/dhs/msa/integration.yaml | 4 +- .../mn_msa_housing_assistance_eligible.yaml | 60 +------ .../gov/states/mn/dhs/msa/mn_msa_person.yaml | 153 +++++++++++++++++- .../payment/mn_msa_housing_assistance.yaml | 4 +- .../msa/payment/mn_msa_payment_category.yaml | 6 +- .../payment/mn_msa_special_needs_total.yaml | 4 +- .../msa/eligibility/mn_msa_eligible_person.py | 27 ++-- .../eligibility/mn_msa_resource_eligible.py | 9 +- .../income/mn_msa_countable_earned_income.py | 32 ++-- .../mn_msa_countable_unearned_income.py | 18 +-- .../income/mn_msa_earned_income_disregard.py | 39 +++-- .../msa/mn_msa_housing_assistance_eligible.py | 39 +---- ...msa_housing_assistance_pathway_eligible.py | 16 -- .../gov/states/mn/dhs/msa/mn_msa_person.py | 60 +++---- .../msa/payment/mn_msa_assistance_standard.py | 3 +- .../mn/dhs/msa/payment/mn_msa_guardian_fee.py | 3 - .../msa/payment/mn_msa_housing_assistance.py | 9 -- .../msa/payment/mn_msa_payment_category.py | 16 +- .../mn_msa_representative_payee_fee.py | 4 - .../msa/payment/mn_msa_special_needs_total.py | 7 +- 32 files changed, 377 insertions(+), 287 deletions(-) delete mode 100644 policyengine_us/variables/gov/states/mn/dhs/msa/mn_msa_housing_assistance_pathway_eligible.py diff --git a/policyengine_us/parameters/gov/states/mn/dhs/msa/assistance_standard/amount.yaml b/policyengine_us/parameters/gov/states/mn/dhs/msa/assistance_standard/amount.yaml index a891f64a7f9..af0bb737ca2 100644 --- a/policyengine_us/parameters/gov/states/mn/dhs/msa/assistance_standard/amount.yaml +++ b/policyengine_us/parameters/gov/states/mn/dhs/msa/assistance_standard/amount.yaml @@ -1,4 +1,4 @@ -description: Minnesota provides this combined federal-plus-state assistance standard by living arrangement under the Minnesota Supplemental Aid program. +description: Minnesota provides this amount as the combined federal-and-state assistance standard under the Minnesota Supplemental Aid program. metadata: unit: currency-USD diff --git a/policyengine_us/parameters/gov/states/mn/dhs/msa/disregard/earned/initial.yaml b/policyengine_us/parameters/gov/states/mn/dhs/msa/disregard/earned/initial.yaml index 3fee1de5377..29e396bfa06 100644 --- a/policyengine_us/parameters/gov/states/mn/dhs/msa/disregard/earned/initial.yaml +++ b/policyengine_us/parameters/gov/states/mn/dhs/msa/disregard/earned/initial.yaml @@ -1,4 +1,4 @@ -description: Minnesota disregards this initial amount of earned income for non-SSI recipients under the Minnesota Supplemental Aid program. +description: Minnesota excludes this amount of earned income under the Minnesota Supplemental Aid program. values: 1989-01-01: 65 diff --git a/policyengine_us/parameters/gov/states/mn/dhs/msa/disregard/earned/rate.yaml b/policyengine_us/parameters/gov/states/mn/dhs/msa/disregard/earned/rate.yaml index 3b9980759c6..bea8d27ccd3 100644 --- a/policyengine_us/parameters/gov/states/mn/dhs/msa/disregard/earned/rate.yaml +++ b/policyengine_us/parameters/gov/states/mn/dhs/msa/disregard/earned/rate.yaml @@ -1,11 +1,11 @@ -description: Minnesota disregards this share of remaining earned income for non-SSI recipients under the Minnesota Supplemental Aid program. +description: Minnesota excludes this share of remaining earned income under the Minnesota Supplemental Aid program. values: 1989-01-01: 0.5 metadata: unit: /1 - period: month + period: year label: Minnesota Supplemental Aid earned income disregard rate reference: - title: MN DHS Combined Manual 0018.18 - Earned Income Disregards (01/2026) diff --git a/policyengine_us/parameters/gov/states/mn/dhs/msa/disregard/general.yaml b/policyengine_us/parameters/gov/states/mn/dhs/msa/disregard/general.yaml index 4568f6fede6..e0a87889491 100644 --- a/policyengine_us/parameters/gov/states/mn/dhs/msa/disregard/general.yaml +++ b/policyengine_us/parameters/gov/states/mn/dhs/msa/disregard/general.yaml @@ -1,14 +1,14 @@ -description: Minnesota disregards this amount of general (unearned-first, then earned) income under the Minnesota Supplemental Aid program, mirroring the federal SSI $20 general income disregard. +description: Minnesota excludes this amount of general income under the Minnesota Supplemental Aid program. values: - 1989-01-01: 20 + 1974-04-01: 20 metadata: unit: currency-USD period: month label: Minnesota Supplemental Aid general income disregard reference: - - title: MN DHS Combined Manual 0018.18 - Earned Income Disregards (01/2026) - href: https://www.dhs.state.mn.us/main/groups/county_access/documents/pub/mndhs-073585.pdf#page=2 - - title: MN House Research Department - Minnesota Supplemental Aid (October 2024) + - title: 20 CFR § 416.1124(c)(12) + href: https://www.ecfr.gov/current/title-20/chapter-III/part-416/subpart-K/section-416.1124#p-416.1124(c)(12) + - title: MN House Research Department - Minnesota Supplemental Aid (October 2024), Income standards and treatment of income href: https://www.house.mn.gov/hrd/pubs/pap_MSA.pdf#page=3 diff --git a/policyengine_us/parameters/gov/states/mn/dhs/msa/eligibility/asset_limit/non_ssi_track.yaml b/policyengine_us/parameters/gov/states/mn/dhs/msa/eligibility/asset_limit/non_ssi_track.yaml index 9b2a11c0163..e9a13222700 100644 --- a/policyengine_us/parameters/gov/states/mn/dhs/msa/eligibility/asset_limit/non_ssi_track.yaml +++ b/policyengine_us/parameters/gov/states/mn/dhs/msa/eligibility/asset_limit/non_ssi_track.yaml @@ -1,14 +1,14 @@ -description: Minnesota limits countable resources to this amount for non-SSI excess-income recipients under the Minnesota Supplemental Aid program. +description: Minnesota limits countable resources to this amount under the Minnesota Supplemental Aid program for the non-SSI excess-income track. values: 1989-01-01: 10_000 metadata: unit: currency-USD - period: month + period: year label: Minnesota Supplemental Aid non-SSI track asset limit reference: - - title: MN House Research Department - Minnesota Supplemental Aid (October 2024) + - title: Minn. Stat. § 256D.37 Subd. 1 + href: https://www.revisor.mn.gov/statutes/cite/256D.37 + - title: MN House Research Department - Minnesota Supplemental Aid (October 2024), Resource and asset limits href: https://www.house.mn.gov/hrd/pubs/pap_MSA.pdf#page=4 - - title: Minn. Stat. § 256D.44 - href: https://www.revisor.mn.gov/statutes/cite/256D.44 diff --git a/policyengine_us/parameters/gov/states/mn/dhs/msa/eligibility/income_limit/couple_fbr_multiplier.yaml b/policyengine_us/parameters/gov/states/mn/dhs/msa/eligibility/income_limit/couple_fbr_multiplier.yaml index 2bf3ccaa4fc..857e34df917 100644 --- a/policyengine_us/parameters/gov/states/mn/dhs/msa/eligibility/income_limit/couple_fbr_multiplier.yaml +++ b/policyengine_us/parameters/gov/states/mn/dhs/msa/eligibility/income_limit/couple_fbr_multiplier.yaml @@ -1,14 +1,14 @@ -description: Minnesota limits gross income to this multiple of the federal Supplemental Security Income individual benefit rate for a couple under the Minnesota Supplemental Aid program. +description: Minnesota limits couple gross income to this multiple of the federal Supplemental Security Income individual benefit rate under the Minnesota Supplemental Aid program. values: 1989-01-01: 6 metadata: unit: /1 - period: month + period: year label: Minnesota Supplemental Aid couple gross income FBR multiplier reference: - - title: MN House Research Department - Minnesota Supplemental Aid (October 2024) - href: https://www.house.mn.gov/hrd/pubs/pap_MSA.pdf#page=3 - - title: Minn. Stat. § 256D.44 + - title: Minn. Stat. § 256D.44 Subd. 1 href: https://www.revisor.mn.gov/statutes/cite/256D.44 + - title: MN House Research Department - Minnesota Supplemental Aid (October 2024), Income standards and treatment of income + href: https://www.house.mn.gov/hrd/pubs/pap_MSA.pdf#page=3 diff --git a/policyengine_us/parameters/gov/states/mn/dhs/msa/eligibility/income_limit/individual_fbr_multiplier.yaml b/policyengine_us/parameters/gov/states/mn/dhs/msa/eligibility/income_limit/individual_fbr_multiplier.yaml index b9e1f549560..da1929e20b9 100644 --- a/policyengine_us/parameters/gov/states/mn/dhs/msa/eligibility/income_limit/individual_fbr_multiplier.yaml +++ b/policyengine_us/parameters/gov/states/mn/dhs/msa/eligibility/income_limit/individual_fbr_multiplier.yaml @@ -1,14 +1,14 @@ -description: Minnesota limits gross income to this multiple of the federal Supplemental Security Income individual benefit rate for an individual under the Minnesota Supplemental Aid program. +description: Minnesota limits individual gross income to this multiple of the federal Supplemental Security Income individual benefit rate under the Minnesota Supplemental Aid program. values: 1989-01-01: 3 metadata: unit: /1 - period: month + period: year label: Minnesota Supplemental Aid individual gross income FBR multiplier reference: - - title: MN House Research Department - Minnesota Supplemental Aid (October 2024) - href: https://www.house.mn.gov/hrd/pubs/pap_MSA.pdf#page=3 - - title: Minn. Stat. § 256D.44 + - title: Minn. Stat. § 256D.44 Subd. 1 href: https://www.revisor.mn.gov/statutes/cite/256D.44 + - title: MN House Research Department - Minnesota Supplemental Aid (October 2024), Income standards and treatment of income + href: https://www.house.mn.gov/hrd/pubs/pap_MSA.pdf#page=3 diff --git a/policyengine_us/parameters/gov/states/mn/dhs/msa/housing_assistance/fbr_multiplier.yaml b/policyengine_us/parameters/gov/states/mn/dhs/msa/housing_assistance/fbr_multiplier.yaml index 9bf3da8fcdf..891d5611887 100644 --- a/policyengine_us/parameters/gov/states/mn/dhs/msa/housing_assistance/fbr_multiplier.yaml +++ b/policyengine_us/parameters/gov/states/mn/dhs/msa/housing_assistance/fbr_multiplier.yaml @@ -1,14 +1,14 @@ -description: Minnesota provides this multiple of the federal Supplemental Security Income individual benefit rate as the housing allowance under the Minnesota Supplemental Aid program. Per Minn. Stat. § 256D.44 Subd. 5(g)(1), the allowance equals one-half of the maximum federal SSI payment amount for a single individual in effect on July 1 each year. The same amount is paid regardless of which Subd. 5(g) eligibility pathway qualifies the recipient (institution-relocation / HCBS-waiver / shelter > 40% income, or chronic homelessness / housing stabilization). +description: Minnesota provides this share of the federal Supplemental Security Income individual benefit rate as the housing allowance under the Minnesota Supplemental Aid program. values: 1989-01-01: 0.5 metadata: unit: /1 - period: month + period: year label: Minnesota Supplemental Aid housing allowance FBR multiplier reference: - - title: Minn. Stat. § 256D.44 Subd. 5(g)(1) (Shelter needs special allowance) + - title: Minn. Stat. § 256D.44 Subd. 5(g)(1) href: https://www.revisor.mn.gov/statutes/cite/256D.44 - title: MN DHS Combined Manual 0023.24 - MSA Housing Assistance href: https://www.dhs.state.mn.us/main/idcplg?IdcService=GET_DYNAMIC_CONVERSION&dDocName=cm_002324&RevisionSelectionMethod=LatestReleased diff --git a/policyengine_us/parameters/gov/states/mn/dhs/msa/special_needs/guardian_fee/max_amount.yaml b/policyengine_us/parameters/gov/states/mn/dhs/msa/special_needs/guardian_fee/max_amount.yaml index c536fc0b20d..d09c9e1d7c1 100644 --- a/policyengine_us/parameters/gov/states/mn/dhs/msa/special_needs/guardian_fee/max_amount.yaml +++ b/policyengine_us/parameters/gov/states/mn/dhs/msa/special_needs/guardian_fee/max_amount.yaml @@ -8,7 +8,7 @@ metadata: period: month label: Minnesota Supplemental Aid guardian fee maximum amount reference: - - title: Minn. Stat. § 256D.44 Subd. 5 (Special needs) + - title: Minn. Stat. § 256D.44 Subd. 5(b) href: https://www.revisor.mn.gov/statutes/cite/256D.44 - title: SSA State Assistance Programs for SSI Recipients, January 2011 - Minnesota, Section 9.5 href: https://www.ssa.gov/policy/docs/progdesc/ssi_st_asst/2011/mn.html diff --git a/policyengine_us/parameters/gov/states/mn/dhs/msa/special_needs/guardian_fee/rate.yaml b/policyengine_us/parameters/gov/states/mn/dhs/msa/special_needs/guardian_fee/rate.yaml index 5c78cc5b2c4..2a21f81a47f 100644 --- a/policyengine_us/parameters/gov/states/mn/dhs/msa/special_needs/guardian_fee/rate.yaml +++ b/policyengine_us/parameters/gov/states/mn/dhs/msa/special_needs/guardian_fee/rate.yaml @@ -1,14 +1,14 @@ -description: Minnesota provides this share of gross monthly income as a guardian or conservator fee special-needs allowance under the Minnesota Supplemental Aid program. +description: Minnesota provides this share of gross monthly income as the guardian or conservator fee special-needs allowance under the Minnesota Supplemental Aid program. values: 1989-01-01: 0.05 metadata: unit: /1 - period: month + period: year label: Minnesota Supplemental Aid guardian fee rate reference: - - title: Minn. Stat. § 256D.44 Subd. 5 (Special needs) + - title: Minn. Stat. § 256D.44 Subd. 5(b) href: https://www.revisor.mn.gov/statutes/cite/256D.44 - title: SSA State Assistance Programs for SSI Recipients, January 2011 - Minnesota, Section 9.5 href: https://www.ssa.gov/policy/docs/progdesc/ssi_st_asst/2011/mn.html diff --git a/policyengine_us/parameters/gov/states/mn/dhs/msa/special_needs/representative_payee_fee/amount.yaml b/policyengine_us/parameters/gov/states/mn/dhs/msa/special_needs/representative_payee_fee/amount.yaml index 7c50c7546a7..f4d99c63c2c 100644 --- a/policyengine_us/parameters/gov/states/mn/dhs/msa/special_needs/representative_payee_fee/amount.yaml +++ b/policyengine_us/parameters/gov/states/mn/dhs/msa/special_needs/representative_payee_fee/amount.yaml @@ -1,6 +1,7 @@ description: Minnesota provides this amount as the representative payee service special-needs allowance under the Minnesota Supplemental Aid program. values: + 1974-04-01: 0 2026-01-01: 57 metadata: @@ -8,7 +9,7 @@ metadata: period: month label: Minnesota Supplemental Aid representative payee fee amount reference: + - title: Minn. Stat. § 256D.44 Subd. 5(c) + href: https://www.revisor.mn.gov/statutes/cite/256D.44 - title: MN DHS Combined Manual 0023.21 - Representative Payee Services (01/2026) href: https://www.dhs.state.mn.us/main/groups/county_access/documents/pub/mndhs-073585.pdf#page=7 - - title: Minn. Stat. § 256D.44 Subd. 5 (Special needs) - href: https://www.revisor.mn.gov/statutes/cite/256D.44 diff --git a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/eligibility/mn_msa_eligible_person.yaml b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/eligibility/mn_msa_eligible_person.yaml index 713aba752a0..b320fbdf8e4 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/eligibility/mn_msa_eligible_person.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/eligibility/mn_msa_eligible_person.yaml @@ -1,4 +1,4 @@ -- name: Case 1, aged SSI recipient is eligible (SSI-receipt path). +- name: Case 1, aged SSI recipient is eligible. period: 2026-01 input: people: @@ -26,7 +26,7 @@ output: mn_msa_eligible_person: true -- name: Case 2, SSI-eligible-except-for-income at MSA standard is eligible (non-SSI path). +- name: Case 2, SSI-eligible-except-for-income is eligible on the non-SSI track. period: 2026-01 input: people: @@ -83,19 +83,18 @@ output: mn_msa_eligible_person: false -- name: Case 5, P2.3 child applicant with high parent-deemed income fails gross income test. +- name: Case 4, income above MSA standard is ineligible. period: 2026-01 input: people: person1: - age: 10 + age: 70 is_blind: false - is_disabled: true + is_disabled: false immigration_status: CITIZEN ssi_countable_resources: 500 ssi: 0 - ssi_unearned_income: 0 - ssi_unearned_income_deemed_from_ineligible_parent: 60_000 # $5,000/mo + ssi_unearned_income: 24_000 # $2,000/mo > $1,055 standard marital_units: marital_unit: members: [person1] @@ -110,23 +109,22 @@ members: [person1] state_code: MN output: - # Parent-deemed $5,000/mo > 300% individual FBR (~$2,982/mo in 2026) - # -> gross income test fails. (P2.3: parent-deemed unearned now flows - # into MSA gross/countable income for child applicants.) + # Countable $2,000 > $1,055 standard -> not income eligible. mn_msa_eligible_person: false -- name: Case 4, income above MSA standard is ineligible. +- name: Case 5, child applicant with high parent-deemed income fails gross income test. period: 2026-01 input: people: person1: - age: 70 + age: 10 is_blind: false - is_disabled: false + is_disabled: true immigration_status: CITIZEN ssi_countable_resources: 500 ssi: 0 - ssi_unearned_income: 24_000 # $2,000/mo > $1,055 standard + ssi_unearned_income: 0 + ssi_unearned_income_deemed_from_ineligible_parent: 60_000 # $5,000/mo marital_units: marital_unit: members: [person1] @@ -141,7 +139,8 @@ members: [person1] state_code: MN output: - # Countable $2,000 > $1,055 standard -> not income eligible. + # Parent-deemed $5,000/mo > 300% individual FBR (~$2,982/mo in 2026) + # -> gross income test fails. mn_msa_eligible_person: false - name: Case 6, non-qualifying living arrangement is ineligible. @@ -202,3 +201,58 @@ state_code: MN output: mn_msa_eligible_person: false + +- name: Case 8, legal permanent resident aged senior is eligible. + period: 2026-01 + input: + people: + person1: + age: 70 + is_blind: false + is_disabled: false + immigration_status: LEGAL_PERMANENT_RESIDENT + ssi_countable_resources: 1_000 + ssi: 11_928 # $994/mo + marital_units: + marital_unit: + members: [person1] + tax_units: + tax_unit: + members: [person1] + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MN + output: + mn_msa_eligible_person: true + +- name: Case 9, undocumented immigrant fails the immigration check. + period: 2026-01 + input: + people: + person1: + age: 70 + is_blind: false + is_disabled: false + immigration_status: UNDOCUMENTED + ssi_countable_resources: 500 + ssi: 0 + ssi_unearned_income: 0 + marital_units: + marital_unit: + members: [person1] + tax_units: + tax_unit: + members: [person1] + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MN + output: + mn_msa_eligible_person: false diff --git a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/integration.yaml b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/integration.yaml index 3a67f9b104e..c4b8052fe4a 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/integration.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/integration.yaml @@ -174,7 +174,7 @@ is_disabled: true immigration_status: CITIZEN ssi_countable_resources: 500 - mn_msa_housing_assistance_pathway_eligible: true + mn_msa_housing_assistance_eligible: true pre_subsidy_rent: 6_000 ssi: 11_928 ssi_earned_income: 0 @@ -358,7 +358,7 @@ is_disabled: false immigration_status: CITIZEN ssi_countable_resources: 500 - mn_msa_housing_assistance_pathway_eligible: true + mn_msa_housing_assistance_eligible: true pre_subsidy_rent: 6_000 ssi: 11_928 ssi_earned_income: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/mn_msa_housing_assistance_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/mn_msa_housing_assistance_eligible.yaml index 2508641fc38..a824e991268 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/mn_msa_housing_assistance_eligible.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/mn_msa_housing_assistance_eligible.yaml @@ -1,48 +1,9 @@ -- name: Case 1, pathway-eligible person with high shelter costs qualifies. +- name: Case 1, default value is false. period: 2026-01 input: people: person1: age: 70 - immigration_status: CITIZEN - mn_msa_housing_assistance_pathway_eligible: true - pre_subsidy_rent: 6_000 - ssi: 11_928 - marital_units: - marital_unit: - members: [person1] - tax_units: - tax_unit: - members: [person1] - spm_units: - spm_unit: - members: [person1] - households: - household: - members: [person1] - state_code: MN - output: - mn_msa_housing_assistance_eligible: true - -- name: Case 2, shelter costs at or below 40 percent of gross income do not qualify. - period: 2026-01 - input: - people: - person1: - age: 70 - immigration_status: CITIZEN - mn_msa_housing_assistance_pathway_eligible: true - pre_subsidy_rent: 4_764 - ssi: 11_928 - marital_units: - marital_unit: - members: [person1] - tax_units: - tax_unit: - members: [person1] - spm_units: - spm_unit: - members: [person1] households: household: members: [person1] @@ -50,29 +11,16 @@ output: mn_msa_housing_assistance_eligible: false -- name: Case 3, current housing assistance prevents eligibility. +- name: Case 2, user-provided true passes through. period: 2026-01 input: people: person1: age: 70 - immigration_status: CITIZEN - mn_msa_housing_assistance_pathway_eligible: true - pre_subsidy_rent: 6_000 - ssi: 11_928 - marital_units: - marital_unit: - members: [person1] - tax_units: - tax_unit: - members: [person1] - spm_units: - spm_unit: - members: [person1] - receives_housing_assistance: true + mn_msa_housing_assistance_eligible: true households: household: members: [person1] state_code: MN output: - mn_msa_housing_assistance_eligible: false + mn_msa_housing_assistance_eligible: true diff --git a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/mn_msa_person.yaml b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/mn_msa_person.yaml index a48333fd3c1..5a5d086bb1b 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/mn_msa_person.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/mn_msa_person.yaml @@ -286,7 +286,7 @@ is_disabled: true immigration_status: CITIZEN mn_msa_living_arrangement: INDIVIDUAL_LIVING_WITH_OTHERS - mn_msa_housing_assistance_pathway_eligible: true + mn_msa_housing_assistance_eligible: true pre_subsidy_rent: 6_000 ssi_countable_resources: 500 ssi: 11_928 @@ -310,3 +310,154 @@ # Base $81 plus housing allowance $497 = $578. mn_msa_payment_category: INDIVIDUAL_LIVING_ALONE mn_msa_person: 578 + +- name: Case 10, asymmetric SSI couple — only one spouse has extra unearned income. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 70 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 800 + mn_msa_living_arrangement: COUPLE_LIVING_ALONE + ssi_claim_is_joint: true + ssi: 8_946 # $745.50/mo (couple FBR / 2) + ssi_earned_income: 0 + ssi_unearned_income: 2_400 # $200/mo to spouse A only + person2: + age: 68 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 800 + mn_msa_living_arrangement: COUPLE_LIVING_ALONE + ssi_claim_is_joint: true + ssi: 8_946 + ssi_earned_income: 0 + ssi_unearned_income: 0 + 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: MN + output: + # Couple-level: SSI couple FBR $1,491 + $200 unearned - $20 disregard = $1,671 countable. + # Standard $1,582 - $1,671 = floored at $0. Bug fix: no over-payment from per-spouse floor. + mn_msa_person: [0, 0] + +- name: Case 11, asymmetric non-SSI couple — only one spouse has earned income. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 70 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 5_000 + mn_msa_living_arrangement: COUPLE_LIVING_ALONE + ssi: 0 + ssi_earned_income: 12_000 # $1,000/mo to spouse A only + ssi_unearned_income: 0 + person2: + age: 68 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 5_000 + mn_msa_living_arrangement: COUPLE_LIVING_ALONE + ssi: 0 + ssi_earned_income: 0 + ssi_unearned_income: 0 + 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: MN + output: + # Couple earned $1,000 - ($20 + $65) = $915 over flat; disregard = $85 + $915*0.5 = $542.50. + # Couple countable = max($1,000 - $542.50, 0) = $457.50. + # Couple supplement = max($1,582 - $457.50, 0) = $1,124.50, split 50/50 = $562.25 per spouse. + mn_msa_person: [562.25, 562.25] + +- name: Case 12, SSI-track recipient with extra unearned income reduces supplement. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 70 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 500 + ssi: 11_928 # $994/mo SSI + ssi_earned_income: 0 + ssi_unearned_income: 2_400 # $200/mo extra unearned + marital_units: + marital_unit: + members: [person1] + tax_units: + tax_unit: + members: [person1] + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MN + output: + # Standard $1,055 - max($994 + $200 - $20, 0) = $1,055 - $1,174 = floored at $0. + mn_msa_person: 0 + +- name: Case 13, negative self-employment does not inflate the supplement. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 70 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 500 + ssi: 11_928 # $994/mo SSI + self_employment_income: -12_000_000 # -$1M/mo extreme negative + marital_units: + marital_unit: + members: [person1] + tax_units: + tax_unit: + members: [person1] + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MN + output: + # Negative earnings must not push supplement above the standard. + # SSI-track countable floors at 0; supplement = $1,055 - max($994 - $20, 0) = $81. + mn_msa_person: 81 diff --git a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/payment/mn_msa_housing_assistance.yaml b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/payment/mn_msa_housing_assistance.yaml index 5f254011fae..f07bd3ce74b 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/payment/mn_msa_housing_assistance.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/payment/mn_msa_housing_assistance.yaml @@ -9,7 +9,7 @@ is_disabled: true immigration_status: CITIZEN ssi_countable_resources: 500 - mn_msa_housing_assistance_pathway_eligible: true + mn_msa_housing_assistance_eligible: true pre_subsidy_rent: 6_000 ssi: 11_928 marital_units: @@ -40,7 +40,7 @@ is_disabled: true immigration_status: CITIZEN ssi_countable_resources: 500 - mn_msa_housing_assistance_pathway_eligible: false + mn_msa_housing_assistance_eligible: false pre_subsidy_rent: 6_000 ssi: 11_928 marital_units: diff --git a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/payment/mn_msa_payment_category.yaml b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/payment/mn_msa_payment_category.yaml index 896159f3e6c..dce37fc9a18 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/payment/mn_msa_payment_category.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/payment/mn_msa_payment_category.yaml @@ -49,7 +49,7 @@ age: 50 is_disabled: true mn_msa_living_arrangement: INDIVIDUAL_LIVING_WITH_OTHERS - mn_msa_housing_assistance_pathway_eligible: true + mn_msa_housing_assistance_eligible: true pre_subsidy_rent: 6_000 ssi: 11_928 households: @@ -66,13 +66,13 @@ person1: age: 70 mn_msa_living_arrangement: COUPLE_LIVING_WITH_OTHERS - mn_msa_housing_assistance_pathway_eligible: true + mn_msa_housing_assistance_eligible: true pre_subsidy_rent: 6_000 ssi: 11_928 person2: age: 70 mn_msa_living_arrangement: COUPLE_LIVING_WITH_OTHERS - mn_msa_housing_assistance_pathway_eligible: false + mn_msa_housing_assistance_eligible: false marital_units: marital_unit: members: [person1, person2] diff --git a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/payment/mn_msa_special_needs_total.yaml b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/payment/mn_msa_special_needs_total.yaml index 16ff8738bc5..3ff6624fbe7 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/payment/mn_msa_special_needs_total.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/payment/mn_msa_special_needs_total.yaml @@ -13,7 +13,7 @@ ssi_medicaid_pays_majority_of_care: true mn_msa_uses_representative_payee: true mn_msa_has_guardian: true - mn_msa_housing_assistance_pathway_eligible: true + mn_msa_housing_assistance_eligible: true pre_subsidy_rent: 18_000 ssi: 360 ssi_unearned_income: 24_000 @@ -48,7 +48,7 @@ ssi_countable_resources: 500 mn_msa_uses_representative_payee: true mn_msa_has_guardian: true - mn_msa_housing_assistance_pathway_eligible: true + mn_msa_housing_assistance_eligible: true pre_subsidy_rent: 12_000 ssi_unearned_income: 24_000 marital_units: diff --git a/policyengine_us/variables/gov/states/mn/dhs/msa/eligibility/mn_msa_eligible_person.py b/policyengine_us/variables/gov/states/mn/dhs/msa/eligibility/mn_msa_eligible_person.py index 0dc292d89f9..48c83e954c7 100644 --- a/policyengine_us/variables/gov/states/mn/dhs/msa/eligibility/mn_msa_eligible_person.py +++ b/policyengine_us/variables/gov/states/mn/dhs/msa/eligibility/mn_msa_eligible_person.py @@ -13,26 +13,19 @@ class mn_msa_eligible_person(Variable): ) def formula(person, period, parameters): - # Per Minn. Stat. § 256D.44 Subd. 1, MSA recipients must be - # categorically aged/blind/disabled, meet the resource and - # gross/net income tests, and reside in Minnesota with a - # qualifying immigration status (citizen or qualified noncitizen). - arrangement = person("mn_msa_payment_category", period) - qualifies_arrangement = arrangement != arrangement.possible_values.NONE + # Per Minn. Stat. § 256D.44 Subd. 1: aged 65+, blind, or 18+ with a + # disability; resource/gross/net income tests; MN resident; citizen + # or qualified noncitizen; in a qualifying living arrangement. age = person("age", period.this_year) aged = person("is_ssi_aged", period.this_year) blind = person("is_blind", period.this_year) disabled_adult = (age >= 18) & person("is_ssi_disabled", period.this_year) - categorically_eligible = aged | blind | disabled_adult - resource_eligible = person("mn_msa_resource_eligible", period) - gross_income_eligible = person("mn_msa_gross_income_eligible", period) - net_income_eligible = person("mn_msa_net_income_eligible", period) - meets_immigration = person("is_citizen_or_legal_immigrant", period.this_year) + arrangement = person("mn_msa_payment_category", period) return ( - qualifies_arrangement - & categorically_eligible - & resource_eligible - & gross_income_eligible - & net_income_eligible - & meets_immigration + (arrangement != arrangement.possible_values.NONE) + & (aged | blind | disabled_adult) + & person("mn_msa_resource_eligible", period) + & person("mn_msa_gross_income_eligible", period) + & person("mn_msa_net_income_eligible", period) + & person("is_citizen_or_legal_immigrant", period.this_year) ) diff --git a/policyengine_us/variables/gov/states/mn/dhs/msa/eligibility/mn_msa_resource_eligible.py b/policyengine_us/variables/gov/states/mn/dhs/msa/eligibility/mn_msa_resource_eligible.py index d40afd53840..0ddf4210990 100644 --- a/policyengine_us/variables/gov/states/mn/dhs/msa/eligibility/mn_msa_resource_eligible.py +++ b/policyengine_us/variables/gov/states/mn/dhs/msa/eligibility/mn_msa_resource_eligible.py @@ -9,16 +9,13 @@ class mn_msa_resource_eligible(Variable): defined_for = StateCode.MN reference = ( "https://www.revisor.mn.gov/statutes/cite/256D.37", - "https://www.lawhelpmn.org/self-help-library/fact-sheet/minnesota-supplemental-aid-msa", "https://www.house.mn.gov/hrd/pubs/pap_MSA.pdf#page=2", ) def formula(person, period, parameters): - # Per Minn. Stat. § 256D.37: SSI track recipients (ssi > 0) already - # passed the federal SSI $2,000/$3,000 resource test. The non-SSI - # excess-income track uses a higher $10,000 personal-property cap. + # SSI recipients have already passed the federal $2,000/$3,000 + # resource test. Non-SSI excess-income track uses MN's $10,000 cap. p = parameters(period).gov.states.mn.dhs.msa.eligibility receives_ssi = person("ssi", period) > 0 countable_resources = person("ssi_countable_resources", period.this_year) - non_ssi_track_eligible = countable_resources <= p.asset_limit.non_ssi_track - return receives_ssi | non_ssi_track_eligible + return receives_ssi | (countable_resources <= p.asset_limit.non_ssi_track) diff --git a/policyengine_us/variables/gov/states/mn/dhs/msa/income/mn_msa_countable_earned_income.py b/policyengine_us/variables/gov/states/mn/dhs/msa/income/mn_msa_countable_earned_income.py index 81e2658204e..e52247b105a 100644 --- a/policyengine_us/variables/gov/states/mn/dhs/msa/income/mn_msa_countable_earned_income.py +++ b/policyengine_us/variables/gov/states/mn/dhs/msa/income/mn_msa_countable_earned_income.py @@ -14,14 +14,9 @@ class mn_msa_countable_earned_income(Variable): ) def formula(person, period, parameters): - # Per MN DHS Combined Manual 0018.18: "For SSI recipients, no - # county action is required. For non-SSI recipients due to - # excess income, disregard the 1st $65 of earned income plus - # half of the remaining earned income." For SSI-track recipients - # the federal $20 + $65 + 1/2 disregards are already consumed - # inside ssi_countable_income, so MSA-side earned income is - # treated as fully disregarded (returned as 0) to avoid - # double-counting. + # SSI-track recipients already have $20 + $65 + 1/2 disregards + # consumed inside ssi_countable_income, so MSA earned is treated + # as fully disregarded to avoid double-counting. gross_earned = add( person, period, @@ -31,6 +26,25 @@ def formula(person, period, parameters): ], ) disregard = person("mn_msa_earned_income_disregard", period) - non_ssi_track_countable = max_(gross_earned - disregard, 0) + arrangement = person("mn_msa_payment_category", period) + LA = arrangement.possible_values + is_couple = (arrangement == LA.COUPLE_LIVING_ALONE) | ( + arrangement == LA.COUPLE_LIVING_WITH_OTHERS + ) + # Couples: aggregate gross earned and disregard at marital unit, + # then split countable 50/50 — prevents wasting the disregard + # when one spouse has zero earnings. + couple_countable = ( + max_( + person.marital_unit.sum(gross_earned) + - person.marital_unit.sum(disregard), + 0, + ) + / 2 + ) + individual_countable = max_(gross_earned - disregard, 0) + non_ssi_track_countable = where( + is_couple, couple_countable, individual_countable + ) receives_ssi = person("ssi", period) > 0 return where(receives_ssi, 0, non_ssi_track_countable) diff --git a/policyengine_us/variables/gov/states/mn/dhs/msa/income/mn_msa_countable_unearned_income.py b/policyengine_us/variables/gov/states/mn/dhs/msa/income/mn_msa_countable_unearned_income.py index c7592155aa6..25e2a420a3f 100644 --- a/policyengine_us/variables/gov/states/mn/dhs/msa/income/mn_msa_countable_unearned_income.py +++ b/policyengine_us/variables/gov/states/mn/dhs/msa/income/mn_msa_countable_unearned_income.py @@ -10,15 +10,10 @@ class mn_msa_countable_unearned_income(Variable): defined_for = StateCode.MN reference = ( "https://www.revisor.mn.gov/statutes/cite/256D.44", - "https://www.dhs.state.mn.us/main/groups/county_access/documents/pub/mndhs-073585.pdf#page=2", "https://www.house.mn.gov/hrd/pubs/pap_MSA.pdf#page=3", ) def formula(person, period, parameters): - # MSA inherits the federal SSI $20 general income disregard. The - # disregard applies first to unearned income; any unused remainder - # rolls over to earned income (handled in - # mn_msa_countable_earned_income). gross_unearned = add( person, period, @@ -29,13 +24,14 @@ def formula(person, period, parameters): ], ) general = parameters(period).gov.states.mn.dhs.msa.disregard.general - # The COUPLE_* assistance standards are couple totals split 50/50 - # onto each spouse, so the $20 general disregard is also applied - # once per couple — half to each spouse. arrangement = person("mn_msa_payment_category", period) LA = arrangement.possible_values - is_couple_arrangement = (arrangement == LA.COUPLE_LIVING_ALONE) | ( + is_couple = (arrangement == LA.COUPLE_LIVING_ALONE) | ( arrangement == LA.COUPLE_LIVING_WITH_OTHERS ) - per_person_general = where(is_couple_arrangement, general / 2, general) - return max_(gross_unearned - per_person_general, 0) + # Couples: aggregate at marital unit, apply $20 once, split 50/50. + couple_countable = ( + max_(person.marital_unit.sum(gross_unearned) - general, 0) / 2 + ) + individual_countable = max_(gross_unearned - general, 0) + return where(is_couple, couple_countable, individual_countable) diff --git a/policyengine_us/variables/gov/states/mn/dhs/msa/income/mn_msa_earned_income_disregard.py b/policyengine_us/variables/gov/states/mn/dhs/msa/income/mn_msa_earned_income_disregard.py index 41a1c0d02c4..7e95c0a3597 100644 --- a/policyengine_us/variables/gov/states/mn/dhs/msa/income/mn_msa_earned_income_disregard.py +++ b/policyengine_us/variables/gov/states/mn/dhs/msa/income/mn_msa_earned_income_disregard.py @@ -14,12 +14,6 @@ class mn_msa_earned_income_disregard(Variable): ) def formula(person, period, parameters): - # Per MN DHS Combined Manual 0018.18, MSA's non-SSI track - # disregards the first $65 of earned income plus half the - # remainder. The federal $20 general disregard also applies on - # the non-SSI track (per House Research and CM 0018.18); it - # consumes unearned income first and any leftover rolls into - # this earned-income calculation. p = parameters(period).gov.states.mn.dhs.msa.disregard gross_unearned = add( person, @@ -38,22 +32,27 @@ def formula(person, period, parameters): "ssi_earned_income_deemed_from_ineligible_spouse", ], ) - # The COUPLE_* assistance standards are couple totals split 50/50 - # onto each spouse, so the $20 general and $65 earned-initial - # disregards are also applied once per couple — half to each - # spouse. The 1/2 leftover-earned rate applies to per-spouse gross - # earned and is unchanged. arrangement = person("mn_msa_payment_category", period) LA = arrangement.possible_values - is_couple_arrangement = (arrangement == LA.COUPLE_LIVING_ALONE) | ( + is_couple = (arrangement == LA.COUPLE_LIVING_ALONE) | ( arrangement == LA.COUPLE_LIVING_WITH_OTHERS ) - per_person_general = where(is_couple_arrangement, p.general / 2, p.general) - per_person_earned_initial = where( - is_couple_arrangement, p.earned.initial / 2, p.earned.initial + # Couples: aggregate to marital unit, apply $20 + $65 + 1/2 once, + # then split 50/50 across spouses. + couple_unearned = person.marital_unit.sum(gross_unearned) + couple_earned = person.marital_unit.sum(gross_earned) + couple_leftover = max_(p.general - couple_unearned, 0) + couple_flat = p.earned.initial + couple_leftover + couple_flat_disregard = min_(couple_earned, couple_flat) + couple_remainder = max_(couple_earned - couple_flat, 0) + couple_per_spouse = ( + couple_flat_disregard + couple_remainder * p.earned.rate + ) / 2 + individual_leftover = max_(p.general - gross_unearned, 0) + individual_flat = p.earned.initial + individual_leftover + individual_flat_disregard = min_(gross_earned, individual_flat) + individual_remainder = max_(gross_earned - individual_flat, 0) + individual_disregard = ( + individual_flat_disregard + individual_remainder * p.earned.rate ) - leftover_general = max_(per_person_general - gross_unearned, 0) - flat = per_person_earned_initial + leftover_general - flat_disregard = min_(gross_earned, flat) - remainder = max_(gross_earned - flat, 0) - return flat_disregard + remainder * p.earned.rate + return where(is_couple, couple_per_spouse, individual_disregard) diff --git a/policyengine_us/variables/gov/states/mn/dhs/msa/mn_msa_housing_assistance_eligible.py b/policyengine_us/variables/gov/states/mn/dhs/msa/mn_msa_housing_assistance_eligible.py index a3108fb55e1..f135914f585 100644 --- a/policyengine_us/variables/gov/states/mn/dhs/msa/mn_msa_housing_assistance_eligible.py +++ b/policyengine_us/variables/gov/states/mn/dhs/msa/mn_msa_housing_assistance_eligible.py @@ -1,45 +1,18 @@ from policyengine_us.model_api import * -from policyengine_us.variables.gov.states.mn.dhs.msa.mn_msa_living_arrangement import ( - MNMSALivingArrangement, -) class mn_msa_housing_assistance_eligible(Variable): value_type = bool entity = Person - label = "Eligible for the Minnesota Supplemental Aid housing allowance under § 256D.44 Subd. 5(g)" + label = "Eligible for the Minnesota Supplemental Aid housing allowance" definition_period = MONTH defined_for = StateCode.MN reference = ( "https://www.revisor.mn.gov/statutes/cite/256D.44", "https://www.dhs.state.mn.us/main/idcplg?IdcService=GET_DYNAMIC_CONVERSION&dDocName=cm_002324&RevisionSelectionMethod=LatestReleased", ) - - def formula(person, period, parameters): - pathway_eligible = person("mn_msa_housing_assistance_pathway_eligible", period) - living_arrangement = person("mn_msa_living_arrangement", period) - LA = MNMSALivingArrangement - is_couple_arrangement = (living_arrangement == LA.COUPLE_LIVING_ALONE) | ( - living_arrangement == LA.COUPLE_LIVING_WITH_OTHERS - ) - - rent = person("rent", period) - utility_expense = person.spm_unit("utility_expense", period) - shelter_cost = where( - is_couple_arrangement, - person.marital_unit.sum(rent) + utility_expense, - rent + utility_expense, - ) - - gross_income = person("mn_msa_gross_income", period) - assistance_unit_income = where( - is_couple_arrangement, - person.marital_unit.sum(gross_income), - gross_income, - ) - high_shelter_cost = shelter_cost > 0.4 * assistance_unit_income - receives_housing_assistance = person.spm_unit( - "receives_housing_assistance", period - ) - - return pathway_eligible & high_shelter_cost & ~receives_housing_assistance + # TODO: model the § 256D.44 Subd. 5(g) eligibility test in a follow-up + # PR — non-financial pathway (institution-relocation / HCBS-waiver / + # chronic homelessness / housing stabilization), shelter cost > 40% + # of unit gross income, and no concurrent housing subsidy. For now + # this is a bare bool input. diff --git a/policyengine_us/variables/gov/states/mn/dhs/msa/mn_msa_housing_assistance_pathway_eligible.py b/policyengine_us/variables/gov/states/mn/dhs/msa/mn_msa_housing_assistance_pathway_eligible.py deleted file mode 100644 index 55807122b13..00000000000 --- a/policyengine_us/variables/gov/states/mn/dhs/msa/mn_msa_housing_assistance_pathway_eligible.py +++ /dev/null @@ -1,16 +0,0 @@ -from policyengine_us.model_api import * - - -class mn_msa_housing_assistance_pathway_eligible(Variable): - value_type = bool - entity = Person - label = ( - "Meets a non-financial Minnesota Supplemental Aid housing assistance pathway" - ) - documentation = "Whether the person meets one of the non-financial pathways for MSA Housing Assistance, such as transition from an institution, HCBS waiver eligibility, chronic homelessness, or housing stabilization." - definition_period = MONTH - defined_for = StateCode.MN - reference = ( - "https://www.revisor.mn.gov/statutes/cite/256D.44", - "https://www.dhs.state.mn.us/main/idcplg?IdcService=GET_DYNAMIC_CONVERSION&dDocName=cm_002324&RevisionSelectionMethod=LatestReleased", - ) diff --git a/policyengine_us/variables/gov/states/mn/dhs/msa/mn_msa_person.py b/policyengine_us/variables/gov/states/mn/dhs/msa/mn_msa_person.py index 28378f5bc4f..8e6f074e4a7 100644 --- a/policyengine_us/variables/gov/states/mn/dhs/msa/mn_msa_person.py +++ b/policyengine_us/variables/gov/states/mn/dhs/msa/mn_msa_person.py @@ -15,27 +15,18 @@ class mn_msa_person(Variable): ) def formula(person, period, parameters): - # Per Minn. Stat. § 256D.44 Subd. 1 and House Research (Oct 2024) p.3: - # for SSI-track recipients, the county counts the federal SSI grant as - # gross unearned income and then allows for a $20 general income - # disregard against the combined (federal SSI + other unearned). The - # federal SSI calculation already absorbed earned disregards, so MSA - # does not re-apply $65 + 1/2 to earned for the SSI track. - # For non-SSI excess-income recipients, there is no federal SSI; the - # countable_income variable applies the $20 + $65 + 1/2 disregards - # following the federal SSI methodology. - # The COUPLE_* assistance standards are couple totals, so split them - # 50/50 across the two eligible spouses. ssi is YEAR-defined with USD - # units, so accessing it from this MONTH formula auto-divides by 12. - # Empirical anchor: SSA 2011 Table 1 living-alone individual reports - # state portion = $81 = $735 standard - max($674 SSI - $20, 0). + # SSI track counts federal SSI FBR as gross unearned, then applies + # the $20 general disregard. FLA-D (Medicaid facility) is exempt + # from the $20 disregard. Non-SSI track uses mn_msa_countable_income + # (already aggregates $20 + $65 + 1/2 federally). + # Couples are computed at the marital unit then split 50/50. arrangement = person("mn_msa_payment_category", period) LA = arrangement.possible_values - is_couple_arrangement = (arrangement == LA.COUPLE_LIVING_ALONE) | ( + is_couple = (arrangement == LA.COUPLE_LIVING_ALONE) | ( arrangement == LA.COUPLE_LIVING_WITH_OTHERS ) + is_medicaid_facility = arrangement == LA.MEDICAID_FACILITY standard = person("mn_msa_assistance_standard", period) - per_person_standard = where(is_couple_arrangement, standard / 2, standard) ssi = person("ssi", period) receives_ssi = ssi > 0 ssi_fbr = person("ssi_amount_if_eligible", period) @@ -49,21 +40,32 @@ def formula(person, period, parameters): "ssi_unearned_income_deemed_from_ineligible_parent", ], ) - # The $20 disregard applies to FLA-A (living alone) and FLA-B (with - # others) recipients but NOT to FLA-D (Medicaid facility) recipients, - # whose $30 federal SSI is a strict personal-needs cap. SSA 2011 - # Table 1: Medicaid-facility individual state portion = $59 = $89 - - # $30 with no $20 added back. - is_medicaid_facility = arrangement == LA.MEDICAID_FACILITY disregard = where(is_medicaid_facility, 0, p.general) - # For couples, the $20 disregard is applied once to combined couple - # income; allocate half to each spouse so the per-person formulas sum - # to the correct couple total. - per_person_disregard = where(is_couple_arrangement, disregard / 2, disregard) - ssi_track_countable = max_(ssi_fbr + raw_unearned - per_person_disregard, 0) - ssi_track = max_(0, per_person_standard - ssi_track_countable) + + # SSI track: aggregate FBR + unearned at marital unit for couples, + # apply $20 once, then split supplement 50/50. + couple_ssi_countable = max_( + person.marital_unit.sum(ssi_fbr) + + person.marital_unit.sum(raw_unearned) + - disregard, + 0, + ) + couple_ssi_supplement = max_(standard - couple_ssi_countable, 0) / 2 + individual_ssi_countable = max_(ssi_fbr + raw_unearned - disregard, 0) + individual_ssi_supplement = max_(standard - individual_ssi_countable, 0) + ssi_track = where(is_couple, couple_ssi_supplement, individual_ssi_supplement) + + # Non-SSI track: countable_income is already per-spouse share for + # couples; sum across the marital unit gives the couple total. countable_income = person("mn_msa_countable_income", period) - non_ssi_track = max_(0, per_person_standard - countable_income) + couple_non_ssi_supplement = ( + max_(standard - person.marital_unit.sum(countable_income), 0) / 2 + ) + individual_non_ssi_supplement = max_(standard - countable_income, 0) + non_ssi_track = where( + is_couple, couple_non_ssi_supplement, individual_non_ssi_supplement + ) + base = where(receives_ssi, ssi_track, non_ssi_track) special_needs = person("mn_msa_special_needs_total", period) return base + special_needs diff --git a/policyengine_us/variables/gov/states/mn/dhs/msa/payment/mn_msa_assistance_standard.py b/policyengine_us/variables/gov/states/mn/dhs/msa/payment/mn_msa_assistance_standard.py index 774077755b9..fae943af1ec 100644 --- a/policyengine_us/variables/gov/states/mn/dhs/msa/payment/mn_msa_assistance_standard.py +++ b/policyengine_us/variables/gov/states/mn/dhs/msa/payment/mn_msa_assistance_standard.py @@ -15,5 +15,4 @@ class mn_msa_assistance_standard(Variable): def formula(person, period, parameters): p = parameters(period).gov.states.mn.dhs.msa.assistance_standard - arrangement = person("mn_msa_payment_category", period) - return p.amount[arrangement] + return p.amount[person("mn_msa_payment_category", period)] diff --git a/policyengine_us/variables/gov/states/mn/dhs/msa/payment/mn_msa_guardian_fee.py b/policyengine_us/variables/gov/states/mn/dhs/msa/payment/mn_msa_guardian_fee.py index 2131ff143a9..dcbc8504514 100644 --- a/policyengine_us/variables/gov/states/mn/dhs/msa/payment/mn_msa_guardian_fee.py +++ b/policyengine_us/variables/gov/states/mn/dhs/msa/payment/mn_msa_guardian_fee.py @@ -14,9 +14,6 @@ class mn_msa_guardian_fee(Variable): ) def formula(person, period, parameters): - # Per SSA 2011 Minnesota Section 9.5: MSA pays the lesser of - # 5% of gross monthly income or $100 to recipients with a - # court-appointed guardian or conservator. p = parameters(period).gov.states.mn.dhs.msa.special_needs.guardian_fee has_guardian = person("mn_msa_has_guardian", period) gross = person("mn_msa_gross_income", period) diff --git a/policyengine_us/variables/gov/states/mn/dhs/msa/payment/mn_msa_housing_assistance.py b/policyengine_us/variables/gov/states/mn/dhs/msa/payment/mn_msa_housing_assistance.py index 77278159e1c..c273311329b 100644 --- a/policyengine_us/variables/gov/states/mn/dhs/msa/payment/mn_msa_housing_assistance.py +++ b/policyengine_us/variables/gov/states/mn/dhs/msa/payment/mn_msa_housing_assistance.py @@ -14,15 +14,6 @@ class mn_msa_housing_assistance(Variable): ) def formula(person, period, parameters): - # Minn. Stat. § 256D.44 Subd. 5(g) authorizes a single housing - # allowance equal to one-half of the federal SSI individual - # benefit rate. Recipients qualify via either pathway: - # (1) Subd. 5(g)(1): institution-relocation, HCBS-waiver - # eligible, or shelter cost > 40% of income (formerly - # modeled as the Shelter Need allowance). - # (2) CM 0023.24: chronic homelessness / housing - # stabilization (the original MSA Housing Assistance). - # The amount is the same (1/2 x FBR) regardless of pathway. p = parameters(period).gov.states.mn.dhs.msa.housing_assistance ssi_fbr = parameters(period).gov.ssa.ssi.amount.individual eligible = person("mn_msa_housing_assistance_eligible", period) diff --git a/policyengine_us/variables/gov/states/mn/dhs/msa/payment/mn_msa_payment_category.py b/policyengine_us/variables/gov/states/mn/dhs/msa/payment/mn_msa_payment_category.py index d0f68b24b8f..1dde9b760ed 100644 --- a/policyengine_us/variables/gov/states/mn/dhs/msa/payment/mn_msa_payment_category.py +++ b/policyengine_us/variables/gov/states/mn/dhs/msa/payment/mn_msa_payment_category.py @@ -18,10 +18,9 @@ class mn_msa_payment_category(Variable): ) def formula(person, period, parameters): - # Per Minn. Stat. § 256D.44 Subd. 2 and Combined Manual 0020.21, - # recipients receiving Medicaid-financed institutional care are paid - # under the FLA-D personal-needs cap regardless of their otherwise - # reported MSA living arrangement. + # Recipients in Medicaid-financed institutional care are paid under + # the FLA-D personal-needs cap regardless of reported arrangement. + # Housing-allowance recipients receive the higher living-alone rate. federal_arrangement = person("ssi_federal_living_arrangement", period.this_year) federal_values = federal_arrangement.possible_values in_medicaid_facility = person("is_in_medicaid_facility", period.this_year) | ( @@ -32,15 +31,12 @@ def formula(person, period, parameters): housing_assistance_eligible = person( "mn_msa_housing_assistance_eligible", period ) - is_couple_arrangement = (living_arrangement == LA.COUPLE_LIVING_ALONE) | ( + is_couple = (living_arrangement == LA.COUPLE_LIVING_ALONE) | ( living_arrangement == LA.COUPLE_LIVING_WITH_OTHERS ) - couple_housing_assistance_eligible = ( - person.marital_unit.sum(housing_assistance_eligible) > 0 - ) apply_housing_standard = where( - is_couple_arrangement, - couple_housing_assistance_eligible, + is_couple, + person.marital_unit.any(housing_assistance_eligible), housing_assistance_eligible, ) housing_standard_arrangement = select( diff --git a/policyengine_us/variables/gov/states/mn/dhs/msa/payment/mn_msa_representative_payee_fee.py b/policyengine_us/variables/gov/states/mn/dhs/msa/payment/mn_msa_representative_payee_fee.py index d0c14a5450f..489ae3c7493 100644 --- a/policyengine_us/variables/gov/states/mn/dhs/msa/payment/mn_msa_representative_payee_fee.py +++ b/policyengine_us/variables/gov/states/mn/dhs/msa/payment/mn_msa_representative_payee_fee.py @@ -14,10 +14,6 @@ class mn_msa_representative_payee_fee(Variable): ) def formula(person, period, parameters): - # Per Minn. Stat. § 256D.44 Subd. 5 and Combined Manual 0023.21, - # MSA pays the SSA standard representative-payee fee to recipients - # who use a representative payee. This input defaults to false; we - # do not model detailed representative-payee eligibility. p = parameters(period).gov.states.mn.dhs.msa.special_needs uses_payee = person("mn_msa_uses_representative_payee", period) return uses_payee * p.representative_payee_fee.amount diff --git a/policyengine_us/variables/gov/states/mn/dhs/msa/payment/mn_msa_special_needs_total.py b/policyengine_us/variables/gov/states/mn/dhs/msa/payment/mn_msa_special_needs_total.py index 76f4a8f3220..8ab21353883 100644 --- a/policyengine_us/variables/gov/states/mn/dhs/msa/payment/mn_msa_special_needs_total.py +++ b/policyengine_us/variables/gov/states/mn/dhs/msa/payment/mn_msa_special_needs_total.py @@ -14,15 +14,14 @@ class mn_msa_special_needs_total(Variable): ) def formula(person, period, parameters): + # Guardian fee and housing allowance are not paid in Medicaid + # facilities; representative payee fee is paid in all settings. arrangement = person("mn_msa_payment_category", period) in_facility = arrangement == arrangement.possible_values.MEDICAID_FACILITY representative_payee = person("mn_msa_representative_payee_fee", period) community_special_needs = add( person, period, - [ - "mn_msa_guardian_fee", - "mn_msa_housing_assistance", - ], + ["mn_msa_guardian_fee", "mn_msa_housing_assistance"], ) return representative_payee + where(in_facility, 0, community_special_needs) From 24b6b63315a15c03541cf8c3e06cd145be7c8043 Mon Sep 17 00:00:00 2001 From: Ziming Date: Sun, 3 May 2026 22:29:28 -0400 Subject: [PATCH 12/17] Fix two assistance-unit eligibility bugs in MN MSA MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Resource cap: § 256D.37 Subd. 1 applies the $10,000 personal-property limit to the assistance unit (married couple combined), not per spouse. Sum ssi_countable_resources at the marital unit before the comparison. Couples with combined resources above the cap (e.g., $6k + $6k) were previously passing because each spouse's share was individually under the limit. Gross-income cap: § 256D.44 Subd. 1 applies the 600% FBR couple cap to any married pair where both spouses are MSA-categorically eligible — not just SSI joint filers. Drop the ssi_claim_is_joint gate. Non-SSI aged couples with combined gross under the couple cap but one spouse above the individual cap were previously being made ineligible. Add tests for both fixes: combined-$12k couple ineligible, $10k boundary eligible, and an aged non-SSI couple with $3k/$2k earnings correctly under the $5,964 couple cap. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../mn_msa_gross_income_eligible.yaml | 41 +++++++++++ .../eligibility/mn_msa_resource_eligible.yaml | 71 +++++++++++++++++++ .../mn_msa_gross_income_eligible.py | 18 ++--- .../eligibility/mn_msa_resource_eligible.py | 7 +- 4 files changed, 124 insertions(+), 13 deletions(-) diff --git a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/eligibility/mn_msa_gross_income_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/eligibility/mn_msa_gross_income_eligible.yaml index 6304b43331a..db93bcfe88f 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/eligibility/mn_msa_gross_income_eligible.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/eligibility/mn_msa_gross_income_eligible.yaml @@ -219,3 +219,44 @@ output: # Couple gross $5,966 > $5,964 cap -> not eligible. mn_msa_gross_income_eligible: [false, false] + +- name: Case 8, non-SSI aged couple uses the 600% couple cap regardless of joint claim. + period: 2026-01 + input: + people: + person1: + age: 70 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 4_000 + ssi: 0 + ssi_claim_is_joint: false # not on SSI -> no joint claim flag + employment_income: 36_000 # $3,000/mo, above $2,982 individual cap + person2: + age: 68 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 4_000 + ssi: 0 + ssi_claim_is_joint: false + employment_income: 24_000 # $2,000/mo + 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: MN + output: + # Both spouses are aged -> couple gross cap $5,964 applies. + # Combined gross $5,000 < $5,964 -> both eligible (would have failed + # under per-person individual $2,982 cap on the higher-earning spouse). + mn_msa_gross_income_eligible: [true, true] diff --git a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/eligibility/mn_msa_resource_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/eligibility/mn_msa_resource_eligible.yaml index 03f723c0538..45b015c9625 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/eligibility/mn_msa_resource_eligible.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/eligibility/mn_msa_resource_eligible.yaml @@ -229,3 +229,74 @@ output: # ssi=0 -> non-SSI track applies; $9,500 <= $10,000 -> eligible. mn_msa_resource_eligible: true + +- name: Case 9, non-SSI couple with combined resources above $10,000 cap is ineligible. + period: 2026-01 + input: + people: + person1: + age: 70 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 6_000 # individually under $10k + ssi: 0 + person2: + age: 68 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 6_000 # individually under $10k + ssi: 0 + 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: MN + output: + # § 256D.37 Subd. 1: $10,000 cap applies to the assistance unit. + # Combined $12,000 > $10,000 -> both spouses ineligible. + mn_msa_resource_eligible: [false, false] + +- name: Case 10, non-SSI couple with combined resources at the $10,000 boundary is eligible. + period: 2026-01 + input: + people: + person1: + age: 70 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 5_000 + ssi: 0 + person2: + age: 68 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 5_000 + ssi: 0 + 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: MN + output: + # Combined $10,000 == cap -> eligible. + mn_msa_resource_eligible: [true, true] diff --git a/policyengine_us/variables/gov/states/mn/dhs/msa/eligibility/mn_msa_gross_income_eligible.py b/policyengine_us/variables/gov/states/mn/dhs/msa/eligibility/mn_msa_gross_income_eligible.py index d4a78782bda..caec42874d1 100644 --- a/policyengine_us/variables/gov/states/mn/dhs/msa/eligibility/mn_msa_gross_income_eligible.py +++ b/policyengine_us/variables/gov/states/mn/dhs/msa/eligibility/mn_msa_gross_income_eligible.py @@ -15,20 +15,16 @@ class mn_msa_gross_income_eligible(Variable): def formula(person, period, parameters): # Per Minn. Stat. § 256D.44 Subd. 1, gross income may not exceed # 300% of the federal SSI individual benefit rate for an - # individual or 600% for a couple. + # individual or 600% for a couple. The couple cap applies to + # any married pair where both spouses are MSA-categorically + # eligible — not just SSI joint filers. p = parameters(period).gov.states.mn.dhs.msa.eligibility.income_limit ssi_fbr = parameters(period).gov.ssa.ssi.amount.individual - joint_claim = person("ssi_claim_is_joint", period.this_year) categorically_eligible = person("is_ssi_aged_blind_disabled", period.this_year) - both_eligible = ( - person.marital_unit.sum(categorically_eligible) == 2 - ) & joint_claim + both_eligible = person.marital_unit.sum(categorically_eligible) == 2 gross = person("mn_msa_gross_income", period) - couple_gross = person.marital_unit.sum(gross) - income = where(both_eligible, couple_gross, gross) - cap = where( - both_eligible, - ssi_fbr * p.couple_fbr_multiplier, - ssi_fbr * p.individual_fbr_multiplier, + income = where(both_eligible, person.marital_unit.sum(gross), gross) + cap = ssi_fbr * where( + both_eligible, p.couple_fbr_multiplier, p.individual_fbr_multiplier ) return income <= cap diff --git a/policyengine_us/variables/gov/states/mn/dhs/msa/eligibility/mn_msa_resource_eligible.py b/policyengine_us/variables/gov/states/mn/dhs/msa/eligibility/mn_msa_resource_eligible.py index 0ddf4210990..d6ce0d33263 100644 --- a/policyengine_us/variables/gov/states/mn/dhs/msa/eligibility/mn_msa_resource_eligible.py +++ b/policyengine_us/variables/gov/states/mn/dhs/msa/eligibility/mn_msa_resource_eligible.py @@ -14,8 +14,11 @@ class mn_msa_resource_eligible(Variable): def formula(person, period, parameters): # SSI recipients have already passed the federal $2,000/$3,000 - # resource test. Non-SSI excess-income track uses MN's $10,000 cap. + # resource test. Per § 256D.37 Subd. 1, the non-SSI $10,000 cap + # applies to the assistance unit (married couple combined), not + # per person. p = parameters(period).gov.states.mn.dhs.msa.eligibility receives_ssi = person("ssi", period) > 0 countable_resources = person("ssi_countable_resources", period.this_year) - return receives_ssi | (countable_resources <= p.asset_limit.non_ssi_track) + unit_resources = person.marital_unit.sum(countable_resources) + return receives_ssi | (unit_resources <= p.asset_limit.non_ssi_track) From b61b86aeee93e9e7429f01beafb861067783211f Mon Sep 17 00:00:00 2001 From: Ziming Date: Sun, 3 May 2026 23:19:57 -0400 Subject: [PATCH 13/17] Fix MN MSA citations and Medicaid facility values MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Address Critical findings from /review-program audit (PR #8215): - MEDICAID_FACILITY 2024/2026 values were double-counting the $30 federal SSI FBR on top of an already-combined PNA. Per CM 0020.21 and CM 0020.24, the assistance standard equals the PNA. Update 2024 to $125 and 2026 to $132 (combined federal+state target), matching the same convention used for community-living rows. - Fix wrong subdivision citations: guardian fee Subd. 5(b) → 5(d); representative payee fee Subd. 5(c) → 5(f). - Replace repealed Minn. Stat. § 256D.37 (repealed 1995) with the live § 256P.02 Subd. 2 + § 256D.425 Subd. 2(b) chain in both the asset-limit parameter and the resource-eligibility variable. - Update 300%/600% FBR gross-income gate citation from § 256D.44 Subd. 1 (which covers only COLA mechanics) to MN DHS Combined Manual 0019.06. - Document deferred § 256D.44 Subd. 2 HCBS-waiver and GRH/Housing Support upgrade pathways alongside the existing Subd. 5(g) deferral comment. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../states/mn/dhs/msa/assistance_standard/amount.yaml | 6 ++++-- .../dhs/msa/eligibility/asset_limit/non_ssi_track.yaml | 6 ++++-- .../eligibility/income_limit/couple_fbr_multiplier.yaml | 4 ++-- .../income_limit/individual_fbr_multiplier.yaml | 4 ++-- .../dhs/msa/special_needs/guardian_fee/max_amount.yaml | 2 +- .../mn/dhs/msa/special_needs/guardian_fee/rate.yaml | 2 +- .../special_needs/representative_payee_fee/amount.yaml | 2 +- .../mn/dhs/msa/eligibility/mn_msa_resource_eligible.py | 9 +++++---- .../mn/dhs/msa/mn_msa_housing_assistance_eligible.py | 9 +++++++++ 9 files changed, 29 insertions(+), 15 deletions(-) diff --git a/policyengine_us/parameters/gov/states/mn/dhs/msa/assistance_standard/amount.yaml b/policyengine_us/parameters/gov/states/mn/dhs/msa/assistance_standard/amount.yaml index af0bb737ca2..9d107ffe831 100644 --- a/policyengine_us/parameters/gov/states/mn/dhs/msa/assistance_standard/amount.yaml +++ b/policyengine_us/parameters/gov/states/mn/dhs/msa/assistance_standard/amount.yaml @@ -13,6 +13,8 @@ metadata: href: https://www.revisor.mn.gov/statutes/cite/256D.44 - title: MN DHS Combined Manual 0020.21 - MSA Assistance Standards (01/2026) href: https://www.dhs.state.mn.us/main/groups/county_access/documents/pub/mndhs-073585.pdf#page=4 + - title: MN DHS Combined Manual 0020.24 - Personal Needs Allowance (01/2026) + href: https://www.dhs.state.mn.us/main/groups/county_access/documents/pub/mndhs-073585.pdf#page=6 - title: MN House Research Department - Minnesota Supplemental Aid (October 2024), MSA Assistance Standards table href: https://www.house.mn.gov/hrd/pubs/pap_MSA.pdf#page=6 - title: SSA State Assistance Programs for SSI Recipients, January 2011 - Minnesota, Table 1 @@ -40,8 +42,8 @@ COUPLE_LIVING_WITH_OTHERS: MEDICAID_FACILITY: 2011-01-01: 89 - 2024-01-01: 155 - 2026-01-01: 162 + 2024-01-01: 125 + 2026-01-01: 132 NONE: 2011-01-01: 0 diff --git a/policyengine_us/parameters/gov/states/mn/dhs/msa/eligibility/asset_limit/non_ssi_track.yaml b/policyengine_us/parameters/gov/states/mn/dhs/msa/eligibility/asset_limit/non_ssi_track.yaml index e9a13222700..855900d2e87 100644 --- a/policyengine_us/parameters/gov/states/mn/dhs/msa/eligibility/asset_limit/non_ssi_track.yaml +++ b/policyengine_us/parameters/gov/states/mn/dhs/msa/eligibility/asset_limit/non_ssi_track.yaml @@ -8,7 +8,9 @@ metadata: period: year label: Minnesota Supplemental Aid non-SSI track asset limit reference: - - title: Minn. Stat. § 256D.37 Subd. 1 - href: https://www.revisor.mn.gov/statutes/cite/256D.37 + - title: Minn. Stat. § 256P.02 Subd. 2 (Resource limit) + href: https://www.revisor.mn.gov/statutes/cite/256P.02 + - title: Minn. Stat. § 256D.425 Subd. 2(b) (Resource standards via § 256P.02) + href: https://www.revisor.mn.gov/statutes/cite/256D.425 - title: MN House Research Department - Minnesota Supplemental Aid (October 2024), Resource and asset limits href: https://www.house.mn.gov/hrd/pubs/pap_MSA.pdf#page=4 diff --git a/policyengine_us/parameters/gov/states/mn/dhs/msa/eligibility/income_limit/couple_fbr_multiplier.yaml b/policyengine_us/parameters/gov/states/mn/dhs/msa/eligibility/income_limit/couple_fbr_multiplier.yaml index 857e34df917..70a52176b44 100644 --- a/policyengine_us/parameters/gov/states/mn/dhs/msa/eligibility/income_limit/couple_fbr_multiplier.yaml +++ b/policyengine_us/parameters/gov/states/mn/dhs/msa/eligibility/income_limit/couple_fbr_multiplier.yaml @@ -8,7 +8,7 @@ metadata: period: year label: Minnesota Supplemental Aid couple gross income FBR multiplier reference: - - title: Minn. Stat. § 256D.44 Subd. 1 - href: https://www.revisor.mn.gov/statutes/cite/256D.44 + - title: MN DHS Combined Manual 0019.06 - Gross Income Limits (MSA) + href: https://www.dhs.state.mn.us/main/idcplg?IdcService=GET_DYNAMIC_CONVERSION&RevisionSelectionMethod=LatestReleased&dDocName=cm_001906 - title: MN House Research Department - Minnesota Supplemental Aid (October 2024), Income standards and treatment of income href: https://www.house.mn.gov/hrd/pubs/pap_MSA.pdf#page=3 diff --git a/policyengine_us/parameters/gov/states/mn/dhs/msa/eligibility/income_limit/individual_fbr_multiplier.yaml b/policyengine_us/parameters/gov/states/mn/dhs/msa/eligibility/income_limit/individual_fbr_multiplier.yaml index da1929e20b9..6cf19f21b19 100644 --- a/policyengine_us/parameters/gov/states/mn/dhs/msa/eligibility/income_limit/individual_fbr_multiplier.yaml +++ b/policyengine_us/parameters/gov/states/mn/dhs/msa/eligibility/income_limit/individual_fbr_multiplier.yaml @@ -8,7 +8,7 @@ metadata: period: year label: Minnesota Supplemental Aid individual gross income FBR multiplier reference: - - title: Minn. Stat. § 256D.44 Subd. 1 - href: https://www.revisor.mn.gov/statutes/cite/256D.44 + - title: MN DHS Combined Manual 0019.06 - Gross Income Limits (MSA) + href: https://www.dhs.state.mn.us/main/idcplg?IdcService=GET_DYNAMIC_CONVERSION&RevisionSelectionMethod=LatestReleased&dDocName=cm_001906 - title: MN House Research Department - Minnesota Supplemental Aid (October 2024), Income standards and treatment of income href: https://www.house.mn.gov/hrd/pubs/pap_MSA.pdf#page=3 diff --git a/policyengine_us/parameters/gov/states/mn/dhs/msa/special_needs/guardian_fee/max_amount.yaml b/policyengine_us/parameters/gov/states/mn/dhs/msa/special_needs/guardian_fee/max_amount.yaml index d09c9e1d7c1..1b92baeefc0 100644 --- a/policyengine_us/parameters/gov/states/mn/dhs/msa/special_needs/guardian_fee/max_amount.yaml +++ b/policyengine_us/parameters/gov/states/mn/dhs/msa/special_needs/guardian_fee/max_amount.yaml @@ -8,7 +8,7 @@ metadata: period: month label: Minnesota Supplemental Aid guardian fee maximum amount reference: - - title: Minn. Stat. § 256D.44 Subd. 5(b) + - title: Minn. Stat. § 256D.44 Subd. 5(d) (Guardian or conservator fee) href: https://www.revisor.mn.gov/statutes/cite/256D.44 - title: SSA State Assistance Programs for SSI Recipients, January 2011 - Minnesota, Section 9.5 href: https://www.ssa.gov/policy/docs/progdesc/ssi_st_asst/2011/mn.html diff --git a/policyengine_us/parameters/gov/states/mn/dhs/msa/special_needs/guardian_fee/rate.yaml b/policyengine_us/parameters/gov/states/mn/dhs/msa/special_needs/guardian_fee/rate.yaml index 2a21f81a47f..e00dfd300d0 100644 --- a/policyengine_us/parameters/gov/states/mn/dhs/msa/special_needs/guardian_fee/rate.yaml +++ b/policyengine_us/parameters/gov/states/mn/dhs/msa/special_needs/guardian_fee/rate.yaml @@ -8,7 +8,7 @@ metadata: period: year label: Minnesota Supplemental Aid guardian fee rate reference: - - title: Minn. Stat. § 256D.44 Subd. 5(b) + - title: Minn. Stat. § 256D.44 Subd. 5(d) (Guardian or conservator fee) href: https://www.revisor.mn.gov/statutes/cite/256D.44 - title: SSA State Assistance Programs for SSI Recipients, January 2011 - Minnesota, Section 9.5 href: https://www.ssa.gov/policy/docs/progdesc/ssi_st_asst/2011/mn.html diff --git a/policyengine_us/parameters/gov/states/mn/dhs/msa/special_needs/representative_payee_fee/amount.yaml b/policyengine_us/parameters/gov/states/mn/dhs/msa/special_needs/representative_payee_fee/amount.yaml index f4d99c63c2c..b512cf109d8 100644 --- a/policyengine_us/parameters/gov/states/mn/dhs/msa/special_needs/representative_payee_fee/amount.yaml +++ b/policyengine_us/parameters/gov/states/mn/dhs/msa/special_needs/representative_payee_fee/amount.yaml @@ -9,7 +9,7 @@ metadata: period: month label: Minnesota Supplemental Aid representative payee fee amount reference: - - title: Minn. Stat. § 256D.44 Subd. 5(c) + - title: Minn. Stat. § 256D.44 Subd. 5(f) (Representative payee fee) href: https://www.revisor.mn.gov/statutes/cite/256D.44 - title: MN DHS Combined Manual 0023.21 - Representative Payee Services (01/2026) href: https://www.dhs.state.mn.us/main/groups/county_access/documents/pub/mndhs-073585.pdf#page=7 diff --git a/policyengine_us/variables/gov/states/mn/dhs/msa/eligibility/mn_msa_resource_eligible.py b/policyengine_us/variables/gov/states/mn/dhs/msa/eligibility/mn_msa_resource_eligible.py index d6ce0d33263..8da35b9a9cb 100644 --- a/policyengine_us/variables/gov/states/mn/dhs/msa/eligibility/mn_msa_resource_eligible.py +++ b/policyengine_us/variables/gov/states/mn/dhs/msa/eligibility/mn_msa_resource_eligible.py @@ -8,15 +8,16 @@ class mn_msa_resource_eligible(Variable): definition_period = MONTH defined_for = StateCode.MN reference = ( - "https://www.revisor.mn.gov/statutes/cite/256D.37", + "https://www.revisor.mn.gov/statutes/cite/256P.02", + "https://www.revisor.mn.gov/statutes/cite/256D.425", "https://www.house.mn.gov/hrd/pubs/pap_MSA.pdf#page=2", ) def formula(person, period, parameters): # SSI recipients have already passed the federal $2,000/$3,000 - # resource test. Per § 256D.37 Subd. 1, the non-SSI $10,000 cap - # applies to the assistance unit (married couple combined), not - # per person. + # resource test. Per § 256P.02 Subd. 2 (applied to MSA via + # § 256D.425 Subd. 2(b)), the non-SSI $10,000 cap applies to the + # assistance unit (married couple combined), not per person. p = parameters(period).gov.states.mn.dhs.msa.eligibility receives_ssi = person("ssi", period) > 0 countable_resources = person("ssi_countable_resources", period.this_year) diff --git a/policyengine_us/variables/gov/states/mn/dhs/msa/mn_msa_housing_assistance_eligible.py b/policyengine_us/variables/gov/states/mn/dhs/msa/mn_msa_housing_assistance_eligible.py index f135914f585..bd8db027a41 100644 --- a/policyengine_us/variables/gov/states/mn/dhs/msa/mn_msa_housing_assistance_eligible.py +++ b/policyengine_us/variables/gov/states/mn/dhs/msa/mn_msa_housing_assistance_eligible.py @@ -16,3 +16,12 @@ class mn_msa_housing_assistance_eligible(Variable): # chronic homelessness / housing stabilization), shelter cost > 40% # of unit gross income, and no concurrent housing subsidy. For now # this is a bare bool input. + # + # The same follow-up should also model the other two § 256D.44 Subd. 2 + # "living alone" upgrade pathways: HCBS waiver enrollment (CADI / EW / + # BI / CAC / DD per CM 0020.21) and GRH/Housing Support placement + # under § 256I.04 subd. 1a. We don't track HCBS-waiver enrollment or + # Housing Support residency at the moment, so recipients in those + # programs who are classified as "living with others" currently + # receive the lower standard rather than the upgraded "living alone" + # standard the statute requires. From eaeb7c76df2a0ccb1a4137bda24877853e08afd4 Mon Sep 17 00:00:00 2001 From: Ziming Date: Mon, 4 May 2026 00:00:31 -0400 Subject: [PATCH 14/17] Collapse MN MSA income variables onto federal SSI helpers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per CM 0018.18 ("for SSI recipients, no county action is required") and House Research Oct 2024, MN MSA inherits federal SSI's income disregards ($20 + $65 + 1/2). The PR's MSA-specific countable-income, earned-income- disregard, and gross-income variables duplicated logic already provided by federal SSI variables. Deletes (5 variables, 3 parameters, 5 test files): - mn_msa_countable_{earned,unearned,}_income - mn_msa_earned_income_disregard - mn_msa_gross_income - disregard/{general,earned/initial,earned/rate}.yaml Rewrites consumers using federal _apply_ssi_exclusions helper for the non-SSI track and raw federal SSI income variables for the SSI track: - mn_msa_person: non-SSI track via _apply_ssi_exclusions; SSI track unchanged (FBR + raw_unearned - $20 with FLA-D carve-out) - mn_msa_net_income_eligible: SSI track now correctly applies the House Research FBR-substitution rule that the deleted variable was missing - mn_msa_gross_income_eligible: removes incorrect FBR substitution per CM 0019.06 (gross test uses raw earned + unearned only) - mn_msa_guardian_fee: per Subd. 5(d) and CM 0023.15, computes 5% of the assistance unit's gross monthly income (raw earned + unearned, excluding SSI per CM 0017) at the marital-unit level Test updates: - mn_msa_assistance_standard.yaml Case 5 medfac: 162 -> 132 - mn_msa_person.yaml Case 3 medfac: 132 -> 102 (state supplement, not combined PNA) - integration.yaml Case 2: SSI couple CLWO now correctly fails the net-income test (couple FBR exceeds the standard) — payment $0 unchanged - integration.yaml Case 11: rewritten as realistic non-SSI track recipient with $2k earnings; exercises guardian fee at cap 86/86 tests pass. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../mn/dhs/msa/disregard/earned/initial.yaml | 14 -- .../mn/dhs/msa/disregard/earned/rate.yaml | 14 -- .../states/mn/dhs/msa/disregard/general.yaml | 14 -- .../mn_msa_countable_earned_income.yaml | 180 ------------------ .../msa/income/mn_msa_countable_income.yaml | 119 ------------ .../mn_msa_countable_unearned_income.yaml | 153 --------------- .../mn_msa_earned_income_disregard.yaml | 119 ------------ .../dhs/msa/income/mn_msa_gross_income.yaml | 154 --------------- .../gov/states/mn/dhs/msa/integration.yaml | 46 ++--- .../gov/states/mn/dhs/msa/mn_msa_person.yaml | 4 +- .../payment/mn_msa_assistance_standard.yaml | 2 +- .../mn_msa_gross_income_eligible.py | 24 ++- .../eligibility/mn_msa_net_income_eligible.py | 89 +++++++-- .../income/mn_msa_countable_earned_income.py | 50 ----- .../dhs/msa/income/mn_msa_countable_income.py | 19 -- .../mn_msa_countable_unearned_income.py | 37 ---- .../income/mn_msa_earned_income_disregard.py | 58 ------ .../mn/dhs/msa/income/mn_msa_gross_income.py | 34 ---- .../gov/states/mn/dhs/msa/mn_msa_person.py | 55 ++++-- .../mn/dhs/msa/payment/mn_msa_guardian_fee.py | 22 ++- 20 files changed, 183 insertions(+), 1024 deletions(-) delete mode 100644 policyengine_us/parameters/gov/states/mn/dhs/msa/disregard/earned/initial.yaml delete mode 100644 policyengine_us/parameters/gov/states/mn/dhs/msa/disregard/earned/rate.yaml delete mode 100644 policyengine_us/parameters/gov/states/mn/dhs/msa/disregard/general.yaml delete mode 100644 policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/income/mn_msa_countable_earned_income.yaml delete mode 100644 policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/income/mn_msa_countable_income.yaml delete mode 100644 policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/income/mn_msa_countable_unearned_income.yaml delete mode 100644 policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/income/mn_msa_earned_income_disregard.yaml delete mode 100644 policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/income/mn_msa_gross_income.yaml delete mode 100644 policyengine_us/variables/gov/states/mn/dhs/msa/income/mn_msa_countable_earned_income.py delete mode 100644 policyengine_us/variables/gov/states/mn/dhs/msa/income/mn_msa_countable_income.py delete mode 100644 policyengine_us/variables/gov/states/mn/dhs/msa/income/mn_msa_countable_unearned_income.py delete mode 100644 policyengine_us/variables/gov/states/mn/dhs/msa/income/mn_msa_earned_income_disregard.py delete mode 100644 policyengine_us/variables/gov/states/mn/dhs/msa/income/mn_msa_gross_income.py diff --git a/policyengine_us/parameters/gov/states/mn/dhs/msa/disregard/earned/initial.yaml b/policyengine_us/parameters/gov/states/mn/dhs/msa/disregard/earned/initial.yaml deleted file mode 100644 index 29e396bfa06..00000000000 --- a/policyengine_us/parameters/gov/states/mn/dhs/msa/disregard/earned/initial.yaml +++ /dev/null @@ -1,14 +0,0 @@ -description: Minnesota excludes this amount of earned income under the Minnesota Supplemental Aid program. - -values: - 1989-01-01: 65 - -metadata: - unit: currency-USD - period: month - label: Minnesota Supplemental Aid earned income initial disregard - reference: - - title: MN DHS Combined Manual 0018.18 - Earned Income Disregards (01/2026) - href: https://www.dhs.state.mn.us/main/groups/county_access/documents/pub/mndhs-073585.pdf#page=2 - - title: MN House Research Department - Minnesota Supplemental Aid (October 2024) - href: https://www.house.mn.gov/hrd/pubs/pap_MSA.pdf#page=3 diff --git a/policyengine_us/parameters/gov/states/mn/dhs/msa/disregard/earned/rate.yaml b/policyengine_us/parameters/gov/states/mn/dhs/msa/disregard/earned/rate.yaml deleted file mode 100644 index bea8d27ccd3..00000000000 --- a/policyengine_us/parameters/gov/states/mn/dhs/msa/disregard/earned/rate.yaml +++ /dev/null @@ -1,14 +0,0 @@ -description: Minnesota excludes this share of remaining earned income under the Minnesota Supplemental Aid program. - -values: - 1989-01-01: 0.5 - -metadata: - unit: /1 - period: year - label: Minnesota Supplemental Aid earned income disregard rate - reference: - - title: MN DHS Combined Manual 0018.18 - Earned Income Disregards (01/2026) - href: https://www.dhs.state.mn.us/main/groups/county_access/documents/pub/mndhs-073585.pdf#page=2 - - title: MN House Research Department - Minnesota Supplemental Aid (October 2024) - href: https://www.house.mn.gov/hrd/pubs/pap_MSA.pdf#page=4 diff --git a/policyengine_us/parameters/gov/states/mn/dhs/msa/disregard/general.yaml b/policyengine_us/parameters/gov/states/mn/dhs/msa/disregard/general.yaml deleted file mode 100644 index e0a87889491..00000000000 --- a/policyengine_us/parameters/gov/states/mn/dhs/msa/disregard/general.yaml +++ /dev/null @@ -1,14 +0,0 @@ -description: Minnesota excludes this amount of general income under the Minnesota Supplemental Aid program. - -values: - 1974-04-01: 20 - -metadata: - unit: currency-USD - period: month - label: Minnesota Supplemental Aid general income disregard - reference: - - title: 20 CFR § 416.1124(c)(12) - href: https://www.ecfr.gov/current/title-20/chapter-III/part-416/subpart-K/section-416.1124#p-416.1124(c)(12) - - title: MN House Research Department - Minnesota Supplemental Aid (October 2024), Income standards and treatment of income - href: https://www.house.mn.gov/hrd/pubs/pap_MSA.pdf#page=3 diff --git a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/income/mn_msa_countable_earned_income.yaml b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/income/mn_msa_countable_earned_income.yaml deleted file mode 100644 index 0aaf7b6af87..00000000000 --- a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/income/mn_msa_countable_earned_income.yaml +++ /dev/null @@ -1,180 +0,0 @@ -- name: Case 1, SSI-track recipient with earnings has zero MSA countable earned (federal SSI already disregards). - period: 2026-01 - input: - people: - person1: - age: 70 - is_blind: false - is_disabled: false - immigration_status: CITIZEN - ssi_countable_resources: 500 - ssi_earned_income: 3_000 # $250/mo - marital_units: - marital_unit: - members: [person1] - tax_units: - tax_unit: - members: [person1] - spm_units: - spm_unit: - members: [person1] - households: - household: - members: [person1] - state_code: MN - output: - # SSI > 0; federal $20 + $65 + 1/2 disregards already in ssi_countable_income. - mn_msa_countable_earned_income: 0 - -- name: Case 2, non-SSI track earnings above $20 + $65 disregard apply remaining and half. - period: 2026-01 - absolute_error_margin: 0.01 - input: - people: - person1: - age: 70 - is_blind: false - is_disabled: false - immigration_status: CITIZEN - ssi_countable_resources: 5_000 # above $2k SSI cap, not on SSI track - ssi: 0 - ssi_earned_income: 3_000 # $250/mo - ssi_unearned_income: 0 - marital_units: - marital_unit: - members: [person1] - tax_units: - tax_unit: - members: [person1] - spm_units: - spm_unit: - members: [person1] - households: - household: - members: [person1] - state_code: MN - output: - # Non-SSI track, no unearned: leftover $20 + $65 = $85 flat; ($250 - $85)/2 = $82.50. - mn_msa_countable_earned_income: 82.50 - -- name: Case 3, non-SSI track earnings exactly at $20 + $65 boundary yield zero countable. - period: 2026-01 - absolute_error_margin: 0.01 - input: - people: - person1: - age: 70 - is_blind: false - is_disabled: false - immigration_status: CITIZEN - ssi_countable_resources: 5_000 - ssi: 0 - ssi_earned_income: 1_020 # $85/mo - ssi_unearned_income: 0 - marital_units: - marital_unit: - members: [person1] - tax_units: - tax_unit: - members: [person1] - spm_units: - spm_unit: - members: [person1] - households: - household: - members: [person1] - state_code: MN - output: - # Non-SSI track: $85 - $85 = $0; half of $0 = $0. - mn_msa_countable_earned_income: 0 - -- name: Case 4, zero earnings yield zero countable. - period: 2026-01 - absolute_error_margin: 0.01 - input: - people: - person1: - age: 70 - is_blind: false - is_disabled: false - immigration_status: CITIZEN - ssi_countable_resources: 500 - ssi_earned_income: 0 - marital_units: - marital_unit: - members: [person1] - tax_units: - tax_unit: - members: [person1] - spm_units: - spm_unit: - members: [person1] - households: - household: - members: [person1] - state_code: MN - output: - # No earnings -> no countable earned income. - mn_msa_countable_earned_income: 0 - -- name: Case 5, non-SSI track unearned income consumes the $20 general so only $65 disregards earned. - period: 2026-01 - absolute_error_margin: 0.01 - input: - people: - person1: - age: 70 - is_blind: false - is_disabled: false - immigration_status: CITIZEN - ssi_countable_resources: 5_000 - ssi: 0 - ssi_earned_income: 792 # $66/mo - ssi_unearned_income: 600 # $50/mo, exceeds $20 general - marital_units: - marital_unit: - members: [person1] - tax_units: - tax_unit: - members: [person1] - spm_units: - spm_unit: - members: [person1] - households: - household: - members: [person1] - state_code: MN - output: - # $20 fully consumed by unearned; only $65 applies to earned: ($66 - $65)/2 = $0.50. - mn_msa_countable_earned_income: 0.50 - -- name: Case 6, non-SSI track large earnings apply $20 + $65 plus half remainder. - period: 2026-01 - absolute_error_margin: 0.01 - input: - people: - person1: - age: 70 - is_blind: false - is_disabled: false - immigration_status: CITIZEN - ssi_countable_resources: 5_000 - ssi: 0 - ssi_earned_income: 12_000 # $1,000/mo - ssi_unearned_income: 0 - marital_units: - marital_unit: - members: [person1] - tax_units: - tax_unit: - members: [person1] - spm_units: - spm_unit: - members: [person1] - households: - household: - members: [person1] - state_code: MN - output: - # Non-SSI track, no unearned: ($1,000 - $85)/2 = $457.50. - mn_msa_countable_earned_income: 457.50 diff --git a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/income/mn_msa_countable_income.yaml b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/income/mn_msa_countable_income.yaml deleted file mode 100644 index a894cb3c1d1..00000000000 --- a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/income/mn_msa_countable_income.yaml +++ /dev/null @@ -1,119 +0,0 @@ -- name: Case 1, non-SSI track unearned income applies the $20 general disregard. - period: 2026-01 - input: - people: - person1: - age: 70 - is_blind: false - is_disabled: false - immigration_status: CITIZEN - ssi_countable_resources: 5_000 - ssi: 0 - ssi_unearned_income: 7_200 # $600/mo - ssi_earned_income: 0 - marital_units: - marital_unit: - members: [person1] - tax_units: - tax_unit: - members: [person1] - spm_units: - spm_unit: - members: [person1] - households: - household: - members: [person1] - state_code: MN - output: - # $600 - $20 general disregard = $580 countable. - mn_msa_countable_income: 580 - -- name: Case 2, non-SSI track earned plus unearned income with the $20 + $65 + 1/2 federal SSI methodology. - period: 2026-01 - input: - people: - person1: - age: 70 - is_blind: false - is_disabled: false - immigration_status: CITIZEN - ssi_countable_resources: 5_000 - ssi: 0 - ssi_unearned_income: 2_400 # $200/mo - ssi_earned_income: 3_000 # $250/mo - marital_units: - marital_unit: - members: [person1] - tax_units: - tax_unit: - members: [person1] - spm_units: - spm_unit: - members: [person1] - households: - household: - members: [person1] - state_code: MN - output: - # Unearned $200 - $20 = $180; earned ($250 - $65)/2 = $92.50; total $272.50. - mn_msa_countable_income: 272.50 - -- name: Case 3, non-SSI track unearned with negative earned does not subtract from unearned. - period: 2026-01 - input: - people: - person1: - age: 70 - is_blind: false - is_disabled: false - immigration_status: CITIZEN - ssi_countable_resources: 5_000 - ssi: 0 - ssi_unearned_income: 6_000 # $500/mo - ssi_earned_income: 600 # $50/mo (below the rolled-over $85 disregard) - marital_units: - marital_unit: - members: [person1] - tax_units: - tax_unit: - members: [person1] - spm_units: - spm_unit: - members: [person1] - households: - household: - members: [person1] - state_code: MN - output: - # Unearned $500 - $20 = $480; earned $50 floored to $0 after $65 disregard. - mn_msa_countable_income: 480 - -- name: Case 4, extreme negative self-employment does not produce negative countable. - period: 2026-01 - absolute_error_margin: 0.01 - input: - people: - person1: - age: 70 - is_blind: false - is_disabled: false - immigration_status: CITIZEN - ssi_countable_resources: 500 - self_employment_income: -12_000_000 # extreme negative - ssi_unearned_income: 0 - marital_units: - marital_unit: - members: [person1] - tax_units: - tax_unit: - members: [person1] - spm_units: - spm_unit: - members: [person1] - households: - household: - members: [person1] - state_code: MN - output: - # Earned floored at 0; unearned 0; total countable = 0. - mn_msa_countable_income: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/income/mn_msa_countable_unearned_income.yaml b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/income/mn_msa_countable_unearned_income.yaml deleted file mode 100644 index e538fccb7c4..00000000000 --- a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/income/mn_msa_countable_unearned_income.yaml +++ /dev/null @@ -1,153 +0,0 @@ -- name: Case 1, unearned income above $20 has the general disregard applied. - period: 2026-01 - input: - people: - person1: - age: 70 - is_blind: false - is_disabled: false - immigration_status: CITIZEN - ssi_countable_resources: 5_000 - ssi: 0 - ssi_unearned_income: 6_000 # $500/mo - marital_units: - marital_unit: - members: [person1] - tax_units: - tax_unit: - members: [person1] - spm_units: - spm_unit: - members: [person1] - households: - household: - members: [person1] - state_code: MN - output: - # $500 - $20 general disregard = $480 countable unearned. - mn_msa_countable_unearned_income: 480 - -- name: Case 2, zero unearned income yields zero countable unearned. - period: 2026-01 - input: - people: - person1: - age: 70 - is_blind: false - is_disabled: false - immigration_status: CITIZEN - ssi_countable_resources: 500 - ssi_unearned_income: 0 - marital_units: - marital_unit: - members: [person1] - tax_units: - tax_unit: - members: [person1] - spm_units: - spm_unit: - members: [person1] - households: - household: - members: [person1] - state_code: MN - output: - mn_msa_countable_unearned_income: 0 - -- name: Case 3, unearned income below $20 floors at zero countable unearned. - period: 2026-01 - input: - people: - person1: - age: 70 - is_blind: false - is_disabled: false - immigration_status: CITIZEN - ssi_countable_resources: 5_000 - ssi: 0 - ssi_unearned_income: 120 # $10/mo, below the $20 general disregard - marital_units: - marital_unit: - members: [person1] - tax_units: - tax_unit: - members: [person1] - spm_units: - spm_unit: - members: [person1] - households: - household: - members: [person1] - state_code: MN - output: - # $10 - $20 floored at $0 (the leftover rolls into earned income). - mn_msa_countable_unearned_income: 0 - -- name: Case 5, P2.2 couple living alone splits the $20 general disregard 50/50 across spouses. - period: 2026-01 - input: - people: - person1: - age: 70 - is_blind: false - is_disabled: false - immigration_status: CITIZEN - ssi_countable_resources: 1_000 - mn_msa_living_arrangement: COUPLE_LIVING_ALONE - ssi: 0 - ssi_unearned_income: 9_660 # $805/mo each - person2: - age: 68 - is_blind: false - is_disabled: false - immigration_status: CITIZEN - ssi_countable_resources: 1_000 - mn_msa_living_arrangement: COUPLE_LIVING_ALONE - ssi: 0 - ssi_unearned_income: 9_660 # $805/mo each - 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: MN - output: - # Per spouse: $805 unearned - $20/2 = $795 countable. Sums to combined - # $1,590 (= $1,610 gross - $20 once-per-couple disregard). - mn_msa_countable_unearned_income: [795, 795] - -- name: Case 4, deemed-from-ineligible-spouse unearned income aggregates with own unearned. - period: 2026-01 - input: - people: - person1: - age: 70 - is_blind: false - is_disabled: false - immigration_status: CITIZEN - ssi_countable_resources: 500 - ssi_unearned_income: 1_200 # $100/mo - ssi_unearned_income_deemed_from_ineligible_spouse: 2_400 # $200/mo deemed - marital_units: - marital_unit: - members: [person1] - tax_units: - tax_unit: - members: [person1] - spm_units: - spm_unit: - members: [person1] - households: - household: - members: [person1] - state_code: MN - output: - # ($100 + $200) - $20 general = $280 countable unearned. - mn_msa_countable_unearned_income: 280 diff --git a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/income/mn_msa_earned_income_disregard.yaml b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/income/mn_msa_earned_income_disregard.yaml deleted file mode 100644 index 24691daf780..00000000000 --- a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/income/mn_msa_earned_income_disregard.yaml +++ /dev/null @@ -1,119 +0,0 @@ -- name: Case 1, earnings above $20 + $65 with no unearned apply $85 plus half remainder. - period: 2026-01 - absolute_error_margin: 0.01 - input: - people: - person1: - age: 70 - is_blind: false - is_disabled: false - immigration_status: CITIZEN - ssi_countable_resources: 5_000 - ssi: 0 - ssi_earned_income: 12_000 # $1,000/mo - ssi_unearned_income: 0 - marital_units: - marital_unit: - members: [person1] - tax_units: - tax_unit: - members: [person1] - spm_units: - spm_unit: - members: [person1] - households: - household: - members: [person1] - state_code: MN - output: - # No unearned -> $20 general rolls into earned: $85 flat + ($1,000 - $85) * 0.5 = $542.50. - mn_msa_earned_income_disregard: 542.50 - -- name: Case 2, earnings with unearned consuming the $20 general apply only $65 plus half remainder. - period: 2026-01 - absolute_error_margin: 0.01 - input: - people: - person1: - age: 70 - is_blind: false - is_disabled: false - immigration_status: CITIZEN - ssi_countable_resources: 5_000 - ssi: 0 - ssi_earned_income: 12_000 # $1,000/mo - ssi_unearned_income: 1_200 # $100/mo, exceeds $20 general - marital_units: - marital_unit: - members: [person1] - tax_units: - tax_unit: - members: [person1] - spm_units: - spm_unit: - members: [person1] - households: - household: - members: [person1] - state_code: MN - output: - # Unearned consumes $20 -> only $65 flat + ($1,000 - $65) * 0.5 = $532.50. - mn_msa_earned_income_disregard: 532.50 - -- name: Case 3, earnings below $65 disregard yield disregard equal to earnings. - period: 2026-01 - absolute_error_margin: 0.01 - input: - people: - person1: - age: 70 - is_blind: false - is_disabled: false - immigration_status: CITIZEN - ssi_countable_resources: 5_000 - ssi: 0 - ssi_earned_income: 600 # $50/mo - ssi_unearned_income: 600 # $50/mo, exceeds $20 general - marital_units: - marital_unit: - members: [person1] - tax_units: - tax_unit: - members: [person1] - spm_units: - spm_unit: - members: [person1] - households: - household: - members: [person1] - state_code: MN - output: - # Earnings $50 < flat $65; disregard caps at gross earnings: $50. - mn_msa_earned_income_disregard: 50 - -- name: Case 4, zero earnings yields zero disregard. - period: 2026-01 - input: - people: - person1: - age: 70 - is_blind: false - is_disabled: false - immigration_status: CITIZEN - ssi_countable_resources: 500 - ssi_earned_income: 0 - marital_units: - marital_unit: - members: [person1] - tax_units: - tax_unit: - members: [person1] - spm_units: - spm_unit: - members: [person1] - households: - household: - members: [person1] - state_code: MN - output: - mn_msa_earned_income_disregard: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/income/mn_msa_gross_income.yaml b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/income/mn_msa_gross_income.yaml deleted file mode 100644 index 03a7770d101..00000000000 --- a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/income/mn_msa_gross_income.yaml +++ /dev/null @@ -1,154 +0,0 @@ -- name: Case 1, SSI recipient gross income includes the federal SSI payment per House Research. - period: 2026-01 - absolute_error_margin: 0.01 - input: - people: - person1: - age: 70 - is_blind: false - is_disabled: false - immigration_status: CITIZEN - ssi_countable_resources: 500 - ssi: 11_928 # $994/mo - ssi_earned_income: 0 - ssi_unearned_income: 0 - marital_units: - marital_unit: - members: [person1] - tax_units: - tax_unit: - members: [person1] - spm_units: - spm_unit: - members: [person1] - households: - household: - members: [person1] - state_code: MN - output: - # Federal SSI $994 counts as gross unearned for the MSA gross test. - mn_msa_gross_income: 994 - -- name: Case 2, non-SSI track recipient gross income sums earned plus unearned only. - period: 2026-01 - absolute_error_margin: 0.01 - input: - people: - person1: - age: 70 - is_blind: false - is_disabled: false - immigration_status: CITIZEN - ssi_countable_resources: 5_000 - ssi: 0 - ssi_earned_income: 6_000 # $500/mo - ssi_unearned_income: 3_600 # $300/mo - marital_units: - marital_unit: - members: [person1] - tax_units: - tax_unit: - members: [person1] - spm_units: - spm_unit: - members: [person1] - households: - household: - members: [person1] - state_code: MN - output: - # Earned $500 + unearned $300 + SSI $0 = $800. - mn_msa_gross_income: 800 - -- name: Case 3, deemed-from-ineligible-spouse income aggregates into gross. - period: 2026-01 - absolute_error_margin: 0.01 - input: - people: - person1: - age: 70 - is_blind: false - is_disabled: false - immigration_status: CITIZEN - ssi_countable_resources: 500 - ssi: 11_928 # $994/mo - ssi_earned_income: 1_200 # $100/mo - ssi_unearned_income: 600 # $50/mo - ssi_earned_income_deemed_from_ineligible_spouse: 2_400 # $200/mo deemed - ssi_unearned_income_deemed_from_ineligible_spouse: 1_200 # $100/mo deemed - marital_units: - marital_unit: - members: [person1] - tax_units: - tax_unit: - members: [person1] - spm_units: - spm_unit: - members: [person1] - households: - household: - members: [person1] - state_code: MN - output: - # Own earned $100 + own unearned $50 + SSI $994 + deemed earned $200 + deemed unearned $100 = $1,444. - mn_msa_gross_income: 1_444 - -- name: Case 4, zero income across all components yields zero gross. - period: 2026-01 - input: - people: - person1: - age: 70 - is_blind: false - is_disabled: false - immigration_status: CITIZEN - ssi_countable_resources: 500 - ssi: 0 - ssi_earned_income: 0 - ssi_unearned_income: 0 - marital_units: - marital_unit: - members: [person1] - tax_units: - tax_unit: - members: [person1] - spm_units: - spm_unit: - members: [person1] - households: - household: - members: [person1] - state_code: MN - output: - mn_msa_gross_income: 0 - -- name: Case 5, reduced SSI payment still counts the applicable federal benefit rate. - period: 2026-01 - absolute_error_margin: 0.01 - input: - people: - person1: - age: 70 - is_blind: false - is_disabled: false - immigration_status: CITIZEN - ssi_countable_resources: 500 - ssi: 6_000 # $500/mo actual payment after federal reductions - ssi_earned_income: 0 - ssi_unearned_income: 0 - marital_units: - marital_unit: - members: [person1] - tax_units: - tax_unit: - members: [person1] - spm_units: - spm_unit: - members: [person1] - households: - household: - members: [person1] - state_code: MN - output: - # MSA counts the applicable $994 SSI FBR, not the reduced $500 SSI payment. - mn_msa_gross_income: 994 diff --git a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/integration.yaml b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/integration.yaml index c4b8052fe4a..2174446b510 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/integration.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/integration.yaml @@ -29,7 +29,6 @@ output: mn_msa_living_arrangement: INDIVIDUAL_LIVING_ALONE mn_msa_assistance_standard: 1_055 - mn_msa_countable_income: 0 mn_msa_eligible_person: true # Need $1,055 - max($994 SSI - $20 general, 0) - countable $0 = $81. mn_msa_person: 81 @@ -78,10 +77,10 @@ output: mn_msa_living_arrangement: [COUPLE_LIVING_WITH_OTHERS, COUPLE_LIVING_WITH_OTHERS] mn_msa_assistance_standard: [1_058, 1_058] - mn_msa_eligible_person: [true, true] - # COUPLE_LIVING_WITH_OTHERS $1,058 is the couple total; per person = - # $529. Per spouse: max(0, $529 - $745.50 - $0) = $0 (couple SSI - # exceeds the couple supplement standard for this living arrangement). + # Couple SSI track countable = $1,491 FBR + $0 unearned - $20 = $1,471. + # Standard $1,058 < $1,471, so the net-income test fails -> not eligible + # and no state supplement (couple FBR exceeds the CLWO standard). + mn_msa_eligible_person: [false, false] mn_msa_person: [0, 0] mn_msa: 0 @@ -116,7 +115,6 @@ output: mn_msa_living_arrangement: INDIVIDUAL_LIVING_ALONE mn_msa_assistance_standard: 1_055 - mn_msa_countable_income: 980 mn_msa_eligible_person: true # Standard $1,055 - countable ($1,000 - $20 general) = $1,055 - $980 = $75. mn_msa_person: 75 @@ -156,12 +154,11 @@ # the federal medical-facility status is detected by mn_msa_payment_category, # which the assistance_standard formula reads. mn_msa_payment_category: MEDICAID_FACILITY - mn_msa_assistance_standard: 162 - mn_msa_countable_income: 0 + mn_msa_assistance_standard: 132 mn_msa_eligible_person: true - # Need $162 - federal SSI $30 - countable $0 = $132 (PNA only as state portion). - mn_msa_person: 132 - mn_msa: 132 + # Combined PNA standard $132 - federal SSI FBR $30 = $102 state supplement. + mn_msa_person: 102 + mn_msa: 102 - name: Case 5, recipient with MSA Housing Assistance add-on. period: 2026-01 @@ -231,7 +228,6 @@ # Countable income components are floored at 0; benefit cannot exceed need - SSI. # $1,055 - max($994 SSI - $20 general, 0) = $1,055 - $974 = $81. mn_msa_assistance_standard: 1_055 - mn_msa_countable_income: 0 mn_msa_person: 81 mn_msa: 81 @@ -384,7 +380,7 @@ mn_msa_person: 578 mn_msa: 578 -- name: Case 11, recipient with representative payee fee and guardian fee combined. +- name: Case 11, non-SSI track recipient with earned income, rep payee, and guardian fee. period: 2026-01 absolute_error_margin: 0.01 input: @@ -394,12 +390,14 @@ is_blind: false is_disabled: false immigration_status: CITIZEN - ssi_countable_resources: 500 + ssi_countable_resources: 500 # under $10k non-SSI track cap mn_msa_uses_representative_payee: true mn_msa_has_guardian: true - ssi: 11_928 # $994/mo federal SSI - ssi_earned_income: 0 - ssi_unearned_income: 12_000 # $1,000/mo gross unearned + # $2,000/mo earned exceeds the federal SSI cap, so the recipient + # qualifies for MSA via the non-SSI track. Force ssi: 0 explicitly + # to make the test deterministic. + ssi: 0 + employment_income: 24_000 marital_units: marital_unit: members: [person1] @@ -416,12 +414,14 @@ output: mn_msa_assistance_standard: 1_055 mn_msa_representative_payee_fee: 57 - # Gross = SSI $994 + unearned $1,000 = $1,994; 5% = $99.70 < $100 cap. - mn_msa_guardian_fee: 99.70 - # Base: max(0, $1,055 - $994 - $1,000 countable) = $0; - # plus $57 payee fee + $99.70 guardian fee = $156.70. - mn_msa_person: 156.70 - mn_msa: 156.70 + # Guardian fee: 5% of $2,000 gross monthly income = $100 (at cap). + mn_msa_guardian_fee: 100 + # Non-SSI track countable: $20 disregard hits unearned ($0), so all + # $20 + $65 = $85 plus 1/2 remainder applies to earned. Countable + # earned = ($2,000 - $85) * 0.5 = $957.50. Base supp = $1,055 - $957.50 + # = $97.50; plus $57 payee fee + $100 guardian fee = $254.50. + mn_msa_person: 254.50 + mn_msa: 254.50 - name: Case 12, person not lawfully present is ineligible regardless of resources or income. period: 2026-01 diff --git a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/mn_msa_person.yaml b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/mn_msa_person.yaml index 5a5d086bb1b..935189f95d9 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/mn_msa_person.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/mn_msa_person.yaml @@ -89,8 +89,8 @@ members: [person1] state_code: MN output: - # Standard $162 - federal SSI $30 - countable $0 = $132 (PNA only, state portion). - mn_msa_person: 132 + # Combined PNA standard $132 - federal SSI FBR $30 = $102 state supplement. + mn_msa_person: 102 - name: Case 4, SSI couple living alone splits the couple-rate 50/50 across spouses. period: 2026-01 diff --git a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/payment/mn_msa_assistance_standard.yaml b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/payment/mn_msa_assistance_standard.yaml index 1f6aa5a6606..bf7b5c10760 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/payment/mn_msa_assistance_standard.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/payment/mn_msa_assistance_standard.yaml @@ -105,7 +105,7 @@ members: [person1] state_code: MN output: - mn_msa_assistance_standard: 162 + mn_msa_assistance_standard: 132 - name: Case 6, NONE arrangement returns zero standard. period: 2026-01 diff --git a/policyengine_us/variables/gov/states/mn/dhs/msa/eligibility/mn_msa_gross_income_eligible.py b/policyengine_us/variables/gov/states/mn/dhs/msa/eligibility/mn_msa_gross_income_eligible.py index caec42874d1..92fee91e32a 100644 --- a/policyengine_us/variables/gov/states/mn/dhs/msa/eligibility/mn_msa_gross_income_eligible.py +++ b/policyengine_us/variables/gov/states/mn/dhs/msa/eligibility/mn_msa_gross_income_eligible.py @@ -8,21 +8,29 @@ class mn_msa_gross_income_eligible(Variable): definition_period = MONTH defined_for = StateCode.MN reference = ( - "https://www.revisor.mn.gov/statutes/cite/256D.44", - "https://www.house.mn.gov/hrd/pubs/pap_MSA.pdf#page=2", + "https://www.dhs.state.mn.us/main/idcplg?IdcService=GET_DYNAMIC_CONVERSION&RevisionSelectionMethod=LatestReleased&dDocName=cm_001906", + "https://www.house.mn.gov/hrd/pubs/pap_MSA.pdf#page=3", ) def formula(person, period, parameters): - # Per Minn. Stat. § 256D.44 Subd. 1, gross income may not exceed - # 300% of the federal SSI individual benefit rate for an - # individual or 600% for a couple. The couple cap applies to - # any married pair where both spouses are MSA-categorically - # eligible — not just SSI joint filers. + # Per CM 0019.06, gross income may not exceed 300% of the federal + # SSI individual benefit rate for an individual or 600% for a couple + # where both spouses are MSA-categorically eligible. p = parameters(period).gov.states.mn.dhs.msa.eligibility.income_limit ssi_fbr = parameters(period).gov.ssa.ssi.amount.individual categorically_eligible = person("is_ssi_aged_blind_disabled", period.this_year) both_eligible = person.marital_unit.sum(categorically_eligible) == 2 - gross = person("mn_msa_gross_income", period) + gross = add( + person, + period, + [ + "ssi_earned_income", + "ssi_unearned_income", + "ssi_earned_income_deemed_from_ineligible_spouse", + "ssi_unearned_income_deemed_from_ineligible_spouse", + "ssi_unearned_income_deemed_from_ineligible_parent", + ], + ) income = where(both_eligible, person.marital_unit.sum(gross), gross) cap = ssi_fbr * where( both_eligible, p.couple_fbr_multiplier, p.individual_fbr_multiplier diff --git a/policyengine_us/variables/gov/states/mn/dhs/msa/eligibility/mn_msa_net_income_eligible.py b/policyengine_us/variables/gov/states/mn/dhs/msa/eligibility/mn_msa_net_income_eligible.py index f2f8665d2b1..07f14065372 100644 --- a/policyengine_us/variables/gov/states/mn/dhs/msa/eligibility/mn_msa_net_income_eligible.py +++ b/policyengine_us/variables/gov/states/mn/dhs/msa/eligibility/mn_msa_net_income_eligible.py @@ -1,4 +1,7 @@ from policyengine_us.model_api import * +from policyengine_us.variables.gov.ssa.ssi.eligibility.income._apply_ssi_exclusions import ( + _apply_ssi_exclusions, +) class mn_msa_net_income_eligible(Variable): @@ -9,23 +12,87 @@ class mn_msa_net_income_eligible(Variable): defined_for = StateCode.MN reference = ( "https://www.revisor.mn.gov/statutes/cite/256D.44", - "https://www.house.mn.gov/hrd/pubs/pap_MSA.pdf#page=2", + "https://www.dhs.state.mn.us/main/groups/county_access/documents/pub/mndhs-073585.pdf#page=2", ) def formula(person, period, parameters): - # Per Minn. Stat. § 256D.44 Subd. 1, MSA pays only to recipients - # whose countable income is at or below the assistance standard - # for their living arrangement. + # Per House Research Oct 2024 p.3 and CM 0018.18: for SSI recipients, + # MSA's net-income calc counts the full federal SSI FBR as gross + # unearned income and applies only the $20 general disregard + # (federal SSI already consumed the $65 + 1/2 earned disregards). + # Non-SSI track recipients get the standard $20 + $65 + 1/2 + # disregards on actual earned and unearned income. + # FLA-D recipients are exempt from the $20 disregard so the formula + # collapses to FBR + unearned vs PNA. arrangement = person("mn_msa_payment_category", period) LA = arrangement.possible_values - is_couple_arrangement = (arrangement == LA.COUPLE_LIVING_ALONE) | ( + is_couple = (arrangement == LA.COUPLE_LIVING_ALONE) | ( arrangement == LA.COUPLE_LIVING_WITH_OTHERS ) - countable_income = person("mn_msa_countable_income", period) - income = where( - is_couple_arrangement, - person.marital_unit.sum(countable_income), - countable_income, + is_medicaid_facility = arrangement == LA.MEDICAID_FACILITY + ssi = person("ssi", period) + receives_ssi = ssi > 0 + ssi_fbr = person("ssi_amount_if_eligible", period) + p = parameters(period).gov.ssa.ssi.income.exclusions + disregard = where(is_medicaid_facility, 0, p.general) + raw_unearned = add( + person, + period, + [ + "ssi_unearned_income", + "ssi_unearned_income_deemed_from_ineligible_spouse", + "ssi_unearned_income_deemed_from_ineligible_parent", + ], ) + + # SSI track: count FBR as unearned, apply $20 once at AU level for + # couples (matches mn_msa_person.py SSI track formula). + couple_ssi_countable = max_( + person.marital_unit.sum(ssi_fbr) + + person.marital_unit.sum(raw_unearned) + - disregard, + 0, + ) + individual_ssi_countable = max_(ssi_fbr + raw_unearned - disregard, 0) + ssi_countable = where(is_couple, couple_ssi_countable, individual_ssi_countable) + + # Non-SSI track: federal $20 + $65 + 1/2 disregards on actual + # earned and unearned. _apply_ssi_exclusions takes annual amounts + # and returns annual countable; convert to monthly. + annual_earned = add( + person, + period.this_year, + [ + "ssi_earned_income", + "ssi_earned_income_deemed_from_ineligible_spouse", + ], + ) + annual_unearned = add( + person, + period.this_year, + [ + "ssi_unearned_income", + "ssi_unearned_income_deemed_from_ineligible_spouse", + "ssi_unearned_income_deemed_from_ineligible_parent", + ], + ) + couple_earned = person.marital_unit.sum(annual_earned) + couple_unearned = person.marital_unit.sum(annual_unearned) + couple_non_ssi_countable = ( + _apply_ssi_exclusions(couple_earned, couple_unearned, parameters, period) + / MONTHS_IN_YEAR + ) + individual_non_ssi_countable = ( + _apply_ssi_exclusions(annual_earned, annual_unearned, parameters, period) + / MONTHS_IN_YEAR + ) + non_ssi_countable = where( + is_couple, couple_non_ssi_countable, individual_non_ssi_countable + ) + + # For couples both ssi_countable and non_ssi_countable are already + # AU-aggregate (computed via marital_unit.sum); the couple standard + # is the AU total, so compare directly. + countable = where(receives_ssi, ssi_countable, non_ssi_countable) standard = person("mn_msa_assistance_standard", period) - return income <= standard + return countable <= standard diff --git a/policyengine_us/variables/gov/states/mn/dhs/msa/income/mn_msa_countable_earned_income.py b/policyengine_us/variables/gov/states/mn/dhs/msa/income/mn_msa_countable_earned_income.py deleted file mode 100644 index e52247b105a..00000000000 --- a/policyengine_us/variables/gov/states/mn/dhs/msa/income/mn_msa_countable_earned_income.py +++ /dev/null @@ -1,50 +0,0 @@ -from policyengine_us.model_api import * - - -class mn_msa_countable_earned_income(Variable): - value_type = float - entity = Person - label = "Minnesota Supplemental Aid countable earned income" - unit = USD - definition_period = MONTH - defined_for = StateCode.MN - reference = ( - "https://www.revisor.mn.gov/statutes/cite/256D.44", - "https://www.dhs.state.mn.us/main/groups/county_access/documents/pub/mndhs-073585.pdf#page=2", - ) - - def formula(person, period, parameters): - # SSI-track recipients already have $20 + $65 + 1/2 disregards - # consumed inside ssi_countable_income, so MSA earned is treated - # as fully disregarded to avoid double-counting. - gross_earned = add( - person, - period, - [ - "ssi_earned_income", - "ssi_earned_income_deemed_from_ineligible_spouse", - ], - ) - disregard = person("mn_msa_earned_income_disregard", period) - arrangement = person("mn_msa_payment_category", period) - LA = arrangement.possible_values - is_couple = (arrangement == LA.COUPLE_LIVING_ALONE) | ( - arrangement == LA.COUPLE_LIVING_WITH_OTHERS - ) - # Couples: aggregate gross earned and disregard at marital unit, - # then split countable 50/50 — prevents wasting the disregard - # when one spouse has zero earnings. - couple_countable = ( - max_( - person.marital_unit.sum(gross_earned) - - person.marital_unit.sum(disregard), - 0, - ) - / 2 - ) - individual_countable = max_(gross_earned - disregard, 0) - non_ssi_track_countable = where( - is_couple, couple_countable, individual_countable - ) - receives_ssi = person("ssi", period) > 0 - return where(receives_ssi, 0, non_ssi_track_countable) diff --git a/policyengine_us/variables/gov/states/mn/dhs/msa/income/mn_msa_countable_income.py b/policyengine_us/variables/gov/states/mn/dhs/msa/income/mn_msa_countable_income.py deleted file mode 100644 index 66c5def4f9c..00000000000 --- a/policyengine_us/variables/gov/states/mn/dhs/msa/income/mn_msa_countable_income.py +++ /dev/null @@ -1,19 +0,0 @@ -from policyengine_us.model_api import * - - -class mn_msa_countable_income(Variable): - value_type = float - entity = Person - label = "Minnesota Supplemental Aid countable income" - unit = USD - definition_period = MONTH - defined_for = StateCode.MN - reference = ( - "https://www.revisor.mn.gov/statutes/cite/256D.44", - "https://www.dhs.state.mn.us/main/groups/county_access/documents/pub/mndhs-073585.pdf#page=2", - ) - - adds = [ - "mn_msa_countable_earned_income", - "mn_msa_countable_unearned_income", - ] diff --git a/policyengine_us/variables/gov/states/mn/dhs/msa/income/mn_msa_countable_unearned_income.py b/policyengine_us/variables/gov/states/mn/dhs/msa/income/mn_msa_countable_unearned_income.py deleted file mode 100644 index 25e2a420a3f..00000000000 --- a/policyengine_us/variables/gov/states/mn/dhs/msa/income/mn_msa_countable_unearned_income.py +++ /dev/null @@ -1,37 +0,0 @@ -from policyengine_us.model_api import * - - -class mn_msa_countable_unearned_income(Variable): - value_type = float - entity = Person - label = "Minnesota Supplemental Aid countable unearned income" - unit = USD - definition_period = MONTH - defined_for = StateCode.MN - reference = ( - "https://www.revisor.mn.gov/statutes/cite/256D.44", - "https://www.house.mn.gov/hrd/pubs/pap_MSA.pdf#page=3", - ) - - def formula(person, period, parameters): - gross_unearned = add( - person, - period, - [ - "ssi_unearned_income", - "ssi_unearned_income_deemed_from_ineligible_spouse", - "ssi_unearned_income_deemed_from_ineligible_parent", - ], - ) - general = parameters(period).gov.states.mn.dhs.msa.disregard.general - arrangement = person("mn_msa_payment_category", period) - LA = arrangement.possible_values - is_couple = (arrangement == LA.COUPLE_LIVING_ALONE) | ( - arrangement == LA.COUPLE_LIVING_WITH_OTHERS - ) - # Couples: aggregate at marital unit, apply $20 once, split 50/50. - couple_countable = ( - max_(person.marital_unit.sum(gross_unearned) - general, 0) / 2 - ) - individual_countable = max_(gross_unearned - general, 0) - return where(is_couple, couple_countable, individual_countable) diff --git a/policyengine_us/variables/gov/states/mn/dhs/msa/income/mn_msa_earned_income_disregard.py b/policyengine_us/variables/gov/states/mn/dhs/msa/income/mn_msa_earned_income_disregard.py deleted file mode 100644 index 7e95c0a3597..00000000000 --- a/policyengine_us/variables/gov/states/mn/dhs/msa/income/mn_msa_earned_income_disregard.py +++ /dev/null @@ -1,58 +0,0 @@ -from policyengine_us.model_api import * - - -class mn_msa_earned_income_disregard(Variable): - value_type = float - entity = Person - label = "Minnesota Supplemental Aid earned income disregard" - unit = USD - definition_period = MONTH - defined_for = StateCode.MN - reference = ( - "https://www.revisor.mn.gov/statutes/cite/256D.44", - "https://www.dhs.state.mn.us/main/groups/county_access/documents/pub/mndhs-073585.pdf#page=2", - ) - - def formula(person, period, parameters): - p = parameters(period).gov.states.mn.dhs.msa.disregard - gross_unearned = add( - person, - period, - [ - "ssi_unearned_income", - "ssi_unearned_income_deemed_from_ineligible_spouse", - "ssi_unearned_income_deemed_from_ineligible_parent", - ], - ) - gross_earned = add( - person, - period, - [ - "ssi_earned_income", - "ssi_earned_income_deemed_from_ineligible_spouse", - ], - ) - arrangement = person("mn_msa_payment_category", period) - LA = arrangement.possible_values - is_couple = (arrangement == LA.COUPLE_LIVING_ALONE) | ( - arrangement == LA.COUPLE_LIVING_WITH_OTHERS - ) - # Couples: aggregate to marital unit, apply $20 + $65 + 1/2 once, - # then split 50/50 across spouses. - couple_unearned = person.marital_unit.sum(gross_unearned) - couple_earned = person.marital_unit.sum(gross_earned) - couple_leftover = max_(p.general - couple_unearned, 0) - couple_flat = p.earned.initial + couple_leftover - couple_flat_disregard = min_(couple_earned, couple_flat) - couple_remainder = max_(couple_earned - couple_flat, 0) - couple_per_spouse = ( - couple_flat_disregard + couple_remainder * p.earned.rate - ) / 2 - individual_leftover = max_(p.general - gross_unearned, 0) - individual_flat = p.earned.initial + individual_leftover - individual_flat_disregard = min_(gross_earned, individual_flat) - individual_remainder = max_(gross_earned - individual_flat, 0) - individual_disregard = ( - individual_flat_disregard + individual_remainder * p.earned.rate - ) - return where(is_couple, couple_per_spouse, individual_disregard) diff --git a/policyengine_us/variables/gov/states/mn/dhs/msa/income/mn_msa_gross_income.py b/policyengine_us/variables/gov/states/mn/dhs/msa/income/mn_msa_gross_income.py deleted file mode 100644 index 94aa9abba74..00000000000 --- a/policyengine_us/variables/gov/states/mn/dhs/msa/income/mn_msa_gross_income.py +++ /dev/null @@ -1,34 +0,0 @@ -from policyengine_us.model_api import * - - -class mn_msa_gross_income(Variable): - value_type = float - entity = Person - label = "Minnesota Supplemental Aid gross income" - unit = USD - definition_period = MONTH - defined_for = StateCode.MN - reference = ( - "https://www.revisor.mn.gov/statutes/cite/256D.44", - "https://www.house.mn.gov/hrd/pubs/pap_MSA.pdf#page=2", - ) - - def formula(person, period, parameters): - # Per Minn. Stat. § 256D.44 Subd. 1, MSA's gross-income screen - # applies to total earned + unearned income. For SSI recipients, - # the county counts the applicable federal SSI benefit rate, not - # the post-income SSI payment. - income = add( - person, - period, - [ - "ssi_earned_income", - "ssi_unearned_income", - "ssi_earned_income_deemed_from_ineligible_spouse", - "ssi_unearned_income_deemed_from_ineligible_spouse", - "ssi_unearned_income_deemed_from_ineligible_parent", - ], - ) - ssi = person("ssi", period) - ssi_fbr = person("ssi_amount_if_eligible", period) - return income + where(ssi > 0, ssi_fbr, ssi) diff --git a/policyengine_us/variables/gov/states/mn/dhs/msa/mn_msa_person.py b/policyengine_us/variables/gov/states/mn/dhs/msa/mn_msa_person.py index 8e6f074e4a7..b29e618e783 100644 --- a/policyengine_us/variables/gov/states/mn/dhs/msa/mn_msa_person.py +++ b/policyengine_us/variables/gov/states/mn/dhs/msa/mn_msa_person.py @@ -1,4 +1,7 @@ from policyengine_us.model_api import * +from policyengine_us.variables.gov.ssa.ssi.eligibility.income._apply_ssi_exclusions import ( + _apply_ssi_exclusions, +) class mn_msa_person(Variable): @@ -15,11 +18,12 @@ class mn_msa_person(Variable): ) def formula(person, period, parameters): - # SSI track counts federal SSI FBR as gross unearned, then applies - # the $20 general disregard. FLA-D (Medicaid facility) is exempt - # from the $20 disregard. Non-SSI track uses mn_msa_countable_income - # (already aggregates $20 + $65 + 1/2 federally). - # Couples are computed at the marital unit then split 50/50. + # MSA inherits federal SSI's $20 / $65 / 1/2 disregards (CM 0018.18). + # The SSI track substitutes the federal SSI FBR for the recipient's + # post-disregard SSI payment so MSA tops up against the FBR rather + # than the federal payment. FLA-D recipients are exempt from the $20 + # general disregard. Couples are computed at the marital unit then + # split 50/50 to avoid wasting disregards on asymmetric income. arrangement = person("mn_msa_payment_category", period) LA = arrangement.possible_values is_couple = (arrangement == LA.COUPLE_LIVING_ALONE) | ( @@ -30,7 +34,7 @@ def formula(person, period, parameters): ssi = person("ssi", period) receives_ssi = ssi > 0 ssi_fbr = person("ssi_amount_if_eligible", period) - p = parameters(period).gov.states.mn.dhs.msa.disregard + p = parameters(period).gov.ssa.ssi.income.exclusions raw_unearned = add( person, period, @@ -55,13 +59,40 @@ def formula(person, period, parameters): individual_ssi_supplement = max_(standard - individual_ssi_countable, 0) ssi_track = where(is_couple, couple_ssi_supplement, individual_ssi_supplement) - # Non-SSI track: countable_income is already per-spouse share for - # couples; sum across the marital unit gives the couple total. - countable_income = person("mn_msa_countable_income", period) - couple_non_ssi_supplement = ( - max_(standard - person.marital_unit.sum(countable_income), 0) / 2 + # Non-SSI track: applies federal SSI exclusions ($20 + $65 + 1/2) + # to raw earned and unearned income. Helper accepts annual amounts + # and returns annual countable; convert to monthly to match MSA. + annual_earned = add( + person, + period.this_year, + [ + "ssi_earned_income", + "ssi_earned_income_deemed_from_ineligible_spouse", + ], + ) + annual_unearned = add( + person, + period.this_year, + [ + "ssi_unearned_income", + "ssi_unearned_income_deemed_from_ineligible_spouse", + "ssi_unearned_income_deemed_from_ineligible_parent", + ], + ) + couple_earned = person.marital_unit.sum(annual_earned) + couple_unearned = person.marital_unit.sum(annual_unearned) + couple_non_ssi_countable_annual = _apply_ssi_exclusions( + couple_earned, couple_unearned, parameters, period + ) + individual_non_ssi_countable_annual = _apply_ssi_exclusions( + annual_earned, annual_unearned, parameters, period + ) + couple_non_ssi_countable = couple_non_ssi_countable_annual / MONTHS_IN_YEAR + individual_non_ssi_countable = ( + individual_non_ssi_countable_annual / MONTHS_IN_YEAR ) - individual_non_ssi_supplement = max_(standard - countable_income, 0) + couple_non_ssi_supplement = max_(standard - couple_non_ssi_countable, 0) / 2 + individual_non_ssi_supplement = max_(standard - individual_non_ssi_countable, 0) non_ssi_track = where( is_couple, couple_non_ssi_supplement, individual_non_ssi_supplement ) diff --git a/policyengine_us/variables/gov/states/mn/dhs/msa/payment/mn_msa_guardian_fee.py b/policyengine_us/variables/gov/states/mn/dhs/msa/payment/mn_msa_guardian_fee.py index dcbc8504514..2ffa581de3b 100644 --- a/policyengine_us/variables/gov/states/mn/dhs/msa/payment/mn_msa_guardian_fee.py +++ b/policyengine_us/variables/gov/states/mn/dhs/msa/payment/mn_msa_guardian_fee.py @@ -14,7 +14,25 @@ class mn_msa_guardian_fee(Variable): ) def formula(person, period, parameters): + # Per Minn. Stat. § 256D.44 Subd. 5(d) and CM 0023.15, the fee + # shall not exceed five percent of the assistance unit's gross + # monthly income up to a maximum of $100 per month. Gross income + # uses the standard CM 0017 / 0019 definition: earned + unearned + # excluding the SSI payment itself per the SSI-exclusion list. + # Aggregate to the marital unit before applying the cap. p = parameters(period).gov.states.mn.dhs.msa.special_needs.guardian_fee has_guardian = person("mn_msa_has_guardian", period) - gross = person("mn_msa_gross_income", period) - return has_guardian * min_(gross * p.rate, p.max_amount) + gross = add( + person, + period, + [ + "ssi_earned_income", + "ssi_unearned_income", + "ssi_earned_income_deemed_from_ineligible_spouse", + "ssi_unearned_income_deemed_from_ineligible_spouse", + "ssi_unearned_income_deemed_from_ineligible_parent", + ], + ) + unit_gross = person.marital_unit.sum(gross) + unit_fee = min_(unit_gross * p.rate, p.max_amount) + return has_guardian * unit_fee From d6dbb836ec940c90a3c4a24df53acd296818032f Mon Sep 17 00:00:00 2001 From: Ziming Date: Mon, 4 May 2026 00:01:29 -0400 Subject: [PATCH 15/17] minor --- uv.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/uv.lock b/uv.lock index ab3334c406c..08fca998113 100644 --- a/uv.lock +++ b/uv.lock @@ -2974,7 +2974,7 @@ wheels = [ [[package]] name = "policyengine-us" -version = "1.665.0" +version = "1.680.0" source = { editable = "." } dependencies = [ { name = "microdf-python" }, From 526ad7c7cff9274b065b46f832191d43db8c7f2d Mon Sep 17 00:00:00 2001 From: Ziming Date: Mon, 4 May 2026 00:31:58 -0400 Subject: [PATCH 16/17] Address V2 review findings on MN MSA MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Critical fixes: - Rep payee fee: drop 1974 zero-row, add 2011 anchor at $25 (per SSA 2011 10%-of-gross-capped-at-$25 rule). Forward-fills from 2011 to match the assistance-standard anchor-year convention. - assistance_standard/amount.yaml: replace wrong subdivision labels (Subd. 2 "basic needs" → Subd. 1 COLA escalator; Subd. 3 "shelter needs" → Subd. 3 "basic needs"). Shelter is in Subd. 5(g). - Replace fabricated "Section 9.5" cite in guardian_fee files with "Special Needs Circumstances - Guardianship fees" (SSA 2011 has no section numbering). - Add 2011-period tests (3 cases in mn_msa_assistance_standard.yaml). - Add COUPLE_LIVING_WITH_OTHERS and NONE coverage to mn_msa_living_arrangement.yaml. CM 0017 correction (per DHS rule "for SSI recipients, count the full amount of the SSI Federal Benefit Rate as gross unearned income"): - mn_msa_gross_income_eligible.py: include SSI FBR for SSI recipients in the 300%/600% gross-income test. - mn_msa_guardian_fee.py: include SSI FBR for SSI recipients in the 5% × AU gross monthly income calc. Test coverage additions: - Medicaid facility with non-zero unearned (FLA-D zero-disregard branch) - Couple net-income at exact-equality boundary ($1,582) and below ($1,581) - Special-needs isolation cases (zero, guardian-only, payee-only, housing-only) - Repurposed integration Case 10 to a couple housing-upgrade scenario - 2024 housing assistance case → $471.50 - Blind 10-year-old eligibility (blind branch has no age cap) - Medicaid facility in mn_msa_eligible_person.yaml and mn_msa_net_income_eligible.yaml Quality cleanups: - Replace stale § 256D.37 cite in mn_msa_resource_eligible.yaml comments. - Document MN-narrower personal-property definition deferral in mn_msa_resource_eligible.py. - Fix misleading $10-half-disregard comment in mn_msa_net_income_eligible Case 4. 102/102 tests pass. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../dhs/msa/assistance_standard/amount.yaml | 4 +- .../guardian_fee/max_amount.yaml | 2 +- .../msa/special_needs/guardian_fee/rate.yaml | 2 +- .../representative_payee_fee/amount.yaml | 2 +- .../eligibility/mn_msa_eligible_person.yaml | 58 +++++++++ .../mn_msa_net_income_eligible.yaml | 117 ++++++++++++++++- .../eligibility/mn_msa_resource_eligible.yaml | 14 ++- .../gov/states/mn/dhs/msa/integration.yaml | 43 +++++-- .../mn/dhs/msa/mn_msa_living_arrangement.yaml | 28 +++++ .../gov/states/mn/dhs/msa/mn_msa_person.yaml | 33 +++++ .../payment/mn_msa_assistance_standard.yaml | 62 +++++++++ .../payment/mn_msa_housing_assistance.yaml | 31 +++++ .../payment/mn_msa_special_needs_total.yaml | 118 ++++++++++++++++++ .../mn_msa_gross_income_eligible.py | 17 ++- .../eligibility/mn_msa_resource_eligible.py | 4 + .../mn/dhs/msa/payment/mn_msa_guardian_fee.py | 13 +- 16 files changed, 511 insertions(+), 37 deletions(-) diff --git a/policyengine_us/parameters/gov/states/mn/dhs/msa/assistance_standard/amount.yaml b/policyengine_us/parameters/gov/states/mn/dhs/msa/assistance_standard/amount.yaml index 9d107ffe831..b533c1209b9 100644 --- a/policyengine_us/parameters/gov/states/mn/dhs/msa/assistance_standard/amount.yaml +++ b/policyengine_us/parameters/gov/states/mn/dhs/msa/assistance_standard/amount.yaml @@ -7,9 +7,9 @@ metadata: breakdown: - mn_msa_living_arrangement reference: - - title: Minn. Stat. § 256D.44 Subd. 2 (Standards of assistance for basic needs) + - title: Minn. Stat. § 256D.44 Subd. 1 (Use of standards; SSI COLA escalator) href: https://www.revisor.mn.gov/statutes/cite/256D.44 - - title: Minn. Stat. § 256D.44 Subd. 3 (Standards of assistance for shelter needs) + - title: Minn. Stat. § 256D.44 Subd. 3 (Standards of assistance for basic needs) href: https://www.revisor.mn.gov/statutes/cite/256D.44 - title: MN DHS Combined Manual 0020.21 - MSA Assistance Standards (01/2026) href: https://www.dhs.state.mn.us/main/groups/county_access/documents/pub/mndhs-073585.pdf#page=4 diff --git a/policyengine_us/parameters/gov/states/mn/dhs/msa/special_needs/guardian_fee/max_amount.yaml b/policyengine_us/parameters/gov/states/mn/dhs/msa/special_needs/guardian_fee/max_amount.yaml index 1b92baeefc0..a4bef844f81 100644 --- a/policyengine_us/parameters/gov/states/mn/dhs/msa/special_needs/guardian_fee/max_amount.yaml +++ b/policyengine_us/parameters/gov/states/mn/dhs/msa/special_needs/guardian_fee/max_amount.yaml @@ -10,5 +10,5 @@ metadata: reference: - title: Minn. Stat. § 256D.44 Subd. 5(d) (Guardian or conservator fee) href: https://www.revisor.mn.gov/statutes/cite/256D.44 - - title: SSA State Assistance Programs for SSI Recipients, January 2011 - Minnesota, Section 9.5 + - title: SSA State Assistance Programs for SSI Recipients, January 2011 - Minnesota, Special Needs Circumstances - Guardianship fees href: https://www.ssa.gov/policy/docs/progdesc/ssi_st_asst/2011/mn.html diff --git a/policyengine_us/parameters/gov/states/mn/dhs/msa/special_needs/guardian_fee/rate.yaml b/policyengine_us/parameters/gov/states/mn/dhs/msa/special_needs/guardian_fee/rate.yaml index e00dfd300d0..e46e61af960 100644 --- a/policyengine_us/parameters/gov/states/mn/dhs/msa/special_needs/guardian_fee/rate.yaml +++ b/policyengine_us/parameters/gov/states/mn/dhs/msa/special_needs/guardian_fee/rate.yaml @@ -10,5 +10,5 @@ metadata: reference: - title: Minn. Stat. § 256D.44 Subd. 5(d) (Guardian or conservator fee) href: https://www.revisor.mn.gov/statutes/cite/256D.44 - - title: SSA State Assistance Programs for SSI Recipients, January 2011 - Minnesota, Section 9.5 + - title: SSA State Assistance Programs for SSI Recipients, January 2011 - Minnesota, Special Needs Circumstances - Guardianship fees href: https://www.ssa.gov/policy/docs/progdesc/ssi_st_asst/2011/mn.html diff --git a/policyengine_us/parameters/gov/states/mn/dhs/msa/special_needs/representative_payee_fee/amount.yaml b/policyengine_us/parameters/gov/states/mn/dhs/msa/special_needs/representative_payee_fee/amount.yaml index b512cf109d8..7a30ab4b1f5 100644 --- a/policyengine_us/parameters/gov/states/mn/dhs/msa/special_needs/representative_payee_fee/amount.yaml +++ b/policyengine_us/parameters/gov/states/mn/dhs/msa/special_needs/representative_payee_fee/amount.yaml @@ -1,7 +1,7 @@ description: Minnesota provides this amount as the representative payee service special-needs allowance under the Minnesota Supplemental Aid program. values: - 1974-04-01: 0 + 2011-01-01: 25 2026-01-01: 57 metadata: diff --git a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/eligibility/mn_msa_eligible_person.yaml b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/eligibility/mn_msa_eligible_person.yaml index b320fbdf8e4..e9d29b5979d 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/eligibility/mn_msa_eligible_person.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/eligibility/mn_msa_eligible_person.yaml @@ -256,3 +256,61 @@ state_code: MN output: mn_msa_eligible_person: false + +- name: Case 10, blind 10-year-old citizen is eligible (blind has no age cap). + period: 2026-01 + input: + people: + person1: + age: 10 + is_blind: true + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 500 + ssi: 11_928 + marital_units: + marital_unit: + members: [person1] + tax_units: + tax_unit: + members: [person1] + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MN + output: + # Blind branch in (aged | blind | disabled_adult) does not gate on age >= 18. + mn_msa_eligible_person: true + +- name: Case 11, Medicaid facility resident is eligible. + period: 2026-01 + input: + people: + person1: + age: 80 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 200 + ssi_lives_in_medical_treatment_facility: true + ssi_medicaid_pays_majority_of_care: true + ssi: 360 # $30/mo FLA-D + marital_units: + marital_unit: + members: [person1] + tax_units: + tax_unit: + members: [person1] + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MN + output: + # Aged + receives federal SSI + low resources + facility category != NONE. + mn_msa_eligible_person: true diff --git a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/eligibility/mn_msa_net_income_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/eligibility/mn_msa_net_income_eligible.yaml index 40d84e66ccf..1007b6447e8 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/eligibility/mn_msa_net_income_eligible.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/eligibility/mn_msa_net_income_eligible.yaml @@ -97,7 +97,7 @@ mn_msa_living_arrangement: COUPLE_LIVING_ALONE ssi_countable_resources: 5_000 ssi: 0 - ssi_unearned_income: 9_618 # $801.50/mo; minus $10 half-disregard = $791.50 + ssi_unearned_income: 9_618 # $801.50/mo person2: age: 70 is_blind: false @@ -106,7 +106,7 @@ mn_msa_living_arrangement: COUPLE_LIVING_ALONE ssi_countable_resources: 5_000 ssi: 0 - ssi_unearned_income: 9_618 # $801.50/mo; minus $10 half-disregard = $791.50 + ssi_unearned_income: 9_618 # $801.50/mo marital_units: marital_unit: members: [person1, person2] @@ -121,5 +121,116 @@ members: [person1, person2] state_code: MN output: - # Couple countable income $1,583 > $1,582 couple standard. + # Non-SSI couple: AU-aggregate raw unearned $1,603/mo - $20 general + # disregard = $1,603 countable. $1,603 > $1,582 couple standard -> + # ineligible for both spouses. mn_msa_net_income_eligible: [false, false] + +- name: Case 5, non-SSI couple at exact-equal couple standard is eligible. + period: 2026-01 + input: + people: + person1: + age: 70 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + mn_msa_living_arrangement: COUPLE_LIVING_ALONE + ssi_countable_resources: 5_000 + ssi: 0 + ssi_unearned_income: 9_612 # $801/mo each = AU $1,602 + person2: + age: 70 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + mn_msa_living_arrangement: COUPLE_LIVING_ALONE + ssi_countable_resources: 5_000 + ssi: 0 + ssi_unearned_income: 9_612 + 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: MN + output: + # AU unearned $1,602/mo - $20 = $1,582 = couple standard -> eligible. + mn_msa_net_income_eligible: [true, true] + +- name: Case 6, non-SSI couple $1 below couple standard is eligible. + period: 2026-01 + input: + people: + person1: + age: 70 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + mn_msa_living_arrangement: COUPLE_LIVING_ALONE + ssi_countable_resources: 5_000 + ssi: 0 + ssi_unearned_income: 9_606 # $800.50/mo each = AU $1,601 + person2: + age: 70 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + mn_msa_living_arrangement: COUPLE_LIVING_ALONE + ssi_countable_resources: 5_000 + ssi: 0 + ssi_unearned_income: 9_606 + 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: MN + output: + # AU unearned $1,601/mo - $20 = $1,581 < $1,582 couple standard -> eligible. + mn_msa_net_income_eligible: [true, true] + +- name: Case 7, Medicaid facility recipient with $30 FBR and $0 unearned passes net-income test. + period: 2026-01 + input: + people: + person1: + age: 80 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 200 + ssi_lives_in_medical_treatment_facility: true + ssi_medicaid_pays_majority_of_care: true + ssi: 360 # $30/mo FLA-D + marital_units: + marital_unit: + members: [person1] + tax_units: + tax_unit: + members: [person1] + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MN + output: + # FLA-D carve-out: disregard = 0. SSI track countable = $30 FBR + 0 unearned + # = $30. Standard $132 PNA -> $30 <= $132 -> eligible. + mn_msa_net_income_eligible: true diff --git a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/eligibility/mn_msa_resource_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/eligibility/mn_msa_resource_eligible.yaml index 45b015c9625..f62853a0132 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/eligibility/mn_msa_resource_eligible.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/eligibility/mn_msa_resource_eligible.yaml @@ -23,7 +23,8 @@ members: [person1] state_code: MN output: - # SSI track: federal $2,000 limit already enforced via is_ssi_eligible. + # SSI recipients pass MSA's resource test by virtue of receiving SSI + # (federal $2,000/$3,000 caps are enforced upstream in the SSI calc). mn_msa_resource_eligible: true - name: Case 2, non-SSI track at $10,000 boundary is resource eligible. @@ -162,7 +163,8 @@ members: [person1] state_code: MN output: - # SSI track: $1,999 < $2,000 federal cap, is_ssi_eligible passes. + # SSI recipient: passes MSA resource test by virtue of receiving SSI. + # The federal $2,000 cap is enforced upstream and is not exercised here. mn_msa_resource_eligible: true - name: Case 7, SSI couple with combined resources just below $3,000 cap. @@ -199,7 +201,8 @@ members: [person1, person2] state_code: MN output: - # SSI track for both: federal $3,000 couple cap enforced via is_ssi_eligible. + # Both spouses are SSI recipients so they pass MSA's resource test. + # The federal $3,000 couple cap is enforced upstream and not exercised. mn_msa_resource_eligible: [true, true] - name: Case 8, person with high resources but ssi=0 falls back to non-SSI track. @@ -262,8 +265,9 @@ members: [person1, person2] state_code: MN output: - # § 256D.37 Subd. 1: $10,000 cap applies to the assistance unit. - # Combined $12,000 > $10,000 -> both spouses ineligible. + # § 256P.02 Subd. 2 (applied via § 256D.425 Subd. 2(b)): $10,000 cap + # applies to the assistance unit. Combined $12,000 > $10,000 -> both + # spouses ineligible. mn_msa_resource_eligible: [false, false] - name: Case 10, non-SSI couple with combined resources at the $10,000 boundary is eligible. diff --git a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/integration.yaml b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/integration.yaml index 2174446b510..cfc02d471f4 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/integration.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/integration.yaml @@ -343,7 +343,7 @@ mn_msa_person: [81, 0] mn_msa: 81 -- name: Case 10, recipient with housing allowance add-on via Subd. 5(g)(1) pathway. +- name: Case 10, couple living-with-others where one spouse has housing assistance triggers couple "living alone" upgrade. period: 2026-01 absolute_error_margin: 0.01 input: @@ -354,31 +354,48 @@ is_disabled: false immigration_status: CITIZEN ssi_countable_resources: 500 + mn_msa_living_arrangement: COUPLE_LIVING_WITH_OTHERS mn_msa_housing_assistance_eligible: true - pre_subsidy_rent: 6_000 - ssi: 11_928 + ssi_claim_is_joint: true + ssi: 8_946 # $745.50/mo each + ssi_earned_income: 0 + ssi_unearned_income: 0 + person2: + age: 68 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 500 + mn_msa_living_arrangement: COUPLE_LIVING_WITH_OTHERS + mn_msa_housing_assistance_eligible: false + ssi_claim_is_joint: true + ssi: 8_946 ssi_earned_income: 0 ssi_unearned_income: 0 marital_units: marital_unit: - members: [person1] + members: [person1, person2] tax_units: tax_unit: - members: [person1] + members: [person1, person2] spm_units: spm_unit: - members: [person1] + members: [person1, person2] households: household: - members: [person1] + members: [person1, person2] state_code: MN output: - mn_msa_assistance_standard: 1_055 - mn_msa_housing_assistance: 497 # 0.5 * $994 - mn_msa_eligible_person: true - # Base: $1,055 - max($994 SSI - $20, 0) = $81; plus $497 housing add-on = $578. - mn_msa_person: 578 - mn_msa: 578 + # marital_unit.any(housing_assistance_eligible) -> True for both spouses. + # COUPLE_LIVING_WITH_OTHERS upgrades to COUPLE_LIVING_ALONE standard ($1,582). + mn_msa_payment_category: [COUPLE_LIVING_ALONE, COUPLE_LIVING_ALONE] + mn_msa_assistance_standard: [1_582, 1_582] + # Couple SSI track countable = $1,491 FBR + 0 unearned - $20 = $1,471. + # Supplement = max(0, $1,582 - $1,471) / 2 = $55.50 each. + # Housing add-on (0.5 * $994) goes only to person1 (the eligible spouse). + mn_msa_housing_assistance: [497, 0] + mn_msa_person: [552.50, 55.50] + mn_msa: 608 - name: Case 11, non-SSI track recipient with earned income, rep payee, and guardian fee. period: 2026-01 diff --git a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/mn_msa_living_arrangement.yaml b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/mn_msa_living_arrangement.yaml index 27afb28f4f7..a77cbbbdb15 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/mn_msa_living_arrangement.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/mn_msa_living_arrangement.yaml @@ -52,3 +52,31 @@ state_code: MN output: mn_msa_living_arrangement: MEDICAID_FACILITY + +- name: Case 5, user-provided COUPLE_LIVING_WITH_OTHERS passes through. + period: 2026-01 + input: + people: + person1: + age: 70 + mn_msa_living_arrangement: COUPLE_LIVING_WITH_OTHERS + households: + household: + members: [person1] + state_code: MN + output: + mn_msa_living_arrangement: COUPLE_LIVING_WITH_OTHERS + +- name: Case 6, user-provided NONE passes through. + period: 2026-01 + input: + people: + person1: + age: 70 + mn_msa_living_arrangement: NONE + households: + household: + members: [person1] + state_code: MN + output: + mn_msa_living_arrangement: NONE diff --git a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/mn_msa_person.yaml b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/mn_msa_person.yaml index 935189f95d9..666dbd29b6c 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/mn_msa_person.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/mn_msa_person.yaml @@ -461,3 +461,36 @@ # Negative earnings must not push supplement above the standard. # SSI-track countable floors at 0; supplement = $1,055 - max($994 - $20, 0) = $81. mn_msa_person: 81 + +- name: Case 14, Medicaid facility with non-zero unearned exercises the FLA-D zero-disregard branch. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 80 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 200 + ssi_lives_in_medical_treatment_facility: true + ssi_medicaid_pays_majority_of_care: true + ssi: 360 # $30/mo FLA-D + ssi_unearned_income: 1_200 # $100/mo + marital_units: + marital_unit: + members: [person1] + tax_units: + tax_unit: + members: [person1] + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MN + output: + # FLA-D carve-out: disregard = 0 (no $20). Countable = $30 FBR + $100 unearned = $130. + # Standard $132 - $130 = $2 state supplement. + mn_msa_person: 2 diff --git a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/payment/mn_msa_assistance_standard.yaml b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/payment/mn_msa_assistance_standard.yaml index bf7b5c10760..75e78a0bfd3 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/payment/mn_msa_assistance_standard.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/payment/mn_msa_assistance_standard.yaml @@ -143,3 +143,65 @@ state_code: MN output: mn_msa_assistance_standard: 1_004 + +- name: Case 8, individual living alone in 2011 uses 2011 rate. + period: 2011-01 + input: + people: + person1: + age: 70 + ssi_countable_resources: 500 + ssi: 8_088 # $674/mo 2011 federal individual FBR + marital_units: + marital_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MN + output: + mn_msa_assistance_standard: 735 + +- name: Case 9, couple living alone in 2011 uses 2011 rate. + period: 2011-01 + input: + people: + person1: + age: 70 + ssi_countable_resources: 500 + mn_msa_living_arrangement: COUPLE_LIVING_ALONE + ssi_claim_is_joint: true + person2: + age: 68 + ssi_countable_resources: 500 + mn_msa_living_arrangement: COUPLE_LIVING_ALONE + ssi_claim_is_joint: true + marital_units: + marital_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: MN + output: + mn_msa_assistance_standard: [1_102, 1_102] + +- name: Case 10, Medicaid facility in 2011 uses 2011 PNA convention. + period: 2011-01 + input: + people: + person1: + age: 80 + ssi_countable_resources: 200 + ssi_lives_in_medical_treatment_facility: true + ssi_medicaid_pays_majority_of_care: true + ssi: 360 # $30/mo FLA-D + marital_units: + marital_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MN + output: + mn_msa_assistance_standard: 89 diff --git a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/payment/mn_msa_housing_assistance.yaml b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/payment/mn_msa_housing_assistance.yaml index f07bd3ce74b..4821384c596 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/payment/mn_msa_housing_assistance.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/payment/mn_msa_housing_assistance.yaml @@ -58,3 +58,34 @@ state_code: MN output: mn_msa_housing_assistance: 0 + +- name: Case 3, 2024 housing assistance uses 2024 individual FBR. + period: 2024-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 50 + is_blind: false + is_disabled: true + immigration_status: CITIZEN + ssi_countable_resources: 500 + mn_msa_housing_assistance_eligible: true + pre_subsidy_rent: 6_000 + ssi: 11_316 # $943/mo (2024 individual FBR) + marital_units: + marital_unit: + members: [person1] + tax_units: + tax_unit: + members: [person1] + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MN + output: + # 0.5 × $943 = $471.50. + mn_msa_housing_assistance: 471.50 diff --git a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/payment/mn_msa_special_needs_total.yaml b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/payment/mn_msa_special_needs_total.yaml index 3ff6624fbe7..cd7a1c803aa 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/payment/mn_msa_special_needs_total.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/payment/mn_msa_special_needs_total.yaml @@ -67,3 +67,121 @@ output: # Representative payee $57 + guardian $100 + housing $497. mn_msa_special_needs_total: 654 + +- name: Case 3, no special needs returns zero. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 70 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 500 + ssi_unearned_income: 12_000 + marital_units: + marital_unit: + members: [person1] + tax_units: + tax_unit: + members: [person1] + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MN + output: + mn_msa_special_needs_total: 0 + +- name: Case 4, guardian only (no payee, no housing). + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 70 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 500 + mn_msa_has_guardian: true + ssi: 11_928 + marital_units: + marital_unit: + members: [person1] + tax_units: + tax_unit: + members: [person1] + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MN + output: + # 5% × $994 SSI FBR (per CM 0017) = $49.70. + mn_msa_special_needs_total: 49.70 + +- name: Case 5, representative payee only. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 70 + is_blind: false + is_disabled: false + immigration_status: CITIZEN + ssi_countable_resources: 500 + mn_msa_uses_representative_payee: true + ssi: 11_928 + marital_units: + marital_unit: + members: [person1] + tax_units: + tax_unit: + members: [person1] + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MN + output: + mn_msa_special_needs_total: 57 + +- name: Case 6, housing assistance only. + period: 2026-01 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 50 + is_blind: false + is_disabled: true + immigration_status: CITIZEN + ssi_countable_resources: 500 + mn_msa_housing_assistance_eligible: true + pre_subsidy_rent: 6_000 + ssi: 11_928 + marital_units: + marital_unit: + members: [person1] + tax_units: + tax_unit: + members: [person1] + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: MN + output: + # 0.5 × $994 SSI individual FBR = $497. + mn_msa_special_needs_total: 497 diff --git a/policyengine_us/variables/gov/states/mn/dhs/msa/eligibility/mn_msa_gross_income_eligible.py b/policyengine_us/variables/gov/states/mn/dhs/msa/eligibility/mn_msa_gross_income_eligible.py index 92fee91e32a..e13a80161b3 100644 --- a/policyengine_us/variables/gov/states/mn/dhs/msa/eligibility/mn_msa_gross_income_eligible.py +++ b/policyengine_us/variables/gov/states/mn/dhs/msa/eligibility/mn_msa_gross_income_eligible.py @@ -15,12 +15,14 @@ class mn_msa_gross_income_eligible(Variable): def formula(person, period, parameters): # Per CM 0019.06, gross income may not exceed 300% of the federal # SSI individual benefit rate for an individual or 600% for a couple - # where both spouses are MSA-categorically eligible. + # where both spouses are MSA-categorically eligible. Per CM 0017, + # for SSI recipients count the full SSI FBR as gross unearned + # income (even when the actual SSI check is less). p = parameters(period).gov.states.mn.dhs.msa.eligibility.income_limit - ssi_fbr = parameters(period).gov.ssa.ssi.amount.individual + ssi_individual_fbr = parameters(period).gov.ssa.ssi.amount.individual categorically_eligible = person("is_ssi_aged_blind_disabled", period.this_year) both_eligible = person.marital_unit.sum(categorically_eligible) == 2 - gross = add( + income = add( person, period, [ @@ -31,8 +33,11 @@ def formula(person, period, parameters): "ssi_unearned_income_deemed_from_ineligible_parent", ], ) - income = where(both_eligible, person.marital_unit.sum(gross), gross) - cap = ssi_fbr * where( + ssi = person("ssi", period) + ssi_fbr = person("ssi_amount_if_eligible", period) + gross = income + where(ssi > 0, ssi_fbr, 0) + au_income = where(both_eligible, person.marital_unit.sum(gross), gross) + cap = ssi_individual_fbr * where( both_eligible, p.couple_fbr_multiplier, p.individual_fbr_multiplier ) - return income <= cap + return au_income <= cap diff --git a/policyengine_us/variables/gov/states/mn/dhs/msa/eligibility/mn_msa_resource_eligible.py b/policyengine_us/variables/gov/states/mn/dhs/msa/eligibility/mn_msa_resource_eligible.py index 8da35b9a9cb..6475f145a17 100644 --- a/policyengine_us/variables/gov/states/mn/dhs/msa/eligibility/mn_msa_resource_eligible.py +++ b/policyengine_us/variables/gov/states/mn/dhs/msa/eligibility/mn_msa_resource_eligible.py @@ -18,6 +18,10 @@ def formula(person, period, parameters): # resource test. Per § 256P.02 Subd. 2 (applied to MSA via # § 256D.425 Subd. 2(b)), the non-SSI $10,000 cap applies to the # assistance unit (married couple combined), not per person. + # We use ssi_countable_resources here as a proxy; § 256P.02 Subd. 2(b) + # has a narrower personal-property definition (cash, bank accounts, + # liquid stocks/bonds, non-excluded vehicles, business accounts) but + # we don't track that breakdown at the moment. p = parameters(period).gov.states.mn.dhs.msa.eligibility receives_ssi = person("ssi", period) > 0 countable_resources = person("ssi_countable_resources", period.this_year) diff --git a/policyengine_us/variables/gov/states/mn/dhs/msa/payment/mn_msa_guardian_fee.py b/policyengine_us/variables/gov/states/mn/dhs/msa/payment/mn_msa_guardian_fee.py index 2ffa581de3b..87d919c7654 100644 --- a/policyengine_us/variables/gov/states/mn/dhs/msa/payment/mn_msa_guardian_fee.py +++ b/policyengine_us/variables/gov/states/mn/dhs/msa/payment/mn_msa_guardian_fee.py @@ -16,13 +16,13 @@ class mn_msa_guardian_fee(Variable): def formula(person, period, parameters): # Per Minn. Stat. § 256D.44 Subd. 5(d) and CM 0023.15, the fee # shall not exceed five percent of the assistance unit's gross - # monthly income up to a maximum of $100 per month. Gross income - # uses the standard CM 0017 / 0019 definition: earned + unearned - # excluding the SSI payment itself per the SSI-exclusion list. - # Aggregate to the marital unit before applying the cap. + # monthly income up to a maximum of $100 per month. Per CM 0017, + # for SSI recipients count the full SSI FBR as gross unearned + # income (even when the actual SSI check is less). Aggregate to + # the marital unit before applying the cap. p = parameters(period).gov.states.mn.dhs.msa.special_needs.guardian_fee has_guardian = person("mn_msa_has_guardian", period) - gross = add( + income = add( person, period, [ @@ -33,6 +33,9 @@ def formula(person, period, parameters): "ssi_unearned_income_deemed_from_ineligible_parent", ], ) + ssi = person("ssi", period) + ssi_fbr = person("ssi_amount_if_eligible", period) + gross = income + where(ssi > 0, ssi_fbr, 0) unit_gross = person.marital_unit.sum(gross) unit_fee = min_(unit_gross * p.rate, p.max_amount) return has_guardian * unit_fee From f19e627327c10723bde0c7606824a840277ce4fe Mon Sep 17 00:00:00 2001 From: Ziming Date: Mon, 4 May 2026 01:06:03 -0400 Subject: [PATCH 17/17] Clean up MN MSA: drop unused pre_subsidy_rent, document duplication - Remove pre_subsidy_rent from 10 test cases (no formula reads it now that housing-assistance financial test is deferred) - Add cycle-avoidance comment to mn_msa_person and mn_msa_net_income_eligible explaining why countable-income logic is intentionally duplicated - Document INDIVIDUAL_LIVING_ALONE default caveat on mn_msa_living_arrangement Co-Authored-By: Claude Opus 4.7 (1M context) --- .../policy/baseline/gov/states/mn/dhs/msa/integration.yaml | 1 - .../policy/baseline/gov/states/mn/dhs/msa/mn_msa_person.yaml | 1 - .../states/mn/dhs/msa/payment/mn_msa_housing_assistance.yaml | 3 --- .../states/mn/dhs/msa/payment/mn_msa_payment_category.yaml | 2 -- .../mn/dhs/msa/payment/mn_msa_special_needs_total.yaml | 3 --- .../mn/dhs/msa/eligibility/mn_msa_net_income_eligible.py | 4 ++++ .../gov/states/mn/dhs/msa/mn_msa_living_arrangement.py | 4 ++++ .../variables/gov/states/mn/dhs/msa/mn_msa_person.py | 5 +++++ 8 files changed, 13 insertions(+), 10 deletions(-) diff --git a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/integration.yaml b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/integration.yaml index cfc02d471f4..b255c1bd7ef 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/integration.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/integration.yaml @@ -172,7 +172,6 @@ immigration_status: CITIZEN ssi_countable_resources: 500 mn_msa_housing_assistance_eligible: true - pre_subsidy_rent: 6_000 ssi: 11_928 ssi_earned_income: 0 ssi_unearned_income: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/mn_msa_person.yaml b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/mn_msa_person.yaml index 666dbd29b6c..c0e330c2d2e 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/mn_msa_person.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/mn_msa_person.yaml @@ -287,7 +287,6 @@ immigration_status: CITIZEN mn_msa_living_arrangement: INDIVIDUAL_LIVING_WITH_OTHERS mn_msa_housing_assistance_eligible: true - pre_subsidy_rent: 6_000 ssi_countable_resources: 500 ssi: 11_928 ssi_earned_income: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/payment/mn_msa_housing_assistance.yaml b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/payment/mn_msa_housing_assistance.yaml index 4821384c596..01bb5bda0dd 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/payment/mn_msa_housing_assistance.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/payment/mn_msa_housing_assistance.yaml @@ -10,7 +10,6 @@ immigration_status: CITIZEN ssi_countable_resources: 500 mn_msa_housing_assistance_eligible: true - pre_subsidy_rent: 6_000 ssi: 11_928 marital_units: marital_unit: @@ -41,7 +40,6 @@ immigration_status: CITIZEN ssi_countable_resources: 500 mn_msa_housing_assistance_eligible: false - pre_subsidy_rent: 6_000 ssi: 11_928 marital_units: marital_unit: @@ -71,7 +69,6 @@ immigration_status: CITIZEN ssi_countable_resources: 500 mn_msa_housing_assistance_eligible: true - pre_subsidy_rent: 6_000 ssi: 11_316 # $943/mo (2024 individual FBR) marital_units: marital_unit: diff --git a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/payment/mn_msa_payment_category.yaml b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/payment/mn_msa_payment_category.yaml index dce37fc9a18..950d70c9a8d 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/payment/mn_msa_payment_category.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/payment/mn_msa_payment_category.yaml @@ -50,7 +50,6 @@ is_disabled: true mn_msa_living_arrangement: INDIVIDUAL_LIVING_WITH_OTHERS mn_msa_housing_assistance_eligible: true - pre_subsidy_rent: 6_000 ssi: 11_928 households: household: @@ -67,7 +66,6 @@ age: 70 mn_msa_living_arrangement: COUPLE_LIVING_WITH_OTHERS mn_msa_housing_assistance_eligible: true - pre_subsidy_rent: 6_000 ssi: 11_928 person2: age: 70 diff --git a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/payment/mn_msa_special_needs_total.yaml b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/payment/mn_msa_special_needs_total.yaml index cd7a1c803aa..41df2492f2f 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/payment/mn_msa_special_needs_total.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/mn/dhs/msa/payment/mn_msa_special_needs_total.yaml @@ -14,7 +14,6 @@ mn_msa_uses_representative_payee: true mn_msa_has_guardian: true mn_msa_housing_assistance_eligible: true - pre_subsidy_rent: 18_000 ssi: 360 ssi_unearned_income: 24_000 marital_units: @@ -49,7 +48,6 @@ mn_msa_uses_representative_payee: true mn_msa_has_guardian: true mn_msa_housing_assistance_eligible: true - pre_subsidy_rent: 12_000 ssi_unearned_income: 24_000 marital_units: marital_unit: @@ -167,7 +165,6 @@ immigration_status: CITIZEN ssi_countable_resources: 500 mn_msa_housing_assistance_eligible: true - pre_subsidy_rent: 6_000 ssi: 11_928 marital_units: marital_unit: diff --git a/policyengine_us/variables/gov/states/mn/dhs/msa/eligibility/mn_msa_net_income_eligible.py b/policyengine_us/variables/gov/states/mn/dhs/msa/eligibility/mn_msa_net_income_eligible.py index 07f14065372..e320803979a 100644 --- a/policyengine_us/variables/gov/states/mn/dhs/msa/eligibility/mn_msa_net_income_eligible.py +++ b/policyengine_us/variables/gov/states/mn/dhs/msa/eligibility/mn_msa_net_income_eligible.py @@ -16,6 +16,10 @@ class mn_msa_net_income_eligible(Variable): ) def formula(person, period, parameters): + # Countable-income logic intentionally duplicates mn_msa_person to + # avoid a defined_for cycle (mn_msa_person is gated on + # mn_msa_eligible_person, which depends on this variable). Keep the + # two formulas in sync when changing income treatment. # Per House Research Oct 2024 p.3 and CM 0018.18: for SSI recipients, # MSA's net-income calc counts the full federal SSI FBR as gross # unearned income and applies only the $20 general disregard diff --git a/policyengine_us/variables/gov/states/mn/dhs/msa/mn_msa_living_arrangement.py b/policyengine_us/variables/gov/states/mn/dhs/msa/mn_msa_living_arrangement.py index 103d442bf43..110809143f1 100644 --- a/policyengine_us/variables/gov/states/mn/dhs/msa/mn_msa_living_arrangement.py +++ b/policyengine_us/variables/gov/states/mn/dhs/msa/mn_msa_living_arrangement.py @@ -17,6 +17,10 @@ class mn_msa_living_arrangement(Variable): definition_period = MONTH defined_for = StateCode.MN possible_values = MNMSALivingArrangement + # Defaults to INDIVIDUAL_LIVING_ALONE; users modeling persons in shared + # housing, congregate care, or Medicaid facilities should set this + # explicitly. The default is reasonable for single-person households but + # may overstate benefits for elderly persons living with relatives. default_value = MNMSALivingArrangement.INDIVIDUAL_LIVING_ALONE reference = ( "https://www.revisor.mn.gov/statutes/cite/256D.44", diff --git a/policyengine_us/variables/gov/states/mn/dhs/msa/mn_msa_person.py b/policyengine_us/variables/gov/states/mn/dhs/msa/mn_msa_person.py index b29e618e783..8b56d48abd8 100644 --- a/policyengine_us/variables/gov/states/mn/dhs/msa/mn_msa_person.py +++ b/policyengine_us/variables/gov/states/mn/dhs/msa/mn_msa_person.py @@ -18,6 +18,11 @@ class mn_msa_person(Variable): ) def formula(person, period, parameters): + # Countable-income logic intentionally duplicates + # mn_msa_net_income_eligible to avoid a defined_for cycle (this + # variable is gated on mn_msa_eligible_person, which depends on + # mn_msa_net_income_eligible). Keep the two formulas in sync when + # changing income treatment. # MSA inherits federal SSI's $20 / $65 / 1/2 disregards (CM 0018.18). # The SSI track substitutes the federal SSI FBR for the recipient's # post-disregard SSI payment so MSA tops up against the FBR rather