diff --git a/changelog_entry.yaml b/changelog_entry.yaml index e69de29bb2d..bbd717d9a26 100644 --- a/changelog_entry.yaml +++ b/changelog_entry.yaml @@ -0,0 +1,4 @@ +- bump: minor + changes: + added: + - Oregon Healthier Oregon program providing Medicaid-equivalent coverage for undocumented immigrants. diff --git a/policyengine_us/parameters/gov/states/or/oha/healthier_oregon/eligibility/adult_income_limit.yaml b/policyengine_us/parameters/gov/states/or/oha/healthier_oregon/eligibility/adult_income_limit.yaml new file mode 100644 index 00000000000..2b0ddf34a7a --- /dev/null +++ b/policyengine_us/parameters/gov/states/or/oha/healthier_oregon/eligibility/adult_income_limit.yaml @@ -0,0 +1,13 @@ +description: Oregon limits gross income to this share of the federal poverty level for adults under the Healthier Oregon program. +metadata: + unit: /1 + period: year + label: Oregon Healthier Oregon adult income limit (% FPL) + reference: + - title: ORS 414.231(4) - Coverage for noncitizens + href: https://oregon.public.law/statutes/ors_414.231 + - title: Oregon Health Plan (OHP), Health Systems Division (HSD) Medical Programs - 2025 income thresholds (page 11) + href: https://sharedsystems.dhsoha.state.or.us/DHSForms/Served/de5530.pdf + +values: + 2023-07-01: 1.38 # 138% FPL for adults diff --git a/policyengine_us/parameters/gov/states/or/oha/healthier_oregon/eligibility/aged_threshold.yaml b/policyengine_us/parameters/gov/states/or/oha/healthier_oregon/eligibility/aged_threshold.yaml new file mode 100644 index 00000000000..8f1aa583c1f --- /dev/null +++ b/policyengine_us/parameters/gov/states/or/oha/healthier_oregon/eligibility/aged_threshold.yaml @@ -0,0 +1,13 @@ +description: Oregon considers individuals at or above this age as aged for Medicaid spending group classification under the Healthier Oregon program. +metadata: + unit: year + period: year + label: Oregon Healthier Oregon aged threshold + reference: + - title: ORS 414.231 - Coverage for noncitizens + href: https://oregon.public.law/statutes/ors_414.231 + - title: Social Security Act Section 1614(a)(1)(A) - Definition of aged (42 USC 1382c) + href: https://www.ssa.gov/OP_Home/ssact/title16b/1614.htm + +values: + 2023-07-01: 65 # Federal standard aged threshold diff --git a/policyengine_us/parameters/gov/states/or/oha/healthier_oregon/eligibility/child_income_limit.yaml b/policyengine_us/parameters/gov/states/or/oha/healthier_oregon/eligibility/child_income_limit.yaml new file mode 100644 index 00000000000..27da73634a9 --- /dev/null +++ b/policyengine_us/parameters/gov/states/or/oha/healthier_oregon/eligibility/child_income_limit.yaml @@ -0,0 +1,13 @@ +description: Oregon limits gross income to this share of the federal poverty level for children under the Healthier Oregon program. +metadata: + unit: /1 + period: year + label: Oregon Healthier Oregon child income limit (% FPL) + reference: + - title: ORS 414.231(1)(a) - Coverage for noncitizens (300% FPL for children) + href: https://oregon.public.law/statutes/ors_414.231 + - title: Oregon Health Plan (OHP), Health Systems Division (HSD) Medical Programs - 2025 income thresholds (page 11) + href: https://sharedsystems.dhsoha.state.or.us/DHSForms/Served/de5530.pdf + +values: + 2023-07-01: 3.05 # 305% FPL for children diff --git a/policyengine_us/parameters/gov/states/or/oha/healthier_oregon/eligibility/child_max_age.yaml b/policyengine_us/parameters/gov/states/or/oha/healthier_oregon/eligibility/child_max_age.yaml new file mode 100644 index 00000000000..7a05a99e2a6 --- /dev/null +++ b/policyengine_us/parameters/gov/states/or/oha/healthier_oregon/eligibility/child_max_age.yaml @@ -0,0 +1,11 @@ +description: Oregon applies the higher child income limit to individuals under this age under the Healthier Oregon program. +metadata: + unit: year + period: year + label: Oregon Healthier Oregon child maximum age + reference: + - title: ORS 414.231 - Coverage for noncitizens (defines child as under 19) + href: https://oregon.public.law/statutes/ors_414.231 + +values: + 2023-07-01: 19 # Children under 19 (ages 0-18) diff --git a/policyengine_us/parameters/gov/states/or/oha/healthier_oregon/eligibility/in_effect.yaml b/policyengine_us/parameters/gov/states/or/oha/healthier_oregon/eligibility/in_effect.yaml new file mode 100644 index 00000000000..a2b8dfeca08 --- /dev/null +++ b/policyengine_us/parameters/gov/states/or/oha/healthier_oregon/eligibility/in_effect.yaml @@ -0,0 +1,16 @@ +description: Oregon makes the Healthier Oregon program available when this parameter is true. +metadata: + unit: bool + period: year + label: Oregon Healthier Oregon in effect + reference: + - title: ORS 414.231 - Coverage for noncitizens + href: https://oregon.public.law/statutes/ors_414.231 + - title: Oregon Health Authority - Healthier Oregon + href: https://www.oregon.gov/oha/hsd/ohp/pages/healthier-oregon.aspx + - title: Oregon House Bill 3352 + href: https://olis.oregonlegislature.gov/liz/2021R1/Downloads/MeasureDocument/HB3352/Enrolled + +values: + 2020-01-01: false + 2023-07-01: true # Full expansion to all ages effective July 1, 2023 diff --git a/policyengine_us/parameters/gov/states/or/oha/healthier_oregon/eligibility/qualified_immigration_statuses.yaml b/policyengine_us/parameters/gov/states/or/oha/healthier_oregon/eligibility/qualified_immigration_statuses.yaml new file mode 100644 index 00000000000..a09a86a7ce1 --- /dev/null +++ b/policyengine_us/parameters/gov/states/or/oha/healthier_oregon/eligibility/qualified_immigration_statuses.yaml @@ -0,0 +1,19 @@ +description: Oregon provides Healthier Oregon coverage to immigrants with these statuses who are not eligible for federal Medicaid. +values: + 2023-07-01: + - UNDOCUMENTED + - DACA + - TPS + - DACA_TPS + +metadata: + label: Oregon Healthier Oregon qualified immigration statuses + period: year + unit: list + reference: + - title: ORS 414.231 - Coverage for noncitizens + href: https://oregon.public.law/statutes/ors_414.231 + - title: Oregon Health Authority - Healthier Oregon + href: https://www.oregon.gov/oha/hsd/ohp/pages/healthier-oregon.aspx + - title: Oregon HB 3352 - Cover All People Act + href: https://olis.oregonlegislature.gov/liz/2021R1/Downloads/MeasureDocument/HB3352/Enrolled diff --git a/policyengine_us/tests/policy/baseline/gov/states/or/oha/healthier_oregon/eligibility/or_healthier_oregon_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/or/oha/healthier_oregon/eligibility/or_healthier_oregon_eligible.yaml new file mode 100644 index 00000000000..813e927e8b3 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/or/oha/healthier_oregon/eligibility/or_healthier_oregon_eligible.yaml @@ -0,0 +1,155 @@ +# Tests for overall Oregon Healthier Oregon eligibility + +- name: Undocumented adult with low income eligible + period: 2024 + input: + age: 30 + immigration_status: UNDOCUMENTED + medicaid_income_level: 1.0 # 100% FPL + state_code: OR + output: + or_healthier_oregon_eligible: true + +- name: Undocumented adult with income too high NOT eligible + period: 2024 + input: + age: 30 + immigration_status: UNDOCUMENTED + medicaid_income_level: 1.5 # 150% FPL (above 138% limit for adults) + state_code: OR + output: + or_healthier_oregon_eligible: false + +- name: Undocumented child with higher income eligible (305% limit) + period: 2024 + input: + age: 10 + immigration_status: UNDOCUMENTED + medicaid_income_level: 2.5 # 250% FPL (within 305% limit for children) + state_code: OR + output: + or_healthier_oregon_eligible: true + +- name: Undocumented child with income too high NOT eligible + period: 2024 + input: + age: 10 + immigration_status: UNDOCUMENTED + medicaid_income_level: 3.5 # 350% FPL (above 305% limit) + state_code: OR + output: + or_healthier_oregon_eligible: false + +- name: US citizen NOT eligible regardless of income + period: 2024 + input: + age: 30 + immigration_status: CITIZEN + medicaid_income_level: 1.0 # 100% FPL + state_code: OR + output: + or_healthier_oregon_eligible: false + +- name: Program not in effect before July 2023 + period: 2022 + input: + age: 30 + immigration_status: UNDOCUMENTED + medicaid_income_level: 1.0 # 100% FPL + state_code: OR + output: + or_healthier_oregon_eligible: false + +- name: DACA recipient with low income eligible + period: 2024 + input: + age: 25 + immigration_status: DACA + medicaid_income_level: 1.0 # 100% FPL + state_code: OR + output: + or_healthier_oregon_eligible: true + +- name: Adult at exactly 138% FPL eligible + period: 2024 + input: + age: 30 + immigration_status: UNDOCUMENTED + medicaid_income_level: 1.38 # 138% FPL (exactly at limit) + state_code: OR + output: + or_healthier_oregon_eligible: true + +# Income boundary tests + +- name: Boundary - Adult at 139% FPL NOT eligible (just above 138% limit) + period: 2024 + input: + age: 30 + immigration_status: UNDOCUMENTED + medicaid_income_level: 1.39 # 139% FPL (just above 138% limit) + state_code: OR + output: + or_healthier_oregon_eligible: false + +- name: Boundary - Child at exactly 305% FPL eligible + period: 2024 + input: + age: 10 + immigration_status: UNDOCUMENTED + medicaid_income_level: 3.05 # 305% FPL (exactly at limit) + state_code: OR + output: + or_healthier_oregon_eligible: true + +- name: Boundary - Child at 306% FPL NOT eligible (just above 305% limit) + period: 2024 + input: + age: 10 + immigration_status: UNDOCUMENTED + medicaid_income_level: 3.06 # 306% FPL (just above 305% limit) + state_code: OR + output: + or_healthier_oregon_eligible: false + +# Age boundary tests (cliff effect at age 18/19) + +- name: Age cliff - Age 18 at 200% FPL eligible (child limit 305%) + period: 2024 + input: + age: 18 + immigration_status: UNDOCUMENTED + medicaid_income_level: 2.0 # 200% FPL (within 305% child limit) + state_code: OR + output: + or_healthier_oregon_eligible: true + +- name: Age cliff - Age 19 at 200% FPL NOT eligible (adult limit 138%) + period: 2024 + input: + age: 19 + immigration_status: UNDOCUMENTED + medicaid_income_level: 2.0 # 200% FPL (above 138% adult limit) + state_code: OR + output: + or_healthier_oregon_eligible: false + +- name: Age cliff - Age 18 at 140% FPL eligible (still a child) + period: 2024 + input: + age: 18 + immigration_status: UNDOCUMENTED + medicaid_income_level: 1.4 # 140% FPL (within 305% child limit) + state_code: OR + output: + or_healthier_oregon_eligible: true + +- name: Age cliff - Age 19 at 140% FPL NOT eligible (adult over 138%) + period: 2024 + input: + age: 19 + immigration_status: UNDOCUMENTED + medicaid_income_level: 1.4 # 140% FPL (above 138% adult limit) + state_code: OR + output: + or_healthier_oregon_eligible: false diff --git a/policyengine_us/tests/policy/baseline/gov/states/or/oha/healthier_oregon/eligibility/or_healthier_oregon_immigration_status_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/or/oha/healthier_oregon/eligibility/or_healthier_oregon_immigration_status_eligible.yaml new file mode 100644 index 00000000000..9d8a27850b4 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/or/oha/healthier_oregon/eligibility/or_healthier_oregon_immigration_status_eligible.yaml @@ -0,0 +1,82 @@ +# Tests for Oregon Healthier Oregon immigration status eligibility + +# Undocumented immigrants are eligible +- name: Undocumented adult eligible + period: 2024 + input: + immigration_status: UNDOCUMENTED + state_code: OR + output: + or_healthier_oregon_immigration_status_eligible: true + +# DACA recipients are eligible +- name: DACA recipient eligible + period: 2024 + input: + immigration_status: DACA + state_code: OR + output: + or_healthier_oregon_immigration_status_eligible: true + +# TPS holders are eligible +- name: TPS holder eligible + period: 2024 + input: + immigration_status: TPS + state_code: OR + output: + or_healthier_oregon_immigration_status_eligible: true + +# DACA_TPS combined status is eligible +- name: DACA_TPS combined status eligible + period: 2024 + input: + immigration_status: DACA_TPS + state_code: OR + output: + or_healthier_oregon_immigration_status_eligible: true + +# Citizens should use regular federal Medicaid +- name: US citizen NOT eligible (uses federal Medicaid) + period: 2024 + input: + immigration_status: CITIZEN + state_code: OR + output: + or_healthier_oregon_immigration_status_eligible: false + +# LPRs should use regular federal Medicaid +- name: LPR NOT eligible (uses federal Medicaid) + period: 2024 + input: + immigration_status: LEGAL_PERMANENT_RESIDENT + state_code: OR + output: + or_healthier_oregon_immigration_status_eligible: false + +# Refugees should use regular federal Medicaid +- name: Refugee NOT eligible (uses federal Medicaid) + period: 2024 + input: + immigration_status: REFUGEE + state_code: OR + output: + or_healthier_oregon_immigration_status_eligible: false + +# Asylees should use regular federal Medicaid +- name: Asylee NOT eligible (uses federal Medicaid) + period: 2024 + input: + immigration_status: ASYLEE + state_code: OR + output: + or_healthier_oregon_immigration_status_eligible: false + +# Non-OR residents not eligible +- name: Undocumented person in CA not eligible for OR program + period: 2024 + input: + immigration_status: UNDOCUMENTED + state_code: CA + output: + or_healthier_oregon_immigration_status_eligible: false diff --git a/policyengine_us/tests/policy/baseline/gov/states/or/oha/healthier_oregon/integration.yaml b/policyengine_us/tests/policy/baseline/gov/states/or/oha/healthier_oregon/integration.yaml new file mode 100644 index 00000000000..7a9198dedd0 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/or/oha/healthier_oregon/integration.yaml @@ -0,0 +1,66 @@ +# Integration tests for Oregon Healthier Oregon with cost calculation + +- name: Undocumented adult gets healthcare benefit value + period: 2024 + absolute_error_margin: 0.1 + input: + people: + person: + age: 30 + immigration_status: UNDOCUMENTED + medicaid_income_level: 1.0 # 100% FPL + households: + household: + members: [person] + state_code: OR + output: + or_healthier_oregon_eligible: [true] + # Cost should be non-zero (Oregon expansion adult rate) + or_healthier_oregon_cost: [7_475.23] # Oregon expansion adult per capita cost + +- name: Undocumented child gets higher income limit + period: 2024 + input: + people: + child: + age: 10 + immigration_status: UNDOCUMENTED + medicaid_income_level: 2.5 # 250% FPL - within child limit + households: + household: + members: [child] + state_code: OR + output: + or_healthier_oregon_eligible: [true] + +- name: US citizen not eligible - uses federal Medicaid instead + period: 2024 + input: + people: + person: + age: 30 + immigration_status: CITIZEN + medicaid_income_level: 1.0 + households: + household: + members: [person] + state_code: OR + output: + or_healthier_oregon_eligible: [false] + or_healthier_oregon_cost: [0] + +- name: Program not available before July 2023 + period: 2022 + input: + people: + person: + age: 30 + immigration_status: UNDOCUMENTED + medicaid_income_level: 1.0 + households: + household: + members: [person] + state_code: OR + output: + or_healthier_oregon_eligible: [false] + or_healthier_oregon_cost: [0] diff --git a/policyengine_us/variables/gov/hhs/medicaid/costs/medicaid_cost_if_enrolled.py b/policyengine_us/variables/gov/hhs/medicaid/costs/medicaid_cost_if_enrolled.py index f1d04ec1a67..acaaf10253f 100644 --- a/policyengine_us/variables/gov/hhs/medicaid/costs/medicaid_cost_if_enrolled.py +++ b/policyengine_us/variables/gov/hhs/medicaid/costs/medicaid_cost_if_enrolled.py @@ -2,6 +2,9 @@ from policyengine_us.variables.gov.hhs.medicaid.costs.medicaid_group import ( MedicaidGroup, ) +from policyengine_us.variables.gov.hhs.medicaid.costs.per_capita_cost_helpers import ( + calculate_per_capita_cost, +) class medicaid_cost_if_enrolled(Variable): @@ -13,74 +16,7 @@ class medicaid_cost_if_enrolled(Variable): defined_for = "is_medicaid_eligible" def formula(person, period, parameters): - state = person.household("state_code", period) group = person("medicaid_group", period) - - p = parameters(period).calibration.gov.hhs.medicaid - - # Handle each group type separately - is_aged_disabled = group == MedicaidGroup.AGED_DISABLED - is_child = group == MedicaidGroup.CHILD - is_expansion_adult = group == MedicaidGroup.EXPANSION_ADULT - is_non_expansion_adult = group == MedicaidGroup.NON_EXPANSION_ADULT - - # Calculate spend and enrollment for each group - aged_spend = p.spending.by_eligibility_group.aged[state] - disabled_spend = p.spending.by_eligibility_group.disabled[state] - child_spend = p.spending.by_eligibility_group.child[state] - expansion_adult_spend = ( - p.spending.by_eligibility_group.expansion_adults[state] + return calculate_per_capita_cost( + person, period, parameters, group, MedicaidGroup ) - non_expansion_adult_spend = ( - p.spending.by_eligibility_group.non_expansion_adults[state] - ) - - aged_enroll = p.enrollment.aged[state] - disabled_enroll = p.enrollment.disabled[state] - child_enroll = p.enrollment.child[state] - expansion_adult_enroll = p.enrollment.expansion_adults[state] - non_expansion_adult_enroll = p.enrollment.non_expansion_adults[state] - - # Combine aged and disabled for AGED_DISABLED group - aged_disabled_spend = aged_spend + disabled_spend - aged_disabled_enroll = aged_enroll + disabled_enroll - - # Select the right values based on group - spend = select( - [ - is_aged_disabled, - is_child, - is_expansion_adult, - is_non_expansion_adult, - ], - [ - aged_disabled_spend, - child_spend, - expansion_adult_spend, - non_expansion_adult_spend, - ], - default=0, - ) - - enroll = select( - [ - is_aged_disabled, - is_child, - is_expansion_adult, - is_non_expansion_adult, - ], - [ - aged_disabled_enroll, - child_enroll, - expansion_adult_enroll, - non_expansion_adult_enroll, - ], - default=0, - ) - - # Avoid divide‑by‑zero in non‑expansion states by assigning national average. - per_capita = p.totals.per_capita[group] - mask = enroll > 0 - per_capita[mask] = spend[mask] / enroll[mask] - - return per_capita diff --git a/policyengine_us/variables/gov/hhs/medicaid/costs/per_capita_cost_helpers.py b/policyengine_us/variables/gov/hhs/medicaid/costs/per_capita_cost_helpers.py new file mode 100644 index 00000000000..25d7bc6a3bf --- /dev/null +++ b/policyengine_us/variables/gov/hhs/medicaid/costs/per_capita_cost_helpers.py @@ -0,0 +1,100 @@ +"""Helper functions for calculating per capita Medicaid costs. + +This module provides shared logic for calculating per capita costs that is +used by: +- medicaid_cost_if_enrolled (for federal Medicaid) +- or_healthier_oregon_cost_if_enrolled (for Oregon Healthier Oregon) +- Other state Medicaid-like programs +""" + +from policyengine_us.model_api import * + + +def calculate_per_capita_cost( + person, period, parameters, group, MedicaidGroup +): + """Calculate per capita Medicaid cost for a given eligibility group. + + Args: + person: The person entity + period: The calculation period + parameters: The parameters object + group: Array of MedicaidGroup values for each person + MedicaidGroup: The MedicaidGroup enum class + + Returns: + Array of per capita costs for each person based on their group + """ + state = person.household("state_code", period) + + p = parameters(period).calibration.gov.hhs.medicaid + + # Get spending by eligibility group and state + aged_spend = p.spending.by_eligibility_group.aged[state] + disabled_spend = p.spending.by_eligibility_group.disabled[state] + child_spend = p.spending.by_eligibility_group.child[state] + expansion_adult_spend = p.spending.by_eligibility_group.expansion_adults[ + state + ] + non_expansion_adult_spend = ( + p.spending.by_eligibility_group.non_expansion_adults[state] + ) + + # Get enrollment by eligibility group and state + aged_enroll = p.enrollment.aged[state] + disabled_enroll = p.enrollment.disabled[state] + child_enroll = p.enrollment.child[state] + expansion_adult_enroll = p.enrollment.expansion_adults[state] + non_expansion_adult_enroll = p.enrollment.non_expansion_adults[state] + + # Combine aged and disabled for AGED_DISABLED group + aged_disabled_spend = aged_spend + disabled_spend + aged_disabled_enroll = aged_enroll + disabled_enroll + + # Determine group membership + is_aged_disabled = group == MedicaidGroup.AGED_DISABLED + is_child = group == MedicaidGroup.CHILD + is_expansion_adult = group == MedicaidGroup.EXPANSION_ADULT + is_non_expansion_adult = group == MedicaidGroup.NON_EXPANSION_ADULT + + # Select spending based on group + spend = select( + [ + is_aged_disabled, + is_child, + is_expansion_adult, + is_non_expansion_adult, + ], + [ + aged_disabled_spend, + child_spend, + expansion_adult_spend, + non_expansion_adult_spend, + ], + default=0, + ) + + # Select enrollment based on group + enroll = select( + [ + is_aged_disabled, + is_child, + is_expansion_adult, + is_non_expansion_adult, + ], + [ + aged_disabled_enroll, + child_enroll, + expansion_adult_enroll, + non_expansion_adult_enroll, + ], + default=0, + ) + + # Calculate per capita cost + # Use national average as fallback when enrollment is zero + per_capita = p.totals.per_capita[group].copy() + mask = enroll > 0 + per_capita[mask] = spend[mask] / enroll[mask] + + return per_capita diff --git a/policyengine_us/variables/gov/states/or/oha/healthier_oregon/eligibility/or_healthier_oregon_eligible.py b/policyengine_us/variables/gov/states/or/oha/healthier_oregon/eligibility/or_healthier_oregon_eligible.py new file mode 100644 index 00000000000..5bd6e441bd0 --- /dev/null +++ b/policyengine_us/variables/gov/states/or/oha/healthier_oregon/eligibility/or_healthier_oregon_eligible.py @@ -0,0 +1,39 @@ +from policyengine_us.model_api import * + + +class or_healthier_oregon_eligible(Variable): + value_type = bool + entity = Person + label = "Eligible for Oregon Healthier Oregon" + definition_period = YEAR + defined_for = StateCode.OR + reference = [ + "https://www.oregon.gov/oha/hsd/ohp/pages/healthier-oregon.aspx", + "https://olis.oregonlegislature.gov/liz/2021R1/Downloads/MeasureDocument/HB3352/Enrolled", + ] + documentation = """ + Oregon's Healthier Oregon program (formerly Cover All People) provides + full Oregon Health Plan benefits to income-eligible residents who are + not eligible for federal Medicaid due to their immigration status. + Effective July 1, 2023 for all ages. + """ + + def formula(person, period, parameters): + p = ( + parameters(period) + .gov.states["or"] + .oha.healthier_oregon.eligibility + ) + + # Program must be in effect + in_effect = p.in_effect + + # Must have non-federally-eligible immigration status + immigration_eligible = person( + "or_healthier_oregon_immigration_status_eligible", period + ) + + # Must meet income requirements + income_eligible = person("or_healthier_oregon_income_eligible", period) + + return in_effect & immigration_eligible & income_eligible diff --git a/policyengine_us/variables/gov/states/or/oha/healthier_oregon/eligibility/or_healthier_oregon_immigration_status_eligible.py b/policyengine_us/variables/gov/states/or/oha/healthier_oregon/eligibility/or_healthier_oregon_immigration_status_eligible.py new file mode 100644 index 00000000000..498e7d61583 --- /dev/null +++ b/policyengine_us/variables/gov/states/or/oha/healthier_oregon/eligibility/or_healthier_oregon_immigration_status_eligible.py @@ -0,0 +1,33 @@ +from policyengine_us.model_api import * +import numpy as np + + +class or_healthier_oregon_immigration_status_eligible(Variable): + value_type = bool + entity = Person + label = "Has eligible immigration status for Oregon Healthier Oregon" + definition_period = YEAR + defined_for = StateCode.OR + reference = [ + "https://www.oregon.gov/oha/hsd/ohp/pages/healthier-oregon.aspx", + "https://olis.oregonlegislature.gov/liz/2021R1/Downloads/MeasureDocument/HB3352/Enrolled", + ] + documentation = """ + Oregon's Healthier Oregon program (formerly Cover All People) provides + full OHP benefits to residents who are not eligible for federal Medicaid + due to their immigration status. This includes undocumented immigrants, + DACA recipients, and other non-qualifying immigration statuses. + """ + + def formula(person, period, parameters): + p = ( + parameters(period) + .gov.states["or"] + .oha.healthier_oregon.eligibility + ) + immigration_status = person("immigration_status", period) + immigration_status_str = immigration_status.decode_to_str() + return np.isin( + immigration_status_str, + p.qualified_immigration_statuses, + ) diff --git a/policyengine_us/variables/gov/states/or/oha/healthier_oregon/eligibility/or_healthier_oregon_income_eligible.py b/policyengine_us/variables/gov/states/or/oha/healthier_oregon/eligibility/or_healthier_oregon_income_eligible.py new file mode 100644 index 00000000000..0c7af7fd940 --- /dev/null +++ b/policyengine_us/variables/gov/states/or/oha/healthier_oregon/eligibility/or_healthier_oregon_income_eligible.py @@ -0,0 +1,38 @@ +from policyengine_us.model_api import * + + +class or_healthier_oregon_income_eligible(Variable): + value_type = bool + entity = Person + label = "Meets Oregon Healthier Oregon income eligibility" + definition_period = YEAR + defined_for = StateCode.OR + reference = [ + "https://www.oregon.gov/oha/hsd/ohp/pages/healthier-oregon.aspx", + "https://www.opb.org/article/2023/07/10/oregon-expands-health-coverage-low-income-residents-immigrants/", + ] + documentation = """ + Oregon Healthier Oregon has different income limits based on age: + - Adults (19+): Up to 138% FPL + - Children (0-18): Up to 305% FPL + """ + + def formula(person, period, parameters): + p = ( + parameters(period) + .gov.states["or"] + .oha.healthier_oregon.eligibility + ) + income_level = person("medicaid_income_level", period) + + age = person("age", period) + is_child = age < p.child_max_age + + # Different income limits based on age + income_limit = where( + is_child, + p.child_income_limit, + p.adult_income_limit, + ) + + return income_level <= income_limit diff --git a/policyengine_us/variables/gov/states/or/oha/healthier_oregon/or_healthier_oregon_cost.py b/policyengine_us/variables/gov/states/or/oha/healthier_oregon/or_healthier_oregon_cost.py new file mode 100644 index 00000000000..c5091eb4ad6 --- /dev/null +++ b/policyengine_us/variables/gov/states/or/oha/healthier_oregon/or_healthier_oregon_cost.py @@ -0,0 +1,20 @@ +from policyengine_us.model_api import * + + +class or_healthier_oregon_cost(Variable): + value_type = float + entity = Person + label = "Oregon Healthier Oregon benefit cost" + unit = USD + definition_period = YEAR + defined_for = "or_healthier_oregon_eligible" + reference = [ + "https://www.oregon.gov/oha/hsd/ohp/pages/healthier-oregon.aspx", + ] + documentation = """ + Oregon Healthier Oregon provides full OHP (Oregon Health Plan) benefits, + equivalent to Medicaid coverage. The cost is calculated using the same + per capita Medicaid costs by eligibility group. + """ + + adds = ["or_healthier_oregon_cost_if_enrolled"] diff --git a/policyengine_us/variables/gov/states/or/oha/healthier_oregon/or_healthier_oregon_cost_if_enrolled.py b/policyengine_us/variables/gov/states/or/oha/healthier_oregon/or_healthier_oregon_cost_if_enrolled.py new file mode 100644 index 00000000000..80cd6feb49c --- /dev/null +++ b/policyengine_us/variables/gov/states/or/oha/healthier_oregon/or_healthier_oregon_cost_if_enrolled.py @@ -0,0 +1,25 @@ +from policyengine_us.model_api import * +from policyengine_us.variables.gov.hhs.medicaid.costs.medicaid_group import ( + MedicaidGroup, +) +from policyengine_us.variables.gov.hhs.medicaid.costs.per_capita_cost_helpers import ( + calculate_per_capita_cost, +) + + +class or_healthier_oregon_cost_if_enrolled(Variable): + value_type = float + entity = Person + label = "Per capita Oregon Healthier Oregon cost by eligibility group" + unit = USD + definition_period = YEAR + defined_for = "or_healthier_oregon_eligible" + reference = ( + "https://www.oregon.gov/oha/hsd/ohp/pages/healthier-oregon.aspx" + ) + + def formula(person, period, parameters): + group = person("or_healthier_oregon_group", period) + return calculate_per_capita_cost( + person, period, parameters, group, MedicaidGroup + ) diff --git a/policyengine_us/variables/gov/states/or/oha/healthier_oregon/or_healthier_oregon_group.py b/policyengine_us/variables/gov/states/or/oha/healthier_oregon/or_healthier_oregon_group.py new file mode 100644 index 00000000000..9ec02880347 --- /dev/null +++ b/policyengine_us/variables/gov/states/or/oha/healthier_oregon/or_healthier_oregon_group.py @@ -0,0 +1,63 @@ +from policyengine_us.model_api import * +from policyengine_us.variables.gov.hhs.medicaid.costs.medicaid_group import ( + MedicaidGroup, +) + + +class or_healthier_oregon_group(Variable): + """Maps Oregon Healthier Oregon enrollees to Medicaid spending groups. + + Precedence order (highest to lowest): + 1. Aged/Disabled - SSI recipients or age 65+ + 2. Non-Expansion Adult - pregnant adults + 3. Expansion Adult - other adults + 4. Child - age < child_max_age parameter (under 19) + """ + + value_type = Enum + possible_values = MedicaidGroup + default_value = MedicaidGroup.NONE + entity = Person + label = "Oregon Healthier Oregon Medicaid spending group" + definition_period = YEAR + defined_for = StateCode.OR + reference = ( + "https://www.oregon.gov/oha/hsd/ohp/pages/healthier-oregon.aspx" + ) + + def formula(person, period, parameters): + age = person("age", period) + is_pregnant = person("is_pregnant", period) + is_disabled = person("is_ssi_recipient_for_medicaid", period) + + # Use the Oregon Healthier Oregon child_max_age parameter + p = ( + parameters(period) + .gov.states["or"] + .oha.healthier_oregon.eligibility + ) + is_child = age < p.child_max_age + + is_aged_disabled = is_disabled | (age >= p.aged_threshold) + + is_non_expansion_adult = is_pregnant & ~is_child & ~is_aged_disabled + + is_expansion_adult = ( + ~is_child & ~is_aged_disabled & ~is_non_expansion_adult + ) + + return select( + [ + is_aged_disabled, + is_child, + is_non_expansion_adult, + is_expansion_adult, + ], + [ + MedicaidGroup.AGED_DISABLED, + MedicaidGroup.CHILD, + MedicaidGroup.NON_EXPANSION_ADULT, + MedicaidGroup.EXPANSION_ADULT, + ], + default=MedicaidGroup.EXPANSION_ADULT, + ) diff --git a/policyengine_us/variables/household/healthcare_benefit_value.py b/policyengine_us/variables/household/healthcare_benefit_value.py index 1e1bc0dd1af..e88602fefcb 100644 --- a/policyengine_us/variables/household/healthcare_benefit_value.py +++ b/policyengine_us/variables/household/healthcare_benefit_value.py @@ -11,4 +11,5 @@ class healthcare_benefit_value(Variable): "medicaid_cost", "per_capita_chip", "aca_ptc", + "or_healthier_oregon_cost", ] diff --git a/uv.lock b/uv.lock index a27d66e4748..06bf5cb6b5b 100644 --- a/uv.lock +++ b/uv.lock @@ -529,11 +529,11 @@ wheels = [ [[package]] name = "filelock" -version = "3.20.1" +version = "3.20.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a7/23/ce7a1126827cedeb958fc043d61745754464eb56c5937c35bbf2b8e26f34/filelock-3.20.1.tar.gz", hash = "sha256:b8360948b351b80f420878d8516519a2204b07aefcdcfd24912a5d33127f188c", size = 19476, upload-time = "2025-12-15T23:54:28.027Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c1/e0/a75dbe4bca1e7d41307323dad5ea2efdd95408f74ab2de8bd7dba9b51a1a/filelock-3.20.2.tar.gz", hash = "sha256:a2241ff4ddde2a7cebddf78e39832509cb045d18ec1a09d7248d6bfc6bfbbe64", size = 19510, upload-time = "2026-01-02T15:33:32.582Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e3/7f/a1a97644e39e7316d850784c642093c99df1290a460df4ede27659056834/filelock-3.20.1-py3-none-any.whl", hash = "sha256:15d9e9a67306188a44baa72f569d2bfd803076269365fdea0934385da4dc361a", size = 16666, upload-time = "2025-12-15T23:54:26.874Z" }, + { url = "https://files.pythonhosted.org/packages/9a/30/ab407e2ec752aa541704ed8f93c11e2a5d92c168b8a755d818b74a3c5c2d/filelock-3.20.2-py3-none-any.whl", hash = "sha256:fbba7237d6ea277175a32c54bb71ef814a8546d8601269e1bfc388de333974e8", size = 16697, upload-time = "2026-01-02T15:33:31.133Z" }, ] [[package]] @@ -563,7 +563,8 @@ dependencies = [ { name = "beautifulsoup4" }, { name = "pygments" }, { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "sphinx", version = "9.0.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "sphinx", version = "9.0.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, + { name = "sphinx", version = "9.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, { name = "sphinx-basic-ng" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ec/20/5f5ad4da6a5a27c80f2ed2ee9aee3f9e36c66e56e21c00fde467b2f8f88f/furo-2025.12.19.tar.gz", hash = "sha256:188d1f942037d8b37cd3985b955839fea62baa1730087dc29d157677c857e2a7", size = 1661473, upload-time = "2025-12-19T17:34:40.889Z" } @@ -1534,7 +1535,7 @@ wheels = [ [[package]] name = "policyengine-us" -version = "1.484.2" +version = "1.485.0" source = { editable = "." } dependencies = [ { name = "microdf-python" }, @@ -1996,11 +1997,11 @@ wheels = [ [[package]] name = "send2trash" -version = "1.8.3" +version = "2.0.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fd/3a/aec9b02217bb79b87bbc1a21bc6abc51e3d5dcf65c30487ac96c0908c722/Send2Trash-1.8.3.tar.gz", hash = "sha256:b18e7a3966d99871aefeb00cfbcfdced55ce4871194810fc71f4aa484b953abf", size = 17394, upload-time = "2024-04-07T00:01:09.267Z" } +sdist = { url = "https://files.pythonhosted.org/packages/62/6e/421803dec0c0dfbf5a27e66491ebe6643a461e4f90422f00ea4c68ae24aa/send2trash-2.0.0.tar.gz", hash = "sha256:1761421da3f9930bfe51ed7c45343948573383ad4c27e3acebc91be324e7770d", size = 17206, upload-time = "2025-12-31T04:12:48.664Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/40/b0/4562db6223154aa4e22f939003cb92514c79f3d4dccca3444253fd17f902/Send2Trash-1.8.3-py3-none-any.whl", hash = "sha256:0c31227e0bd08961c7665474a3d1ef7193929fedda4233843689baa056be46c9", size = 18072, upload-time = "2024-04-07T00:01:07.438Z" }, + { url = "https://files.pythonhosted.org/packages/1b/5a/f2f2e5eda25579f754acd83399c522ee03d6acbe001dfe53c8a1ec928b44/send2trash-2.0.0-py3-none-any.whl", hash = "sha256:e70d5ce41dbb890882cc78bc25d137478330b39a391e756fadf82e34da4d85b8", size = 17642, upload-time = "2025-12-31T04:12:45.336Z" }, ] [[package]] @@ -2093,40 +2094,71 @@ name = "sphinx" version = "9.0.4" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version >= '3.12'", "python_full_version == '3.11.*'", ] dependencies = [ - { name = "alabaster", marker = "python_full_version >= '3.11'" }, - { name = "babel", marker = "python_full_version >= '3.11'" }, - { name = "colorama", marker = "python_full_version >= '3.11' and sys_platform == 'win32'" }, - { name = "docutils", version = "0.22.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, - { name = "imagesize", marker = "python_full_version >= '3.11'" }, - { name = "jinja2", marker = "python_full_version >= '3.11'" }, - { name = "packaging", marker = "python_full_version >= '3.11'" }, - { name = "pygments", marker = "python_full_version >= '3.11'" }, - { name = "requests", marker = "python_full_version >= '3.11'" }, - { name = "roman-numerals", marker = "python_full_version >= '3.11'" }, - { name = "snowballstemmer", marker = "python_full_version >= '3.11'" }, - { name = "sphinxcontrib-applehelp", marker = "python_full_version >= '3.11'" }, - { name = "sphinxcontrib-devhelp", marker = "python_full_version >= '3.11'" }, - { name = "sphinxcontrib-htmlhelp", marker = "python_full_version >= '3.11'" }, - { name = "sphinxcontrib-jsmath", marker = "python_full_version >= '3.11'" }, - { name = "sphinxcontrib-qthelp", marker = "python_full_version >= '3.11'" }, - { name = "sphinxcontrib-serializinghtml", marker = "python_full_version >= '3.11'" }, + { name = "alabaster", marker = "python_full_version == '3.11.*'" }, + { name = "babel", marker = "python_full_version == '3.11.*'" }, + { name = "colorama", marker = "python_full_version == '3.11.*' and sys_platform == 'win32'" }, + { name = "docutils", version = "0.22.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, + { name = "imagesize", marker = "python_full_version == '3.11.*'" }, + { name = "jinja2", marker = "python_full_version == '3.11.*'" }, + { name = "packaging", marker = "python_full_version == '3.11.*'" }, + { name = "pygments", marker = "python_full_version == '3.11.*'" }, + { name = "requests", marker = "python_full_version == '3.11.*'" }, + { name = "roman-numerals", marker = "python_full_version == '3.11.*'" }, + { name = "snowballstemmer", marker = "python_full_version == '3.11.*'" }, + { name = "sphinxcontrib-applehelp", marker = "python_full_version == '3.11.*'" }, + { name = "sphinxcontrib-devhelp", marker = "python_full_version == '3.11.*'" }, + { name = "sphinxcontrib-htmlhelp", marker = "python_full_version == '3.11.*'" }, + { name = "sphinxcontrib-jsmath", marker = "python_full_version == '3.11.*'" }, + { name = "sphinxcontrib-qthelp", marker = "python_full_version == '3.11.*'" }, + { name = "sphinxcontrib-serializinghtml", marker = "python_full_version == '3.11.*'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/42/50/a8c6ccc36d5eacdfd7913ddccd15a9cee03ecafc5ee2bc40e1f168d85022/sphinx-9.0.4.tar.gz", hash = "sha256:594ef59d042972abbc581d8baa577404abe4e6c3b04ef61bd7fc2acbd51f3fa3", size = 8710502, upload-time = "2025-12-04T07:45:27.343Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/c6/3f/4bbd76424c393caead2e1eb89777f575dee5c8653e2d4b6afd7a564f5974/sphinx-9.0.4-py3-none-any.whl", hash = "sha256:5bebc595a5e943ea248b99c13814c1c5e10b3ece718976824ffa7959ff95fffb", size = 3917713, upload-time = "2025-12-04T07:45:24.944Z" }, ] +[[package]] +name = "sphinx" +version = "9.1.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", +] +dependencies = [ + { name = "alabaster", marker = "python_full_version >= '3.12'" }, + { name = "babel", marker = "python_full_version >= '3.12'" }, + { name = "colorama", marker = "python_full_version >= '3.12' and sys_platform == 'win32'" }, + { name = "docutils", version = "0.22.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "imagesize", marker = "python_full_version >= '3.12'" }, + { name = "jinja2", marker = "python_full_version >= '3.12'" }, + { name = "packaging", marker = "python_full_version >= '3.12'" }, + { name = "pygments", marker = "python_full_version >= '3.12'" }, + { name = "requests", marker = "python_full_version >= '3.12'" }, + { name = "roman-numerals", marker = "python_full_version >= '3.12'" }, + { name = "snowballstemmer", marker = "python_full_version >= '3.12'" }, + { name = "sphinxcontrib-applehelp", marker = "python_full_version >= '3.12'" }, + { name = "sphinxcontrib-devhelp", marker = "python_full_version >= '3.12'" }, + { name = "sphinxcontrib-htmlhelp", marker = "python_full_version >= '3.12'" }, + { name = "sphinxcontrib-jsmath", marker = "python_full_version >= '3.12'" }, + { name = "sphinxcontrib-qthelp", marker = "python_full_version >= '3.12'" }, + { name = "sphinxcontrib-serializinghtml", marker = "python_full_version >= '3.12'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cd/bd/f08eb0f4eed5c83f1ba2a3bd18f7745a2b1525fad70660a1c00224ec468a/sphinx-9.1.0.tar.gz", hash = "sha256:7741722357dd75f8190766926071fed3bdc211c74dd2d7d4df5404da95930ddb", size = 8718324, upload-time = "2025-12-31T15:09:27.646Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/73/f7/b1884cb3188ab181fc81fa00c266699dab600f927a964df02ec3d5d1916a/sphinx-9.1.0-py3-none-any.whl", hash = "sha256:c84fdd4e782504495fe4f2c0b3413d6c2bf388589bb352d439b2a3bb99991978", size = 3921742, upload-time = "2025-12-31T15:09:25.561Z" }, +] + [[package]] name = "sphinx-basic-ng" version = "1.0.0b2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "sphinx", version = "9.0.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "sphinx", version = "9.0.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, + { name = "sphinx", version = "9.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/98/0b/a866924ded68efec7a1759587a4e478aec7559d8165fac8b2ad1c0e774d6/sphinx_basic_ng-1.0.0b2.tar.gz", hash = "sha256:9ec55a47c90c8c002b5960c57492ec3021f5193cb26cebc2dc4ea226848651c9", size = 20736, upload-time = "2023-07-08T18:40:54.166Z" } wheels = [