diff --git a/changelog.d/co-adult-financial-ism.fixed.md b/changelog.d/co-adult-financial-ism.fixed.md new file mode 100644 index 00000000000..9f56ac23d37 --- /dev/null +++ b/changelog.d/co-adult-financial-ism.fixed.md @@ -0,0 +1 @@ +Count Colorado's in-kind support and maintenance (9 CCR 2503-5 3.520.79) in AND-CS income: SSA's one-third reduction for SSI recipients in another person's household from March 2014, Colorado's maximum for clients without SSI, and the pre-2014 shelter and food components. diff --git a/policyengine_us/parameters/gov/states/co/ssa/ism/component_share/food.yaml b/policyengine_us/parameters/gov/states/co/ssa/ism/component_share/food.yaml new file mode 100644 index 00000000000..bf70dd4f1f7 --- /dev/null +++ b/policyengine_us/parameters/gov/states/co/ssa/ism/component_share/food.yaml @@ -0,0 +1,21 @@ +description: Before 2014 Colorado counted this share of the Aid to the Needy Disabled-Colorado Supplement grant standard as unearned income when food was supplied in full at no cost. + +values: + # Applied from 2007, as for the shelter share. The table effective + # January 1, 2014 dropped the food component. + 2007-01-01: 0.205 + 2014-01-01: 0 + +metadata: + unit: /1 + period: month + label: Colorado AND-CS food component share before 2014 + reference: + - title: 9 CCR 2503-1, Section 3.250.222 (effective January 1, 2008), in-kind items other than shelter and utilities + href: https://www.sos.state.co.us/CCR/GenerateRulePdf.do?fileName=9+CCR+2503-1&ruleVersionId=2137#page=103 + - title: 9 CCR 2503-1, Sections 3.460.43, 3.460.45, AND/AB/SSI-CS components (emergency rule effective January 1, 2007) + href: https://www.sos.state.co.us/CCR/GenerateRulePdf.do?fileName=9+CCR+2503-1&ruleVersionId=2137#page=134 + - title: 9 CCR 2503-2, Section 3.250.222 (effective September 15, 2012) + href: https://www.sos.state.co.us/CCR/GenerateRulePdf.do?fileName=9+CCR+2503-2&ruleVersionId=4793#page=32 + - title: 9 CCR 2503-4, Section 3.460.45, grant component table without a food component (effective January 1, 2014) + href: https://www.sos.state.co.us/CCR/GenerateRulePdf.do?fileName=9+CCR+2503-4&ruleVersionId=5539#page=13 diff --git a/policyengine_us/parameters/gov/states/co/ssa/ism/component_share/shelter.yaml b/policyengine_us/parameters/gov/states/co/ssa/ism/component_share/shelter.yaml new file mode 100644 index 00000000000..b58349b3220 --- /dev/null +++ b/policyengine_us/parameters/gov/states/co/ssa/ism/component_share/shelter.yaml @@ -0,0 +1,22 @@ +description: Before 2014 Colorado counted up to this share of the Aid to the Needy Disabled-Colorado Supplement grant standard as in-kind support and maintenance for shelter and utilities. + +values: + # Applied from 2007, the first table whose base is the grant standard + # without a conflicting figure (the January 1, 2004 table had 37.0% and + # printed a $513 maximum grant). From 2014 the cap is a dollar amount + # (ism/maximum.yaml). + 2007-01-01: 0.366 + +metadata: + unit: /1 + period: month + label: Colorado AND-CS shelter and utilities component share before 2014 + reference: + - title: 9 CCR 2503-1, Section 3.250.22 (effective January 1, 2008; ISM capped at the shelter and utility component) + href: https://www.sos.state.co.us/CCR/GenerateRulePdf.do?fileName=9+CCR+2503-1&ruleVersionId=2137#page=101 + - title: 9 CCR 2503-1, Sections 3.460.43, 3.460.45, AND/AB/SSI-CS components (emergency rule effective January 1, 2007) + href: https://www.sos.state.co.us/CCR/GenerateRulePdf.do?fileName=9+CCR+2503-1&ruleVersionId=2137#page=134 + - title: 9 CCR 2503-2, Section 3.250.22 (effective September 15, 2012) + href: https://www.sos.state.co.us/CCR/GenerateRulePdf.do?fileName=9+CCR+2503-2&ruleVersionId=4793#page=30 + - title: 9 CCR 2503-4, Sections 3.460.43, 3.460.45 (effective September 15, 2012) + href: https://www.sos.state.co.us/CCR/GenerateRulePdf.do?fileName=9+CCR+2503-4&ruleVersionId=4795#page=13 diff --git a/policyengine_us/parameters/gov/states/co/ssa/ism/maximum.yaml b/policyengine_us/parameters/gov/states/co/ssa/ism/maximum.yaml new file mode 100644 index 00000000000..36d94d73c50 --- /dev/null +++ b/policyengine_us/parameters/gov/states/co/ssa/ism/maximum.yaml @@ -0,0 +1,52 @@ +description: Colorado counts up to this amount of in-kind support and maintenance for shelter as unearned income of an Old Age Pension or Aid to the Needy Disabled-Colorado Supplement client. + +values: + # 9 CCR 2503-5 3.520.79(E): one-third of the SSI benefit standard plus a + # $20 disregard, rounded to the nearest dollar. The rule publishes $284 for + # 2021, a dollar below that formula's $285; the published amounts are used. + 2014-01-01: 260 + 2015-01-01: 264 + 2017-01-01: 265 + 2018-01-01: 270 + 2019-01-01: 277 + 2020-01-01: 281 + 2021-01-01: 284 + 2022-01-01: 300 + 2023-01-01: 325 + 2024-01-01: 334 + 2025-01-01: 342 + 2026-01-01: 351 + +metadata: + unit: currency-USD + period: month + label: Colorado Adult Financial maximum in-kind support and maintenance + reference: + - title: 9 CCR 2503-4, Section 3.460.45, AND/AB/SSI-CS grant component table (effective January 1, 2014) + href: https://www.sos.state.co.us/CCR/GenerateRulePdf.do?fileName=9+CCR+2503-4&ruleVersionId=5539#page=13 + - title: 9 CCR 2503-5, Sections 3.530(B), 3.546(E) (effective March 2, 2014) + href: https://www.sos.state.co.us/CCR/GenerateRulePdf.do?fileName=9+CCR+2503-5&ruleVersionId=5597#page=44 + - title: 9 CCR 2503-5, Sections 3.530(B), 3.546(E) (effective January 1, 2015) + href: https://www.sos.state.co.us/CCR/GenerateRulePdf.do?fileName=9+CCR+2503-5&ruleVersionId=6068#page=45 + - title: 9 CCR 2503-5, Sections 3.530(B), 3.546(E) (effective November 4, 2016; 2017 amount) + href: https://www.sos.state.co.us/CCR/GenerateRulePdf.do?fileName=9+CCR+2503-5&ruleVersionId=6928#page=47 + - title: 9 CCR 2503-5, Sections 3.530(B), 3.546(E) (effective December 1, 2017; 2018 amount) + href: https://www.sos.state.co.us/CCR/GenerateRulePdf.do?fileName=9+CCR+2503-5&ruleVersionId=7377#page=46 + - title: 9 CCR 2503-5, Sections 3.530(B), 3.546(E) (effective November 2, 2018; 2019 amount) + href: https://www.sos.state.co.us/CCR/GenerateRulePdf.do?fileName=9+CCR+2503-5&ruleVersionId=7827#page=46 + - title: 9 CCR 2503-5, Sections 3.530(B), 3.546(E) (effective January 1, 2020) + href: https://www.sos.state.co.us/CCR/GenerateRulePdf.do?fileName=9+CCR+2503-5&ruleVersionId=8443#page=46 + - title: 9 CCR 2503-5, Sections 3.530(B), 3.546(D) (effective January 1, 2021) + href: https://www.sos.state.co.us/CCR/GenerateRulePdf.do?fileName=9+CCR+2503-5&ruleVersionId=9241#page=61 + - title: 9 CCR 2503-5, Sections 3.530(B), 3.546(D) (effective January 1, 2022) + href: https://www.sos.state.co.us/CCR/GenerateRulePdf.do?fileName=9+CCR+2503-5&ruleVersionId=9918#page=61 + - title: 9 CCR 2503-5, Section 3.546(D) (effective January 1, 2023) + href: https://www.sos.state.co.us/CCR/GenerateRulePdf.do?fileName=9+CCR+2503-5&ruleVersionId=10601#page=73 + - title: 9 CCR 2503-5, Sections 3.530(B), 3.546(D) (effective March 30, 2024; 2024 amount) + href: https://www.sos.state.co.us/CCR/GenerateRulePdf.do?fileName=9+CCR+2503-5&ruleVersionId=11381#page=57 + - title: 9 CCR 2503-5, Sections 3.530(B), 3.546(D) (effective January 1, 2025) + href: https://www.sos.state.co.us/CCR/GenerateRulePdf.do?fileName=9+CCR+2503-5&ruleVersionId=11744#page=57 + - title: 9 CCR 2503-5, Sections 3.530(B), 3.546(D) (effective March 2, 2026) + href: https://www.sos.state.co.us/CCR/GenerateRulePdf.do?fileName=9+CCR+2503-5&ruleVersionId=12428#page=74 + - title: 9 CCR 2503-5, Section 3.520.79(E) + href: https://www.sos.state.co.us/CCR/GenerateRulePdf.do?fileName=9+CCR+2503-5&ruleVersionId=12428#page=74 diff --git a/policyengine_us/tests/core/test_co_ism_invariants.py b/policyengine_us/tests/core/test_co_ism_invariants.py new file mode 100644 index 00000000000..340b0f6e34f --- /dev/null +++ b/policyengine_us/tests/core/test_co_ism_invariants.py @@ -0,0 +1,241 @@ +"""Property tests: Colorado Adult Financial in-kind support and maintenance. + +9 CCR 2503-5 3.520.79 counts in-kind support and maintenance (ISM) as +unearned income of an Old Age Pension or AND-CS client. Under 3.520.79(B)(6) +and 3.520.71(E), an SSI recipient whom SSA charges ISM is charged "a matching +ISM" ("an identical ISM amount"); other clients are counted up to the maximum +ISM (3.520.79(C), $351 in 2026). + +Each case is a disabled SSI client aged 18-59 with a disability benefit and +earnings, single or married to a disabled spouse with the same arrangement, +in 2026. Each case appears twice, as a twin with no in-kind support (N) and +as one receiving it (S): in another person's household under SSI's +one-third reduction, or in their own household with outside shelter support, +optionally of a given value. A third set of people are clients aged 62 +without SSI, receiving shelter support of a drawn value. + +1. Matching (differential): an SSI recipient's ISM equals SSA's. Under the + one-third reduction this is the cut SSA made to the benefit rate, the + twins' ssi_amount_if_eligible difference; otherwise it is SSI's presumed + maximum value ISM, halved for a couple. +2. Bounds: ISM is non-negative, and an SSI recipient's is no more than the + presumed maximum value, one-third of the benefit rate plus $20. +3. No backfill: receiving in-kind support lowers SSI plus AND-CS by at least + SSA's ISM less the client's share of the $20 disregard (3.549(A)(2)) and + less any state supplementary payment that raises a grant to the $13 + floor (3.549(B)), so AND-CS restores no more of SSA's reduction than + those. +4. Payment, not value: a client without SSI who receives shelter from + others, and so pays none (3.520.79(C)(3)), is counted the $351 maximum + whatever the shelter is worth. +""" + +import numpy as np +import pytest + +from policyengine_us import Simulation + +YEAR = 2026 +PERIOD = f"{YEAR}-01" +TOLERANCE = 0.01 +GROUPS = ("tax_units", "marital_units", "spm_units", "families", "households") + + +def _add_unit(groups, key, members): + for name in GROUPS: + groups[name][f"{name}_{key}"] = {"members": members} + groups["households"][f"households_{key}"]["state_code"] = {YEAR: "CO"} + + +def _arrangement_inputs(arrangement, value): + if arrangement == "another_household": + return { + "ssi_lives_in_another_persons_household": {PERIOD: True}, + "ssi_receives_shelter_from_others_in_household": {PERIOD: True}, + "ssi_others_pay_all_meals": {PERIOD: True}, + } + inputs = {"ssi_receives_outside_shelter_support": {PERIOD: True}} + if value: + inputs["ssi_shelter_support_value"] = {YEAR: value * 12} + return inputs + + +def build_situation(cases, values): + people = {} + groups = {name: {} for name in GROUPS} + for i, c in enumerate(cases): + for twin in "NS": + key = f"{twin}{i}" + members = [] + for role in ("client", "spouse") if c["married"] else ("client",): + name = f"{role}_{key}" + people[name] = { + "age": {YEAR: c[f"{role}_age"]}, + "is_disabled": {YEAR: True}, + "meets_ssi_disability_criteria": {YEAR: True}, + "social_security_disability": {YEAR: c[f"{role}_ssdi"] * 12}, + "employment_income": {YEAR: c[f"{role}_earnings"] * 12}, + } + if twin == "S": + people[name].update( + _arrangement_inputs(c["arrangement"], c["support_value"]) + ) + members.append(name) + if c["married"]: + people[members[0]]["is_tax_unit_head"] = {YEAR: True} + people[members[1]]["is_tax_unit_spouse"] = {YEAR: True} + _add_unit(groups, key, members) + for j, value in enumerate(values): + name = f"older_{j}" + people[name] = { + "age": {YEAR: 62}, + "pension_income": {YEAR: 12_000}, + "ssi_receives_outside_shelter_support": {PERIOD: True}, + "ssi_shelter_support_value": {YEAR: value * 12}, + } + _add_unit(groups, f"older_{j}", [name]) + return {"people": people, **groups} + + +def calculate(cases, values): + simulation = Simulation(situation=build_situation(cases, values)) + names = list(simulation.populations["person"].ids) + results = {} + for variable in ( + "co_ssa_in_kind_support_and_maintenance", + "co_state_supplement", + "co_state_supplement_countable_income", + "ssi", + "ssi_amount_if_eligible", + "ssi_in_kind_support_and_maintenance", + "ssi_couple_computation_applies", + ): + period = YEAR if variable == "ssi_in_kind_support_and_maintenance" else PERIOD + values_ = np.asarray(simulation.calculate(variable, period)) + if variable == "ssi_in_kind_support_and_maintenance": + values_ = values_ / 12 + results[variable] = dict(zip(names, values_)) + p = simulation.tax_benefit_system.parameters(PERIOD) + return results, p + + +def assert_properties(cases, values): + results, p = calculate(cases, values) + ism = results["co_ssa_in_kind_support_and_maintenance"] + supplement = results["co_state_supplement"] + ssi = results["ssi"] + rate = results["ssi_amount_if_eligible"] + maximum = p.gov.states.co.ssa.ism.maximum + individual_pmv = p.gov.ssa.ssi.amount.individual / 3 + 20 + and_cs = p.gov.states.co.ssa.state_supplement + disregard = and_cs.income.disregards.unearned + countable = results["co_state_supplement_countable_income"] + for i, c in enumerate(cases): + roles = ("client", "spouse") if c["married"] else ("client",) + for role in roles: + n, s = f"{role}_N{i}", f"{role}_S{i}" + # 2. Bounds. + assert ism[n] == 0, c + assert -TOLERANCE <= ism[s] <= individual_pmv + TOLERANCE, c + if ssi[s] <= 0: + continue + # 1. Matching. + if c["arrangement"] == "another_household": + expected = rate[n] - rate[s] + else: + expected = results["ssi_in_kind_support_and_maintenance"][s] + if results["ssi_couple_computation_applies"][s]: + expected /= 2 + assert ism[s] == pytest.approx(expected, abs=TOLERANCE), c + # 3. No backfill. + share = disregard / 2 if c["married"] else disregard + grant = max(0, and_cs.grant_standard - ssi[s] - countable[s]) + floor_top_up = supplement[s] - grant if supplement[s] > 0 else 0 + drop = ssi[n] + supplement[n] - ssi[s] - supplement[s] + assert drop >= ism[s] - share - floor_top_up - TOLERANCE, c + for j, value in enumerate(values): + # 4. Payment, not value. + assert ism[f"older_{j}"] == pytest.approx(maximum, abs=TOLERANCE), value + + +def test_worked_examples(): + """co_state_supplement YAML Cases 28 and 34, monthly: a single client in + another person's household is charged 331.33 and paid $20; each member of + a couple is charged 248.50 and paid 258.50. A client without SSI who + pays nothing is counted the $351 maximum, whatever the support is + worth.""" + single = { + "married": False, + "client_age": 40, + "client_ssdi": 0, + "client_earnings": 0, + "spouse_age": 40, + "spouse_ssdi": 0, + "spouse_earnings": 0, + "arrangement": "another_household", + "support_value": 0, + } + couple = {**single, "married": True, "client_age": 50, "spouse_age": 52} + results, _ = calculate([single, couple], [0, 200, 400]) + ism = results["co_ssa_in_kind_support_and_maintenance"] + supplement = results["co_state_supplement"] + assert ism["client_S0"] == pytest.approx(331.33, abs=TOLERANCE) + assert supplement["client_S0"] == pytest.approx(20, abs=TOLERANCE) + assert ism["client_S1"] == pytest.approx(248.50, abs=TOLERANCE) + assert supplement["spouse_S1"] == pytest.approx(258.50, abs=TOLERANCE) + assert ism["older_0"] == pytest.approx(351, abs=TOLERANCE) + assert ism["older_1"] == pytest.approx(351, abs=TOLERANCE) + assert ism["older_2"] == pytest.approx(351, abs=TOLERANCE) + assert_properties([single, couple], [0, 200, 400]) + + +try: + import hypothesis + import hypothesis.strategies as st +except ImportError: # pragma: no cover + hypothesis = None + +if hypothesis is not None: + # Each example builds a Simulation; on a loaded runner input generation + # can trip the too_slow health check, which says nothing about the model. + SLOW = [hypothesis.HealthCheck.too_slow] + + def amount(high): + return st.one_of(st.just(0), st.integers(min_value=1, max_value=high)) + + @st.composite + def case(draw): + return { + "married": draw(st.booleans()), + "client_age": draw(st.integers(min_value=18, max_value=59)), + "client_ssdi": draw(amount(1_200)), + "client_earnings": draw(amount(1_500)), + "spouse_age": draw(st.integers(min_value=18, max_value=59)), + "spouse_ssdi": draw(amount(1_200)), + "spouse_earnings": draw(amount(1_500)), + "arrangement": draw(st.sampled_from(["another_household", "outside"])), + "support_value": draw(amount(500)), + } + + # Review counterexample: the supported spouse's $10 grant is raised to + # the $13 floor. + FLOOR_CASE = { + "married": True, + "client_age": 18, + "client_ssdi": 0, + "client_earnings": 1_145, + "spouse_age": 18, + "spouse_ssdi": 0, + "spouse_earnings": 106, + "arrangement": "another_household", + "support_value": 0, + } + + @hypothesis.settings(max_examples=15, deadline=None, suppress_health_check=SLOW) + @hypothesis.example(cases=[FLOOR_CASE], values=[0]) + @hypothesis.given( + st.lists(case(), min_size=1, max_size=8), + st.lists(amount(500), min_size=1, max_size=6), + ) + def test_properties(cases, values): + assert_properties(cases, values) diff --git a/policyengine_us/tests/policy/baseline/gov/states/co/ssa/co_ssa_in_kind_support_and_maintenance.yaml b/policyengine_us/tests/policy/baseline/gov/states/co/ssa/co_ssa_in_kind_support_and_maintenance.yaml new file mode 100644 index 00000000000..82670d6fa97 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/co/ssa/co_ssa_in_kind_support_and_maintenance.yaml @@ -0,0 +1,393 @@ +- name: Case 1, an SSI recipient in another person's household is charged SSA's one-third reduction. + # 9 CCR 2503-5 3.520.79(B)(6), 3.520.71(E): a matching ISM. SSA counts + # one-third of the $994 rate as income, 331.33 (20 CFR 416.1131(a)), and + # pays 994 - 331.33 = 662.67. + absolute_error_margin: 0.01 + period: 2026-01 + input: + people: + person: + age: 40 + is_disabled: true + meets_ssi_disability_criteria: true + ssi_lives_in_another_persons_household: true + ssi_receives_shelter_from_others_in_household: true + ssi_others_pay_all_meals: true + households: + household: + members: [person] + state_code: CO + output: + ssi: 662.67 + co_ssa_in_kind_support_and_maintenance: 331.33 + +- name: Case 2, an SSI recipient with outside shelter support is charged SSA's presumed maximum value. + # 994 / 3 + 20 = 351.33 (20 CFR 416.1140), matched under 3.520.79(B)(6). + absolute_error_margin: 0.01 + period: 2026-01 + input: + people: + person: + age: 40 + is_disabled: true + meets_ssi_disability_criteria: true + ssi_receives_outside_shelter_support: true + households: + household: + members: [person] + state_code: CO + output: + co_ssa_in_kind_support_and_maintenance: 351.33 + +- name: Case 3, SSA's lower actual value of shelter support is matched. + # Shelter support worth $200/month is below the 351.33 presumed maximum + # value, so SSA counts 200 and pays 994 - 180 = 814; Colorado matches 200. + absolute_error_margin: 0.01 + period: 2026-01 + input: + people: + person: + age: 40 + is_disabled: true + meets_ssi_disability_criteria: true + ssi_receives_outside_shelter_support: true + ssi_shelter_support_value: 2_400 + households: + household: + members: [person] + state_code: CO + output: + ssi: 814 + co_ssa_in_kind_support_and_maintenance: 200 + +- name: Case 4, each member of an SSI couple in another person's household is charged half the couple's reduction. + # SSA counts one-third of the $1,491 couple rate, 497, and pays + # 994 / 2 = 497 to each spouse; each spouse matches 248.50. + absolute_error_margin: 0.01 + period: 2026-01 + input: + people: + head: + age: 50 + is_disabled: true + meets_ssi_disability_criteria: true + ssi_lives_in_another_persons_household: true + ssi_receives_shelter_from_others_in_household: true + ssi_others_pay_all_meals: true + is_tax_unit_head: true + spouse: + age: 52 + is_disabled: true + meets_ssi_disability_criteria: true + ssi_lives_in_another_persons_household: true + ssi_receives_shelter_from_others_in_household: true + ssi_others_pay_all_meals: true + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [head, spouse] + marital_units: + marital_unit: + members: [head, spouse] + households: + household: + members: [head, spouse] + state_code: CO + output: + ssi: [497, 497] + co_ssa_in_kind_support_and_maintenance: [248.5, 248.5] + +- name: Case 5, each member of an SSI couple with outside shelter support is charged half the couple's presumed maximum value. + # 1,491 / 3 + 20 = 517 for the couple, 258.50 each. + absolute_error_margin: 0.01 + period: 2026-01 + input: + people: + head: + age: 50 + is_disabled: true + meets_ssi_disability_criteria: true + ssi_receives_outside_shelter_support: true + is_tax_unit_head: true + spouse: + age: 52 + is_disabled: true + meets_ssi_disability_criteria: true + ssi_receives_outside_shelter_support: true + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [head, spouse] + marital_units: + marital_unit: + members: [head, spouse] + households: + household: + members: [head, spouse] + state_code: CO + output: + co_ssa_in_kind_support_and_maintenance: [258.5, 258.5] + +- name: Case 6, a client without SSI who pays no shelter costs is counted the maximum ISM. + # Aged 62 and not disabled, so not on SSI. 3.520.79(C)(3): "the current + # maximum ISM amount is counted", $351 in 2026 (3.530(B)). + period: 2026-01 + input: + people: + person: + age: 62 + pension_income: 6_000 + ssi_lives_in_another_persons_household: true + ssi_receives_shelter_from_others_in_household: true + ssi_others_pay_all_meals: true + households: + household: + members: [person] + state_code: CO + output: + ssi: 0 + co_ssa_in_kind_support_and_maintenance: 351 + +- name: Case 7, a client without SSI who pays nothing is counted the maximum whatever the shelter is worth. + # Others supply shelter worth $200/month in full. 3.520.79(C)(3) counts + # "the current maximum ISM amount", $351, for a client "paying no shelter + # costs"; the value of the shelter does not enter Colorado's calculation. + period: 2026-01 + input: + people: + person: + age: 62 + pension_income: 6_000 + ssi_receives_outside_shelter_support: true + ssi_shelter_support_value: 2_400 + households: + household: + members: [person] + state_code: CO + output: + co_ssa_in_kind_support_and_maintenance: 351 + +- name: Case 8, a client without SSI in subsidized housing is not charged ISM. + # 3.520.79(B)(2). + period: 2026-01 + input: + people: + person: + age: 62 + pension_income: 6_000 + ssi_receives_outside_shelter_support: true + spm_units: + spm_unit: + members: [person] + receives_housing_assistance: true + households: + household: + members: [person] + state_code: CO + output: + co_ssa_in_kind_support_and_maintenance: 0 + +- name: Case 9, a client without SSI who owns their home is not charged ISM. + # 3.520.79(B)(1). + period: 2026-01 + input: + people: + person: + age: 62 + pension_income: 6_000 + ssi_receives_outside_shelter_support: true + households: + household: + members: [person] + state_code: CO + tenure_type: OWNED_OUTRIGHT + output: + co_ssa_in_kind_support_and_maintenance: 0 + +- name: Case 10, an SSI recipient who owns their home is charged SSA's ISM anyway. + # 3.520.79(B)(5)-(6): the exemptions apply "unless" SSA charges an ISM, in + # which case the client is charged a matching ISM, 351.33. + absolute_error_margin: 0.01 + period: 2026-01 + input: + people: + person: + age: 40 + is_disabled: true + meets_ssi_disability_criteria: true + ssi_receives_outside_shelter_support: true + households: + household: + members: [person] + state_code: CO + tenure_type: OWNED_OUTRIGHT + output: + co_ssa_in_kind_support_and_maintenance: 351.33 + +- name: Case 11, a child whom SSI deems from parents is not charged ISM. + # A disabled child living with a parent who has no income: SSI deems + # instead of counting in-kind support (20 CFR 416.1165), so SSA charges + # no ISM and Colorado matches none. + period: 2026-01 + input: + people: + parent: + age: 40 + own_children_in_household: 1 + is_tax_unit_head: true + child: + age: 10 + is_disabled: true + meets_ssi_disability_criteria: true + ssi_receives_shelter_from_others_in_household: true + is_tax_unit_dependent: true + tax_units: + tax_unit: + members: [parent, child] + households: + household: + members: [parent, child] + state_code: CO + output: + ssi: [0, 994] + co_ssa_in_kind_support_and_maintenance: [0, 0] + +- name: Case 12, no shelter support, no ISM. + period: 2026-01 + input: + people: + person: + age: 40 + is_disabled: true + meets_ssi_disability_criteria: true + households: + household: + members: [person] + state_code: CO + output: + co_ssa_in_kind_support_and_maintenance: 0 + +- name: Case 13, before the matching rule, January 2014 counts Colorado's maximum. + # 9 CCR 2503-2 3.250.22 caps ISM at the shelter component, $260 from + # January 1, 2014 (9 CCR 2503-4 3.460.45); the client pays no shelter + # costs, so the full $260 is counted. + period: 2014-01 + input: + people: + person: + age: 40 + is_disabled: true + meets_ssi_disability_criteria: true + ssi_lives_in_another_persons_household: true + ssi_receives_shelter_from_others_in_household: true + ssi_others_pay_all_meals: true + households: + household: + members: [person] + state_code: CO + output: + co_ssa_in_kind_support_and_maintenance: 260 + +- name: Case 14, the matching rule takes effect in March 2014. + # January and February 2014 count Colorado's $260 maximum (Case 13). From + # March, 3.520.79(D) (effective March 2, 2014) matches SSA's one-third + # reduction, one-third of the $721 rate, 240.33/month. For the year: + # 2 x 260 + 10 x 240.33 = 2,923.33. + absolute_error_margin: 0.1 + period: 2014 + input: + people: + person: + age: 40 + is_disabled: true + meets_ssi_disability_criteria: true + ssi_lives_in_another_persons_household: true + ssi_receives_shelter_from_others_in_household: true + ssi_others_pay_all_meals: true + households: + household: + members: [person] + state_code: CO + output: + co_ssa_in_kind_support_and_maintenance: 2_923.33 + +- name: Case 15, before 2014 free shelter and food count their grant standard components. + # 3.250.22, 3.250.222 and 3.460.45 (components effective 2007): shelter + # and utilities 36.6% and food 20.5% of the $698 standard, + # 255.47 + 143.09 = 398.56. + absolute_error_margin: 0.01 + period: 2012-01 + input: + people: + person: + age: 40 + is_disabled: true + meets_ssi_disability_criteria: true + ssi_lives_in_another_persons_household: true + ssi_receives_shelter_from_others_in_household: true + ssi_others_pay_all_meals: true + households: + household: + members: [person] + state_code: CO + output: + co_ssa_in_kind_support_and_maintenance: 398.56 + +- name: Case 16, before 2014 shelter support alone counts the shelter component. + # 36.6% of $698 = 255.47; food is not supplied in full. + absolute_error_margin: 0.01 + period: 2012-01 + input: + people: + person: + age: 40 + is_disabled: true + meets_ssi_disability_criteria: true + ssi_receives_outside_shelter_support: true + households: + household: + members: [person] + state_code: CO + output: + co_ssa_in_kind_support_and_maintenance: 255.47 + +- name: Case 17, the 2007 components. + # Shelter and utilities 36.6% and food 20.5% of the $648 standard + # (components effective January 1, 2007): 237.17 + 132.84 = 370.01. + absolute_error_margin: 0.01 + period: 2007-01 + input: + people: + person: + age: 40 + is_disabled: true + meets_ssi_disability_criteria: true + ssi_lives_in_another_persons_household: true + ssi_receives_shelter_from_others_in_household: true + ssi_others_pay_all_meals: true + households: + household: + members: [person] + state_code: CO + output: + co_ssa_in_kind_support_and_maintenance: 370.01 + +- name: Case 18, room and board during medical confinement is not counted. + # A client without SSI in a hospital whose room and board Medicaid pays. + # 3.520.786(B) exempts "room and board furnished during medical + # confinement and paid for by a third party". + period: 2026-01 + input: + people: + person: + age: 62 + pension_income: 6_000 + ssi_receives_outside_shelter_support: true + ssi_lives_in_medical_treatment_facility: true + ssi_medicaid_pays_majority_of_care: true + households: + household: + members: [person] + state_code: CO + output: + co_ssa_in_kind_support_and_maintenance: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/states/co/ssa/state_supplement/co_state_supplement.yaml b/policyengine_us/tests/policy/baseline/gov/states/co/ssa/state_supplement/co_state_supplement.yaml index 35c11d8a0ef..6a7b9292292 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/co/ssa/state_supplement/co_state_supplement.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/co/ssa/state_supplement/co_state_supplement.yaml @@ -480,10 +480,10 @@ co_state_supplement: 0 - name: Case 28, a single SSI recipient in another person's household. - # SSI pays two-thirds of the $994 rate, 662.67/month (20 CFR 416.1131). - # With no countable income, the grant is 994 - 662.67 = 331.33/month, or - # 3,976/year. SSA's Colorado tables describe the supplement as increased to - # offset the reduced federal payment. + # SSI counts one-third of the $994 rate, 331.33, as income and pays 662.67 + # (20 CFR 416.1131). 3.520.79(B)(6) charges a matching ISM of 331.33, + # counted as unearned income less the $20 disregard (3.549(A)(2)): 311.33. + # Grant 994 - 662.67 - 311.33 = 20/month, or 240/year. absolute_error_margin: 0.1 period: 2026 input: @@ -501,7 +501,9 @@ state_code: CO output: ssi: 7_952 - co_state_supplement: 3_976 + co_ssa_in_kind_support_and_maintenance: 3_976 + co_state_supplement_countable_income: 3_736 + co_state_supplement: 240 - name: Case 29, a client whose working spouse is on Medicaid. # The client has no income; the spouse is not disabled, earns $1,500/month @@ -630,3 +632,138 @@ state_code: CO output: co_state_supplement: 0 + +- name: Case 33, in another person's household with other unearned income. + # $100/month of SSDI: SSI 662.67 - (100 - 20) = 582.67. Colorado counts + # 100 + 331.33 - 20 = 411.33, so total countable income is + # 582.67 + 411.33 = 994, the grant standard: not eligible (3.520.781(A)). + absolute_error_margin: 0.1 + period: 2026 + input: + people: + person: + age: 40 + is_disabled: true + meets_ssi_disability_criteria: true + social_security_disability: 1_200 + ssi_lives_in_another_persons_household: true + ssi_receives_shelter_from_others_in_household: true + ssi_others_pay_all_meals: true + households: + household: + members: [person] + state_code: CO + output: + ssi: 6_992 + co_state_supplement_countable_income: 4_936 + co_state_supplement: 0 + +- name: Case 34, an SSI couple in another person's household. + # SSI counts one-third of the $1,491 couple rate, 497, and pays 497 to each + # spouse. Each spouse matches half the couple's ISM, 248.50, less their $10 + # share of the disregard: 238.50. Grant 994 - 497 - 238.50 = 258.50/month + # (3,102/year) each. + absolute_error_margin: 0.1 + period: 2026 + input: + people: + head: + age: 50 + is_disabled: true + meets_ssi_disability_criteria: true + ssi_lives_in_another_persons_household: true + ssi_receives_shelter_from_others_in_household: true + ssi_others_pay_all_meals: true + is_tax_unit_head: true + spouse: + age: 52 + is_disabled: true + meets_ssi_disability_criteria: true + ssi_lives_in_another_persons_household: true + ssi_receives_shelter_from_others_in_household: true + ssi_others_pay_all_meals: true + is_tax_unit_spouse: true + tax_units: + tax_unit: + members: [head, spouse] + marital_units: + marital_unit: + members: [head, spouse] + households: + household: + members: [head, spouse] + state_code: CO + output: + ssi: [5_964, 5_964] + co_state_supplement_countable_income: [2_862, 2_862] + co_state_supplement: [3_102, 3_102] + +- name: Case 35, before 2014 free shelter and food leave no grant. + # 2012: SSI pays two-thirds of $698, 465.33. 9 CCR 2503-2 3.250.22, + # 3.250.222 count shelter and utilities (36.6%) and food (20.5%) of the + # $698 standard, 398.56, less $20: 378.56. 698 - 465.33 - 378.56 < 0. + absolute_error_margin: 0.1 + period: 2012 + input: + people: + person: + age: 40 + is_disabled: true + meets_ssi_disability_criteria: true + ssi_lives_in_another_persons_household: true + ssi_receives_shelter_from_others_in_household: true + ssi_others_pay_all_meals: true + households: + household: + members: [person] + state_code: CO + output: + ssi: 5_584 + co_ssa_in_kind_support_and_maintenance: 4_782.7 + co_state_supplement: 0 + +- name: Case 36, 2014 changes from Colorado's maximum ISM to SSA's in March. + # SSI pays two-thirds of $721, 480.67. January and February count the $260 + # maximum, less $20: grant 721 - 480.67 - 240 = 0.33. From March the + # matching ISM is 240.33, less $20: grant 721 - 480.67 - 220.33 = 20. + # For the year: 2 x 0.33 + 10 x 20 = 200.67. There is no payment floor + # before 2026. + absolute_error_margin: 0.1 + period: 2014 + input: + people: + person: + age: 40 + is_disabled: true + meets_ssi_disability_criteria: true + ssi_lives_in_another_persons_household: true + ssi_receives_shelter_from_others_in_household: true + ssi_others_pay_all_meals: true + households: + household: + members: [person] + state_code: CO + output: + co_state_supplement: 200.67 + +- name: Case 37, 2025 in another person's household. + # SSI pays two-thirds of $967, 644.67; the matching ISM 322.33 less $20 is + # 302.33. Grant 967 - 644.67 - 302.33 = 20/month, or 240/year. + absolute_error_margin: 0.1 + period: 2025 + input: + people: + person: + age: 40 + is_disabled: true + meets_ssi_disability_criteria: true + ssi_lives_in_another_persons_household: true + ssi_receives_shelter_from_others_in_household: true + ssi_others_pay_all_meals: true + households: + household: + members: [person] + state_code: CO + output: + ssi: 7_736 + co_state_supplement: 240 diff --git a/policyengine_us/variables/gov/states/co/ssa/co_ssa_in_kind_support_and_maintenance.py b/policyengine_us/variables/gov/states/co/ssa/co_ssa_in_kind_support_and_maintenance.py new file mode 100644 index 00000000000..042bf679e81 --- /dev/null +++ b/policyengine_us/variables/gov/states/co/ssa/co_ssa_in_kind_support_and_maintenance.py @@ -0,0 +1,132 @@ +from policyengine_us.model_api import * +from policyengine_us.variables.gov.ssa.ssi.eligibility.status.ssi_federal_living_arrangement import ( + SSIFederalLivingArrangement, +) +from policyengine_us.variables.household.expense.housing.tenure_type import ( + TenureType, +) + + +def _ism_exempt(person, period): + """No ISM for a client who resides in and owns their home, receives + subsidized housing or is homeless (9 CCR 2503-5 3.520.79(B)(1)-(3); + 9 CCR 2503-1 3.250.22(A), (B), (G) before 2014), or whose room and board + is furnished during medical confinement: exempt income when a third + party pays, and not in-kind support when the client does (3.520.786(B); + 3.250.32 before 2014).""" + tenure = person.household("tenure_type", period.this_year) + owned = (tenure == TenureType.OWNED_OUTRIGHT) | ( + tenure == TenureType.OWNED_WITH_MORTGAGE + ) + in_another_household = person("ssi_lives_in_another_persons_household", period) + owns_home = owned & ~in_another_household + subsidized = person.spm_unit("receives_housing_assistance", period.this_year) + homeless = person.household("is_homeless", period.this_year) + medical_confinement = person("ssi_lives_in_medical_treatment_facility", period) + return owns_home | subsidized | homeless | medical_confinement + + +def _colorado_shelter_ism(person, period, maximum): + """ISM for shelter as Colorado computes it. + + 3.520.79(A), (C) (3.250.22 and 3.250.221 before 2014). Colorado's amount + depends on what the client pays, not on what the shelter is worth: the + maximum less the client's payment ((C)(1)), the lesser of the fair share + and the maximum less the payment ((C)(2)), or, for a client "paying no + shelter costs", the maximum ((C)(3)). PolicyEngine has no input for the + client's payment, the fair share or the market value, so a client who + receives shelter from others is taken to pay none and is counted the + maximum. + """ + receives_shelter = person( + "ssi_receives_shelter_from_others_in_household", period + ) | person("ssi_receives_outside_shelter_support", period) + return where(receives_shelter & ~_ism_exempt(person, period), maximum, 0) + + +def _ssa_ism(person, period, parameters): + """The in-kind support and maintenance SSA counts for an SSI recipient. + + Under the presumed maximum value rule (20 CFR 416.1140) this is + ssi_in_kind_support_and_maintenance, which holds a couple's presumed + maximum value for each spouse; SSI splits it equally. Under the + one-third reduction rule SSA counts "one-third of the Federal benefit + rate as additional income" (20 CFR 416.1131(a)) for the individual, the + couple or the individual with a deemed spouse; ssi_amount_if_eligible is + the rate after that reduction. + """ + pmv_ism = ( + person("ssi_in_kind_support_and_maintenance", period.this_year) / MONTHS_IN_YEAR + ) + couple_computation = person("ssi_couple_computation_applies", period) + pmv_ism = where(couple_computation, pmv_ism / 2, pmv_ism) + arrangement = person("ssi_federal_living_arrangement", period) + one_third_reduction_applies = ( + arrangement == SSIFederalLivingArrangement.ANOTHER_PERSONS_HOUSEHOLD + ) + rate = parameters(period).gov.ssa.ssi.amount.one_third_reduction_rate + reduced_rate = person("ssi_amount_if_eligible", period) + one_third = reduced_rate * rate / (1 - rate) + return where(one_third_reduction_applies, one_third, pmv_ism) + + +class co_ssa_in_kind_support_and_maintenance(Variable): + value_type = float + entity = Person + label = "Colorado Adult Financial in-kind support and maintenance" + documentation = "In-kind support and maintenance that Colorado counts as unearned income of an Old Age Pension or Aid to the Needy Disabled-Colorado Supplement client." + unit = USD + definition_period = MONTH + defined_for = StateCode.CO + reference = ( + # 3.520.71(E), PDF page 44; 3.520.79, PDF pages 72-74 + "https://www.sos.state.co.us/CCR/GenerateRulePdf.do?fileName=9+CCR+2503-5&ruleVersionId=12428#page=72", + "https://www.sos.state.co.us/CCR/GenerateRulePdf.do?fileName=9+CCR+2503-5&ruleVersionId=12428#page=44", + # 3.520.786(B), PDF page 71 + "https://www.sos.state.co.us/CCR/GenerateRulePdf.do?fileName=9+CCR+2503-5&ruleVersionId=12428#page=71", + # 3.250.22, 3.250.222, 3.250.32 before 2014 + "https://www.sos.state.co.us/CCR/GenerateRulePdf.do?fileName=9+CCR+2503-2&ruleVersionId=4793#page=30", + "https://www.sos.state.co.us/CCR/GenerateRulePdf.do?fileName=9+CCR+2503-4&ruleVersionId=4795#page=13", + ) + + def formula_2007(person, period, parameters): + # 9 CCR 2503-1 3.250.22 (9 CCR 2503-2 from September 2012): ISM for + # shelter is capped at the shelter and utility component of the + # grant standard, and 3.250.222 counts food supplied in full at its + # component (3.460.43, 3.460.45). PolicyEngine models OAP only from + # 2023, so the AND-CS standard's components are used. The rule + # counted ISM before 2007 too, but the table effective January 1, + # 2004 prints a $513 AND/SSI-CS maximum grant where the grant + # standard is $601, so the base for 2002-2006 is unsettled and those + # years are not modeled. + p = parameters(period).gov.states.co.ssa + standard = p.state_supplement.grant_standard + share = p.ism.component_share + shelter = _colorado_shelter_ism(person, period, share.shelter * standard) + food_supplied_in_full = person("ssi_others_pay_all_meals", period) + food = where( + food_supplied_in_full & ~_ism_exempt(person, period), + share.food * standard, + 0, + ) + return shelter + food + + def formula_2014(person, period, parameters): + # From January 1, 2014 the shelter cap is one-third of the SSI + # benefit standard plus $20 (9 CCR 2503-4 3.460.45), and food is no + # longer counted. + maximum = parameters(period).gov.states.co.ssa.ism.maximum + return _colorado_shelter_ism(person, period, maximum) + + def formula_2014_03(person, period, parameters): + # 3.520.79(B)(6) and 3.520.71(E), effective March 2, 2014 and applied + # from the March 2014 month: an SSI recipient whom SSA charges ISM is + # charged "a matching ISM" ("an identical ISM amount"). With + # PolicyEngine's inputs SSA charges ISM whenever 3.520.79(C) would, + # except to a child in the parental household (whom SSI deems + # instead) or a medical facility resident, so SSI recipients are + # charged SSA's ISM. Other clients' ISM follows 3.520.79(C). + maximum = parameters(period).gov.states.co.ssa.ism.maximum + colorado = _colorado_shelter_ism(person, period, maximum) + receives_ssi = (person("ssi", period) > 0) | person("receives_ssi", period) + return where(receives_ssi, _ssa_ism(person, period, parameters), colorado) diff --git a/policyengine_us/variables/gov/states/co/ssa/state_supplement/_and_cs_client_income.py b/policyengine_us/variables/gov/states/co/ssa/state_supplement/_and_cs_client_income.py index 7e7fd6bc2b7..8532d1b55ee 100644 --- a/policyengine_us/variables/gov/states/co/ssa/state_supplement/_and_cs_client_income.py +++ b/policyengine_us/variables/gov/states/co/ssa/state_supplement/_and_cs_client_income.py @@ -16,13 +16,8 @@ def and_cs_client_income(person, period, parameters): """ p = parameters(period).gov.states.co.ssa.state_supplement.income.disregards earned = person("ssi_earned_income", period) - # A joint couple's in-kind support and maintenance is one presumed - # maximum value for the couple (ssi_pmv_amount), which SSI splits - # equally between the spouses. 3.520.71(E) and 3.520.79(B)(6) charge an - # SSI recipient the same ISM that SSA charges. - ism = person("ssi_in_kind_support_and_maintenance", period) - couple_computation = person("ssi_couple_computation_applies", period) - ism = where(couple_computation, ism / 2, ism) + # 3.520.79 counts in-kind support and maintenance as unearned income. + ism = person("co_ssa_in_kind_support_and_maintenance", period) unearned = person("ssi_unearned_income", period) + ism ssi = person("ssi", period) # 3.549(A)(2)(c)(1): a married client's $20 disregard is split with the