diff --git a/changelog.d/health-coverage-rules.added.md b/changelog.d/health-coverage-rules.added.md new file mode 100644 index 00000000000..1ec2fc1fd68 --- /dev/null +++ b/changelog.d/health-coverage-rules.added.md @@ -0,0 +1 @@ +Added reported current health coverage inputs and ACA/Medicaid coverage reconciliation helpers. diff --git a/policyengine_us/tests/policy/baseline/gov/aca/eligibility/coverage_report_model_conflict.yaml b/policyengine_us/tests/policy/baseline/gov/aca/eligibility/coverage_report_model_conflict.yaml new file mode 100644 index 00000000000..57e2e317fca --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/aca/eligibility/coverage_report_model_conflict.yaml @@ -0,0 +1,26 @@ +- name: Case 1, reported Marketplace plus Medicaid conflict is flagged. + period: 2025 + input: + reported_has_marketplace_health_coverage_at_interview: true + medicaid_enrolled: true + output: + coverage_report_model_conflict: true + +- name: Case 2, Marketplace without conflicting coverage is not flagged. + period: 2025 + input: + age: 40 + medicaid_enrolled: false + is_chip_eligible: false + medicare_enrolled: false + has_esi: false + reported_has_marketplace_health_coverage_at_interview: true + output: + coverage_report_model_conflict: false + +- name: Case 3, non-Marketplace coverage without reported Marketplace is not flagged. + period: 2025 + input: + reported_has_non_marketplace_direct_purchase_health_coverage_at_interview: true + output: + coverage_report_model_conflict: false diff --git a/policyengine_us/tests/policy/baseline/gov/aca/eligibility/has_qualifying_non_marketplace_health_coverage_at_interview.yaml b/policyengine_us/tests/policy/baseline/gov/aca/eligibility/has_qualifying_non_marketplace_health_coverage_at_interview.yaml new file mode 100644 index 00000000000..32f017800d4 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/aca/eligibility/has_qualifying_non_marketplace_health_coverage_at_interview.yaml @@ -0,0 +1,25 @@ +- name: Case 1, Medicaid enrollment counts as qualifying non-Marketplace coverage. + period: 2025 + input: + medicaid_enrolled: true + output: + has_qualifying_non_marketplace_health_coverage_at_interview: true + +- name: Case 2, IHS-only coverage does not count as qualifying non-Marketplace coverage. + period: 2025 + input: + age: 40 + medicaid_enrolled: false + is_chip_eligible: false + medicare_enrolled: false + has_esi: false + reported_has_indian_health_service_coverage_at_interview: true + output: + has_qualifying_non_marketplace_health_coverage_at_interview: false + +- name: Case 3, off-exchange coverage counts as qualifying non-Marketplace coverage. + period: 2025 + input: + reported_has_non_marketplace_direct_purchase_health_coverage_at_interview: true + output: + has_qualifying_non_marketplace_health_coverage_at_interview: true diff --git a/policyengine_us/tests/policy/baseline/gov/hhs/medicaid/medicaid_enrolled.yaml b/policyengine_us/tests/policy/baseline/gov/hhs/medicaid/medicaid_enrolled.yaml index 1d04db468fd..1cf2e79d539 100644 --- a/policyengine_us/tests/policy/baseline/gov/hhs/medicaid/medicaid_enrolled.yaml +++ b/policyengine_us/tests/policy/baseline/gov/hhs/medicaid/medicaid_enrolled.yaml @@ -103,3 +103,28 @@ is_medicaid_eligible: [true] medicaid_enrolled: [true] takes_up_medicaid_if_eligible: [true] + +- name: Case 5, reported Medicaid coverage preserves CA continuous coverage. + period: 2026 + input: + people: + person1: + age: 35 + employment_income: 15_000 + immigration_status: UNDOCUMENTED + reported_has_medicaid_health_coverage_at_interview: true + tax_units: + tax_unit: + members: [person1] + spm_units: + spm_unit: + members: [person1] + households: + household: + members: [person1] + state_code: CA + output: + receives_medicaid: [true] + is_ca_medicaid_immigration_status_eligible: [true] + is_medicaid_eligible: [true] + medicaid_enrolled: [true] diff --git a/policyengine_us/tests/policy/baseline/gov/hhs/medicaid/receives_medicaid.yaml b/policyengine_us/tests/policy/baseline/gov/hhs/medicaid/receives_medicaid.yaml new file mode 100644 index 00000000000..f571a9ca231 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/hhs/medicaid/receives_medicaid.yaml @@ -0,0 +1,13 @@ +- name: Case 1, reported Medicaid coverage feeds current Medicaid receipt. + period: 2026 + input: + reported_has_medicaid_health_coverage_at_interview: true + output: + receives_medicaid: true + +- name: Case 2, Medicaid receipt defaults to false without a reported flag. + period: 2026 + input: + reported_has_medicaid_health_coverage_at_interview: false + output: + receives_medicaid: false diff --git a/policyengine_us/variables/gov/aca/eligibility/coverage_report_model_conflict.py b/policyengine_us/variables/gov/aca/eligibility/coverage_report_model_conflict.py new file mode 100644 index 00000000000..b545dac31b9 --- /dev/null +++ b/policyengine_us/variables/gov/aca/eligibility/coverage_report_model_conflict.py @@ -0,0 +1,17 @@ +from policyengine_us.model_api import * + + +class coverage_report_model_conflict(Variable): + value_type = bool + entity = Person + label = "Reported Marketplace coverage conflicts with modeled qualifying non-Marketplace coverage" + definition_period = YEAR + + def formula(person, period, parameters): + return person( + "reported_has_marketplace_health_coverage_at_interview", + period, + ) & person( + "has_qualifying_non_marketplace_health_coverage_at_interview", + period, + ) diff --git a/policyengine_us/variables/gov/aca/eligibility/has_esi.py b/policyengine_us/variables/gov/aca/eligibility/has_esi.py index 77d070eb042..1a40425810f 100644 --- a/policyengine_us/variables/gov/aca/eligibility/has_esi.py +++ b/policyengine_us/variables/gov/aca/eligibility/has_esi.py @@ -6,3 +6,9 @@ class has_esi(Variable): entity = Person label = "Person currently has ESI" definition_period = YEAR + + def formula(person, period, parameters): + return person( + "reported_has_employer_sponsored_health_coverage_at_interview", + period, + ) diff --git a/policyengine_us/variables/gov/aca/eligibility/has_qualifying_non_marketplace_health_coverage_at_interview.py b/policyengine_us/variables/gov/aca/eligibility/has_qualifying_non_marketplace_health_coverage_at_interview.py new file mode 100644 index 00000000000..1796dcbbe11 --- /dev/null +++ b/policyengine_us/variables/gov/aca/eligibility/has_qualifying_non_marketplace_health_coverage_at_interview.py @@ -0,0 +1,28 @@ +from policyengine_us.model_api import * + + +class has_qualifying_non_marketplace_health_coverage_at_interview(Variable): + value_type = bool + entity = Person + label = "Person has qualifying non-Marketplace health coverage at interview" + definition_period = YEAR + + def formula(person, period, parameters): + return ( + add( + person, + period, + [ + "has_esi", + "medicaid_enrolled", + "is_chip_eligible", + "medicare_enrolled", + "reported_has_non_marketplace_direct_purchase_health_coverage_at_interview", + "reported_has_other_means_tested_health_coverage_at_interview", + "reported_has_tricare_health_coverage_at_interview", + "reported_has_champva_health_coverage_at_interview", + "reported_has_va_health_coverage_at_interview", + ], + ) + > 0 + ) diff --git a/policyengine_us/variables/gov/hhs/medicaid/receives_medicaid.py b/policyengine_us/variables/gov/hhs/medicaid/receives_medicaid.py index 85da705f3d4..d74d2e6d0fc 100644 --- a/policyengine_us/variables/gov/hhs/medicaid/receives_medicaid.py +++ b/policyengine_us/variables/gov/hhs/medicaid/receives_medicaid.py @@ -15,3 +15,9 @@ class receives_medicaid(Variable): This differs from medicaid_enrolled which is computed based on eligibility and takeup rate. """ + + def formula(person, period, parameters): + return person( + "reported_has_medicaid_health_coverage_at_interview", + period, + ) diff --git a/policyengine_us/variables/household/expense/health/has_marketplace_health_coverage.py b/policyengine_us/variables/household/expense/health/has_marketplace_health_coverage.py index 39243b96768..79d99c2570b 100644 --- a/policyengine_us/variables/household/expense/health/has_marketplace_health_coverage.py +++ b/policyengine_us/variables/household/expense/health/has_marketplace_health_coverage.py @@ -4,6 +4,11 @@ class has_marketplace_health_coverage(Variable): value_type = bool entity = Person - label = "Is eligible for health insurance from an ACA Marketplace plan because has no employer-sponsored health insurance coverage." + label = "Person currently has Marketplace health coverage" definition_period = YEAR - default_value = True + + def formula(person, period, parameters): + return person( + "reported_has_marketplace_health_coverage_at_interview", + period, + ) diff --git a/policyengine_us/variables/input/health_coverage.py b/policyengine_us/variables/input/health_coverage.py new file mode 100644 index 00000000000..400366be577 --- /dev/null +++ b/policyengine_us/variables/input/health_coverage.py @@ -0,0 +1,124 @@ +from policyengine_us.model_api import * + + +class ReportedHealthCoverageAtInterview: + value_type = bool + entity = Person + definition_period = YEAR + default_value = False + + +class reported_has_direct_purchase_health_coverage_at_interview( + ReportedHealthCoverageAtInterview, Variable +): + label = "Reported direct-purchase health coverage at interview" + + +class reported_has_marketplace_health_coverage_at_interview( + ReportedHealthCoverageAtInterview, Variable +): + label = "Reported Marketplace health coverage at interview" + + +class reported_has_subsidized_marketplace_health_coverage_at_interview( + ReportedHealthCoverageAtInterview, Variable +): + label = "Reported subsidized Marketplace health coverage at interview" + + +class reported_has_unsubsidized_marketplace_health_coverage_at_interview( + ReportedHealthCoverageAtInterview, Variable +): + label = "Reported unsubsidized Marketplace health coverage at interview" + + +class reported_has_non_marketplace_direct_purchase_health_coverage_at_interview( + ReportedHealthCoverageAtInterview, Variable +): + label = "Reported non-Marketplace direct-purchase health coverage at interview" + + +class reported_has_employer_sponsored_health_coverage_at_interview( + ReportedHealthCoverageAtInterview, Variable +): + label = "Reported employer-sponsored health coverage at interview" + + +class reported_has_medicare_health_coverage_at_interview( + ReportedHealthCoverageAtInterview, Variable +): + label = "Reported Medicare health coverage at interview" + + +class reported_has_medicaid_health_coverage_at_interview( + ReportedHealthCoverageAtInterview, Variable +): + label = "Reported Medicaid health coverage at interview" + + +class reported_has_means_tested_health_coverage_at_interview( + ReportedHealthCoverageAtInterview, Variable +): + label = "Reported means-tested health coverage at interview" + + +class reported_has_chip_health_coverage_at_interview( + ReportedHealthCoverageAtInterview, Variable +): + label = "Reported CHIP health coverage at interview" + + +class reported_has_other_means_tested_health_coverage_at_interview( + ReportedHealthCoverageAtInterview, Variable +): + label = "Reported other means-tested health coverage at interview" + + +class reported_has_tricare_health_coverage_at_interview( + ReportedHealthCoverageAtInterview, Variable +): + label = "Reported TRICARE health coverage at interview" + + +class reported_has_champva_health_coverage_at_interview( + ReportedHealthCoverageAtInterview, Variable +): + label = "Reported CHAMPVA health coverage at interview" + + +class reported_has_va_health_coverage_at_interview( + ReportedHealthCoverageAtInterview, Variable +): + label = "Reported VA health coverage at interview" + + +class reported_has_indian_health_service_coverage_at_interview( + ReportedHealthCoverageAtInterview, Variable +): + label = "Reported Indian Health Service coverage at interview" + + +class reported_has_private_health_coverage_at_interview( + ReportedHealthCoverageAtInterview, Variable +): + label = "Reported private health coverage at interview" + + +class reported_has_public_health_coverage_at_interview( + ReportedHealthCoverageAtInterview, Variable +): + label = "Reported public health coverage at interview" + + +class reported_is_insured_at_interview(ReportedHealthCoverageAtInterview, Variable): + label = "Reported insured at interview" + + +class reported_is_uninsured_at_interview(ReportedHealthCoverageAtInterview, Variable): + label = "Reported uninsured at interview" + + +class reported_has_multiple_health_coverage_at_interview( + ReportedHealthCoverageAtInterview, Variable +): + label = "Reported multiple health coverage types at interview"