diff --git a/changelog_entry.yaml b/changelog_entry.yaml index e69de29bb2d..77948dcca8b 100644 --- a/changelog_entry.yaml +++ b/changelog_entry.yaml @@ -0,0 +1,8 @@ +- bump: minor + changes: + added: + - State-specific Medicaid takeup rates calibrated against administrative enrollment data. Rates range from 53% (Utah) to 99% (Colorado), replacing the uniform 93% national rate. + - Deprivation requirement parameter for Medicaid parent eligibility in non-expansion states (TX, FL, GA, etc.). + - is_single_parent_household variable to determine if a person qualifies under Section 1931 deprivation rules. + changed: + - Medicaid parent eligibility (is_parent_for_medicaid_nfc) now checks deprivation requirement for non-expansion states, correctly excluding married parents who don't meet Section 1931 criteria. diff --git a/policyengine_us/parameters/gov/hhs/medicaid/eligibility/categories/parent/requires_deprivation.yaml b/policyengine_us/parameters/gov/hhs/medicaid/eligibility/categories/parent/requires_deprivation.yaml new file mode 100644 index 00000000000..dc3f48d8ef8 --- /dev/null +++ b/policyengine_us/parameters/gov/hhs/medicaid/eligibility/categories/parent/requires_deprivation.yaml @@ -0,0 +1,123 @@ +description: > + Whether the state requires a child to be "deprived of parental support" + for parent Medicaid eligibility. This is based on Section 1931 criteria + which applies to non-expansion states using pre-ACA AFDC rules. + In practice, this means two-parent households generally do not qualify + unless one parent is incapacitated or unemployed. +metadata: + label: Medicaid parent eligibility requires deprivation + unit: bool + period: year + breakdown: + - state_code + reference: + - title: Section 1931 Medicaid Eligibility + href: https://www.law.cornell.edu/uscode/text/42/1396u-1 + - title: KFF - Medicaid Eligibility for Parents + href: https://www.kff.org/medicaid/state-indicator/medicaid-income-eligibility-limits-for-parents/ +# Non-expansion states that use Section 1931 deprivation rules +# These states did NOT expand Medicaid and have very restrictive parent eligibility +AL: + 2018-01-01: true +FL: + 2018-01-01: true +GA: + 2018-01-01: true +KS: + 2018-01-01: true +MS: + 2018-01-01: true +NC: + 2018-01-01: true + 2023-12-01: false # NC expanded Medicaid +SC: + 2018-01-01: true +SD: + 2018-01-01: true +TN: + 2018-01-01: true +TX: + 2018-01-01: true +WI: + 2018-01-01: true # WI has unusual partial expansion but keeps deprivation +WY: + 2018-01-01: true +# Expansion states - no deprivation requirement +AK: + 2018-01-01: false +AR: + 2018-01-01: false +AZ: + 2018-01-01: false +CA: + 2018-01-01: false +CO: + 2018-01-01: false +CT: + 2018-01-01: false +DC: + 2018-01-01: false +DE: + 2018-01-01: false +HI: + 2018-01-01: false +IA: + 2018-01-01: false +ID: + 2018-01-01: false +IL: + 2018-01-01: false +IN: + 2018-01-01: false +KY: + 2018-01-01: false +LA: + 2018-01-01: false +MA: + 2018-01-01: false +MD: + 2018-01-01: false +ME: + 2018-01-01: false +MI: + 2018-01-01: false +MN: + 2018-01-01: false +MO: + 2018-01-01: false +MT: + 2018-01-01: false +ND: + 2018-01-01: false +NE: + 2018-01-01: false +NH: + 2018-01-01: false +NJ: + 2018-01-01: false +NM: + 2018-01-01: false +NV: + 2018-01-01: false +NY: + 2018-01-01: false +OH: + 2018-01-01: false +OK: + 2018-01-01: false +OR: + 2018-01-01: false +PA: + 2018-01-01: false +RI: + 2018-01-01: false +UT: + 2018-01-01: false +VA: + 2018-01-01: false +VT: + 2018-01-01: false +WA: + 2018-01-01: false +WV: + 2018-01-01: false diff --git a/policyengine_us/parameters/gov/hhs/medicaid/takeup_rate.yaml b/policyengine_us/parameters/gov/hhs/medicaid/takeup_rate.yaml index 1a3a6db93a2..1c38156ad8d 100644 --- a/policyengine_us/parameters/gov/hhs/medicaid/takeup_rate.yaml +++ b/policyengine_us/parameters/gov/hhs/medicaid/takeup_rate.yaml @@ -1,12 +1,116 @@ description: Percentage of people who do enroll in Medicaid, if eligible. -values: - 2018-01-01: 0.93 metadata: label: Medicaid takeup rate unit: /1 period: year economy: false household: false + breakdown: + - state_code reference: - title: KFF "A Closer Look at the Remaining Uninsured Population Eligible for Medicaid and CHIP" - href: https://www.kff.org/uninsured/issue-brief/a-closer-look-at-the-remaining-uninsured-population-eligible-for-medicaid-and-chip/#:~:text=the%20uninsured%20rate%20dropped%20to,States%20began%20the + href: https://www.kff.org/uninsured/issue-brief/a-closer-look-at-the-remaining-uninsured-population-eligible-for-medicaid-and-chip/ + - title: State-specific rates derived from MACPAC enrollment targets vs modeled eligibility + href: https://www.medicaid.gov/medicaid/program-information/medicaid-and-chip-enrollment-data/report-highlights/index.html +AK: + 2018-01-01: 0.88 +AL: + 2018-01-01: 0.92 +AR: + 2018-01-01: 0.79 +AZ: + 2018-01-01: 0.95 +CA: + 2018-01-01: 0.78 +CO: + 2018-01-01: 0.99 +CT: + 2018-01-01: 0.89 +DC: + 2018-01-01: 0.99 +DE: + 2018-01-01: 0.86 +FL: + 2018-01-01: 0.98 +GA: + 2018-01-01: 0.73 +HI: + 2018-01-01: 0.88 +IA: + 2018-01-01: 0.84 +ID: + 2018-01-01: 0.78 +IL: + 2018-01-01: 0.85 +IN: + 2018-01-01: 0.99 +KS: + 2018-01-01: 0.92 +KY: + 2018-01-01: 0.87 +LA: + 2018-01-01: 0.79 +MA: + 2018-01-01: 0.94 +MD: + 2018-01-01: 0.95 +ME: + 2018-01-01: 0.92 +MI: + 2018-01-01: 0.91 +MN: + 2018-01-01: 0.89 +MO: + 2018-01-01: 0.89 +MS: + 2018-01-01: 0.75 +MT: + 2018-01-01: 0.83 +NC: + 2018-01-01: 0.94 +ND: + 2018-01-01: 0.91 +NE: + 2018-01-01: 0.79 +NH: + 2018-01-01: 0.84 +NJ: + 2018-01-01: 0.74 +NM: + 2018-01-01: 0.84 +NV: + 2018-01-01: 0.93 +NY: + 2018-01-01: 0.86 +OH: + 2018-01-01: 0.82 +OK: + 2018-01-01: 0.77 +OR: + 2018-01-01: 0.92 +PA: + 2018-01-01: 0.64 +RI: + 2018-01-01: 0.94 +SC: + 2018-01-01: 0.93 +SD: + 2018-01-01: 0.88 +TN: + 2018-01-01: 0.92 +TX: + 2018-01-01: 0.76 +UT: + 2018-01-01: 0.53 +VA: + 2018-01-01: 0.82 +VT: + 2018-01-01: 0.93 +WA: + 2018-01-01: 0.98 +WI: + 2018-01-01: 0.91 +WV: + 2018-01-01: 0.83 +WY: + 2018-01-01: 0.70 diff --git a/policyengine_us/tests/policy/baseline/gov/hhs/medicaid/takes_up_medicaid_if_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/hhs/medicaid/takes_up_medicaid_if_eligible.yaml index 0ec73681c48..4cfdeab1f8e 100644 --- a/policyengine_us/tests/policy/baseline/gov/hhs/medicaid/takes_up_medicaid_if_eligible.yaml +++ b/policyengine_us/tests/policy/baseline/gov/hhs/medicaid/takes_up_medicaid_if_eligible.yaml @@ -1,6 +1,7 @@ - name: Seed above takeup rate period: 2025 input: + state_code: CA medicaid_take_up_seed: 1 output: takes_up_medicaid_if_eligible: false @@ -8,6 +9,7 @@ - name: Seed below takeup rate period: 2025 input: - medicaid_take_up_seed: 0.9 + state_code: CA + medicaid_take_up_seed: 0.5 output: takes_up_medicaid_if_eligible: true diff --git a/policyengine_us/tests/policy/baseline/gov/ssa/ssi/eligibility/test_meets_ssi_resource_test.py b/policyengine_us/tests/policy/baseline/gov/ssa/ssi/eligibility/test_meets_ssi_resource_test.py new file mode 100644 index 00000000000..03820515966 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/ssa/ssi/eligibility/test_meets_ssi_resource_test.py @@ -0,0 +1,25 @@ +from policyengine_us import Simulation + + +def test_meets_ssi_resource_test_microsim_branch(): + sim = Simulation( + situation={ + "people": { + "person1": { + "ssi_resource_test_seed": {"2024": 0.1}, + }, + "person2": { + "ssi_resource_test_seed": {"2024": 0.9}, + }, + }, + "households": { + "household1": { + "members": ["person1", "person2"], + }, + }, + } + ) + sim.dataset = "mock" + result = sim.calculate("meets_ssi_resource_test", "2024") + assert result[0] == True # 0.1 < 0.4 pass_rate + assert result[1] == False # 0.9 >= 0.4 pass_rate diff --git a/policyengine_us/variables/gov/hhs/medicaid/eligibility/categories/parent/is_parent_for_medicaid_nfc.py b/policyengine_us/variables/gov/hhs/medicaid/eligibility/categories/parent/is_parent_for_medicaid_nfc.py index 31348017c29..6b4807aef81 100644 --- a/policyengine_us/variables/gov/hhs/medicaid/eligibility/categories/parent/is_parent_for_medicaid_nfc.py +++ b/policyengine_us/variables/gov/hhs/medicaid/eligibility/categories/parent/is_parent_for_medicaid_nfc.py @@ -6,10 +6,40 @@ class is_parent_for_medicaid_nfc(Variable): entity = Person label = "Medicaid parent non-financial criteria" definition_period = YEAR + documentation = """ + Determines if a person meets the non-financial criteria for Medicaid + parent eligibility. + + In expansion states: Any adult who is not a dependent and has dependents + in their tax unit qualifies. + + In non-expansion states (Section 1931 states): The state requires a child + to be "deprived of parental support" which typically means single-parent + households only. Two-parent households generally do not qualify unless + one parent is incapacitated or unemployed. + + References: + - 42 USC 1396u-1 (Section 1931) + - https://www.kff.org/medicaid/state-indicator/medicaid-income-eligibility-limits-for-parents/ + """ def formula(person, period, parameters): is_dependent = person("is_tax_unit_dependent", period) has_dependent_in_tax_unit = ( person.tax_unit("tax_unit_count_dependents", period) > 0 ) - return ~is_dependent & has_dependent_in_tax_unit + + # Basic criteria: not a dependent AND has dependents + meets_basic_criteria = ~is_dependent & has_dependent_in_tax_unit + + # Check if state requires deprivation (non-expansion states) + state = person.household("state_code_str", period) + p = parameters(period).gov.hhs.medicaid.eligibility.categories.parent + requires_deprivation = p.requires_deprivation[state] + + # For states requiring deprivation, must be single parent + is_single_parent = person("is_single_parent_household", period) + requires_deprivation_bool = requires_deprivation.astype(bool) + meets_deprivation = ~requires_deprivation_bool | is_single_parent + + return meets_basic_criteria & meets_deprivation diff --git a/policyengine_us/variables/gov/hhs/medicaid/eligibility/categories/parent/is_single_parent_household.py b/policyengine_us/variables/gov/hhs/medicaid/eligibility/categories/parent/is_single_parent_household.py new file mode 100644 index 00000000000..4c3259e8ac4 --- /dev/null +++ b/policyengine_us/variables/gov/hhs/medicaid/eligibility/categories/parent/is_single_parent_household.py @@ -0,0 +1,39 @@ +from policyengine_us.model_api import * + + +class is_single_parent_household(Variable): + value_type = bool + entity = Person + label = "Person is in a single-parent household (for Medicaid deprivation)" + definition_period = YEAR + documentation = """ + Determines if a person is a single parent for purposes of Medicaid + Section 1931 eligibility in non-expansion states. + + Under Section 1931, states that did not expand Medicaid require a child + to be "deprived of parental support" for the parent to qualify. This + typically means single-parent households qualify, while two-parent + households generally do not (unless one parent is incapacitated/unemployed). + + This variable returns True for: + - Head of Household filers with dependents (clearly single parents) + - Single filers with dependents + + It returns False for: + - Joint filers (married couples) + - Married filing separately (still married) + """ + + def formula(person, period, parameters): + filing_status = person.tax_unit("filing_status", period) + is_head = person("is_tax_unit_head", period) + has_dependents = ( + person.tax_unit("tax_unit_count_dependents", period) > 0 + ) + + # Single parent = not married AND has dependents AND is the head + is_joint = filing_status == filing_status.possible_values.JOINT + is_separate = filing_status == filing_status.possible_values.SEPARATE + is_married_filing = is_joint | is_separate + + return ~is_married_filing & has_dependents & is_head diff --git a/policyengine_us/variables/gov/hhs/medicaid/takes_up_medicaid_if_eligible.py b/policyengine_us/variables/gov/hhs/medicaid/takes_up_medicaid_if_eligible.py index c1497f24557..fa37cb21418 100644 --- a/policyengine_us/variables/gov/hhs/medicaid/takes_up_medicaid_if_eligible.py +++ b/policyengine_us/variables/gov/hhs/medicaid/takes_up_medicaid_if_eligible.py @@ -4,10 +4,11 @@ class takes_up_medicaid_if_eligible(Variable): value_type = bool entity = Person - label = "Whether a random eligible person unit does not enroll in Medicaid" + label = "Whether an eligible person takes up Medicaid" definition_period = YEAR def formula(person, period, parameters): seed = person("medicaid_take_up_seed", period) - takeup_rate = parameters(period).gov.hhs.medicaid.takeup_rate + state = person.household("state_code_str", period) + takeup_rate = parameters(period).gov.hhs.medicaid.takeup_rate[state] return seed < takeup_rate diff --git a/policyengine_us/variables/gov/ssa/ssi/eligibility/resources/meets_ssi_resource_test.py b/policyengine_us/variables/gov/ssa/ssi/eligibility/resources/meets_ssi_resource_test.py index 3d25cfee08e..a5ef2745541 100644 --- a/policyengine_us/variables/gov/ssa/ssi/eligibility/resources/meets_ssi_resource_test.py +++ b/policyengine_us/variables/gov/ssa/ssi/eligibility/resources/meets_ssi_resource_test.py @@ -14,7 +14,8 @@ def formula(person, period, parameters): # Apply policy logic in individual simulation. p = parameters(period).gov.ssa.ssi if person.simulation.dataset is not None: - return random(person) < p.eligibility.resources.pass_rate + seed = person("ssi_resource_test_seed", period) + return seed < p.eligibility.resources.pass_rate joint_claim = person("ssi_claim_is_joint", period) personal_resources = person("ssi_countable_resources", period) countable_resources = where( diff --git a/policyengine_us/variables/gov/ssa/ssi/eligibility/resources/ssi_resource_test_seed.py b/policyengine_us/variables/gov/ssa/ssi/eligibility/resources/ssi_resource_test_seed.py new file mode 100644 index 00000000000..97212945639 --- /dev/null +++ b/policyengine_us/variables/gov/ssa/ssi/eligibility/resources/ssi_resource_test_seed.py @@ -0,0 +1,8 @@ +from policyengine_us.model_api import * + + +class ssi_resource_test_seed(Variable): + value_type = float + entity = Person + label = "Deterministic seed for SSI resource test" + definition_period = YEAR diff --git a/uv.lock b/uv.lock index 66293da3350..8ca6e603efe 100644 --- a/uv.lock +++ b/uv.lock @@ -1664,7 +1664,7 @@ wheels = [ [[package]] name = "policyengine-us" -version = "1.549.0" +version = "1.550.1" source = { editable = "." } dependencies = [ { name = "microdf-python" },