From 5110af796bff03086f8c6a6aaa7fd364c11d4366 Mon Sep 17 00:00:00 2001 From: Daphne Hansell <128793799+daphnehanse11@users.noreply.github.com> Date: Wed, 27 May 2026 14:50:48 -0400 Subject: [PATCH 1/6] Model Virginia Medicaid LIFC regional limits --- changelog.d/fixed/5798.md | 1 + .../lifc/income_limit/group1/additional.yaml | 10 ++ .../lifc/income_limit/group1/main.yaml | 28 ++++ .../lifc/income_limit/group2/additional.yaml | 10 ++ .../lifc/income_limit/group2/main.yaml | 28 ++++ .../lifc/income_limit/group3/additional.yaml | 10 ++ .../lifc/income_limit/group3/main.yaml | 28 ++++ .../lifc/income_limit/max_household_size.yaml | 10 ++ .../dmas/medicaid/lifc/localities/group1.yaml | 103 ++++++++++++ .../dmas/medicaid/lifc/localities/group2.yaml | 40 +++++ .../dmas/medicaid/lifc/localities/group3.yaml | 25 +++ .../parent/medicaid_parent_income_limit.yaml | 30 ++++ .../va/dmas/medicaid/lifc/integration.yaml | 152 ++++++++++++++++++ .../lifc/va_medicaid_lifc_income_limit.yaml | 71 ++++++++ .../lifc/va_medicaid_lifc_locality_group.yaml | 55 +++++++ .../parent/is_parent_for_medicaid_fc.py | 6 +- .../parent/medicaid_parent_income_limit.py | 23 +++ .../lifc/va_medicaid_lifc_income_limit.py | 42 +++++ .../lifc/va_medicaid_lifc_locality_group.py | 35 ++++ 19 files changed, 703 insertions(+), 4 deletions(-) create mode 100644 changelog.d/fixed/5798.md create mode 100644 policyengine_us/parameters/gov/states/va/dmas/medicaid/lifc/income_limit/group1/additional.yaml create mode 100644 policyengine_us/parameters/gov/states/va/dmas/medicaid/lifc/income_limit/group1/main.yaml create mode 100644 policyengine_us/parameters/gov/states/va/dmas/medicaid/lifc/income_limit/group2/additional.yaml create mode 100644 policyengine_us/parameters/gov/states/va/dmas/medicaid/lifc/income_limit/group2/main.yaml create mode 100644 policyengine_us/parameters/gov/states/va/dmas/medicaid/lifc/income_limit/group3/additional.yaml create mode 100644 policyengine_us/parameters/gov/states/va/dmas/medicaid/lifc/income_limit/group3/main.yaml create mode 100644 policyengine_us/parameters/gov/states/va/dmas/medicaid/lifc/income_limit/max_household_size.yaml create mode 100644 policyengine_us/parameters/gov/states/va/dmas/medicaid/lifc/localities/group1.yaml create mode 100644 policyengine_us/parameters/gov/states/va/dmas/medicaid/lifc/localities/group2.yaml create mode 100644 policyengine_us/parameters/gov/states/va/dmas/medicaid/lifc/localities/group3.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/hhs/medicaid/eligibility/categories/parent/medicaid_parent_income_limit.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/va/dmas/medicaid/lifc/integration.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/va/dmas/medicaid/lifc/va_medicaid_lifc_income_limit.yaml create mode 100644 policyengine_us/tests/policy/baseline/gov/states/va/dmas/medicaid/lifc/va_medicaid_lifc_locality_group.yaml create mode 100644 policyengine_us/variables/gov/hhs/medicaid/eligibility/categories/parent/medicaid_parent_income_limit.py create mode 100644 policyengine_us/variables/gov/states/va/dmas/medicaid/lifc/va_medicaid_lifc_income_limit.py create mode 100644 policyengine_us/variables/gov/states/va/dmas/medicaid/lifc/va_medicaid_lifc_locality_group.py diff --git a/changelog.d/fixed/5798.md b/changelog.d/fixed/5798.md new file mode 100644 index 00000000000..a4e3d628131 --- /dev/null +++ b/changelog.d/fixed/5798.md @@ -0,0 +1 @@ +Model Virginia Medicaid parent eligibility limits by LIFC locality group. diff --git a/policyengine_us/parameters/gov/states/va/dmas/medicaid/lifc/income_limit/group1/additional.yaml b/policyengine_us/parameters/gov/states/va/dmas/medicaid/lifc/income_limit/group1/additional.yaml new file mode 100644 index 00000000000..6aa0022ef4a --- /dev/null +++ b/policyengine_us/parameters/gov/states/va/dmas/medicaid/lifc/income_limit/group1/additional.yaml @@ -0,0 +1,10 @@ +description: Virginia adds this amount for each additional person under the Medicaid Low Income Families With Children program. +values: + 2025-07-01: 1_668 +metadata: + unit: currency-USD + period: year + label: Virginia Medicaid LIFC Group I additional-person income limit + reference: + - title: Virginia Medical Assistance Eligibility Manual, Chapter M04, Appendix 3 + href: https://www.dmas.virginia.gov/media/0aynyhxk/m04-1-1-26a.pdf#page=51 diff --git a/policyengine_us/parameters/gov/states/va/dmas/medicaid/lifc/income_limit/group1/main.yaml b/policyengine_us/parameters/gov/states/va/dmas/medicaid/lifc/income_limit/group1/main.yaml new file mode 100644 index 00000000000..315047d5c86 --- /dev/null +++ b/policyengine_us/parameters/gov/states/va/dmas/medicaid/lifc/income_limit/group1/main.yaml @@ -0,0 +1,28 @@ +description: Virginia limits income to this amount under the Medicaid Low Income Families With Children program. +1: + 2025-07-01: 3_948 +2: + 2025-07-01: 5_940 +3: + 2025-07-01: 7_584 +4: + 2025-07-01: 9_168 +5: + 2025-07-01: 10_752 +6: + 2025-07-01: 12_120 +7: + 2025-07-01: 13_704 +8: + 2025-07-01: 15_312 +metadata: + unit: currency-USD + period: year + label: Virginia Medicaid LIFC Group I income limit + breakdown: + - range(1, 9) + breakdown_label: + - Household size + reference: + - title: Virginia Medical Assistance Eligibility Manual, Chapter M04, Appendix 3 + href: https://www.dmas.virginia.gov/media/0aynyhxk/m04-1-1-26a.pdf#page=51 diff --git a/policyengine_us/parameters/gov/states/va/dmas/medicaid/lifc/income_limit/group2/additional.yaml b/policyengine_us/parameters/gov/states/va/dmas/medicaid/lifc/income_limit/group2/additional.yaml new file mode 100644 index 00000000000..f939dca61d7 --- /dev/null +++ b/policyengine_us/parameters/gov/states/va/dmas/medicaid/lifc/income_limit/group2/additional.yaml @@ -0,0 +1,10 @@ +description: Virginia adds this amount for each additional person under the Medicaid Low Income Families With Children program. +values: + 2025-07-01: 1_860 +metadata: + unit: currency-USD + period: year + label: Virginia Medicaid LIFC Group II additional-person income limit + reference: + - title: Virginia Medical Assistance Eligibility Manual, Chapter M04, Appendix 3 + href: https://www.dmas.virginia.gov/media/0aynyhxk/m04-1-1-26a.pdf#page=51 diff --git a/policyengine_us/parameters/gov/states/va/dmas/medicaid/lifc/income_limit/group2/main.yaml b/policyengine_us/parameters/gov/states/va/dmas/medicaid/lifc/income_limit/group2/main.yaml new file mode 100644 index 00000000000..296e9968df1 --- /dev/null +++ b/policyengine_us/parameters/gov/states/va/dmas/medicaid/lifc/income_limit/group2/main.yaml @@ -0,0 +1,28 @@ +description: Virginia limits income to this amount under the Medicaid Low Income Families With Children program. +1: + 2025-07-01: 5_148 +2: + 2025-07-01: 7_308 +3: + 2025-07-01: 9_204 +4: + 2025-07-01: 10_956 +5: + 2025-07-01: 12_900 +6: + 2025-07-01: 14_508 +7: + 2025-07-01: 16_260 +8: + 2025-07-01: 18_120 +metadata: + unit: currency-USD + period: year + label: Virginia Medicaid LIFC Group II income limit + breakdown: + - range(1, 9) + breakdown_label: + - Household size + reference: + - title: Virginia Medical Assistance Eligibility Manual, Chapter M04, Appendix 3 + href: https://www.dmas.virginia.gov/media/0aynyhxk/m04-1-1-26a.pdf#page=51 diff --git a/policyengine_us/parameters/gov/states/va/dmas/medicaid/lifc/income_limit/group3/additional.yaml b/policyengine_us/parameters/gov/states/va/dmas/medicaid/lifc/income_limit/group3/additional.yaml new file mode 100644 index 00000000000..af6c6e059a4 --- /dev/null +++ b/policyengine_us/parameters/gov/states/va/dmas/medicaid/lifc/income_limit/group3/additional.yaml @@ -0,0 +1,10 @@ +description: Virginia adds this amount for each additional person under the Medicaid Low Income Families With Children program. +values: + 2025-07-01: 2_256 +metadata: + unit: currency-USD + period: year + label: Virginia Medicaid LIFC Group III additional-person income limit + reference: + - title: Virginia Medical Assistance Eligibility Manual, Chapter M04, Appendix 3 + href: https://www.dmas.virginia.gov/media/0aynyhxk/m04-1-1-26a.pdf#page=51 diff --git a/policyengine_us/parameters/gov/states/va/dmas/medicaid/lifc/income_limit/group3/main.yaml b/policyengine_us/parameters/gov/states/va/dmas/medicaid/lifc/income_limit/group3/main.yaml new file mode 100644 index 00000000000..a94406c8abb --- /dev/null +++ b/policyengine_us/parameters/gov/states/va/dmas/medicaid/lifc/income_limit/group3/main.yaml @@ -0,0 +1,28 @@ +description: Virginia limits income to this amount under the Medicaid Low Income Families With Children program. +1: + 2025-07-01: 7_704 +2: + 2025-07-01: 10_308 +3: + 2025-07-01: 12_576 +4: + 2025-07-01: 14_712 +5: + 2025-07-01: 17_424 +6: + 2025-07-01: 19_356 +7: + 2025-07-01: 21_516 +8: + 2025-07-01: 23_796 +metadata: + unit: currency-USD + period: year + label: Virginia Medicaid LIFC Group III income limit + breakdown: + - range(1, 9) + breakdown_label: + - Household size + reference: + - title: Virginia Medical Assistance Eligibility Manual, Chapter M04, Appendix 3 + href: https://www.dmas.virginia.gov/media/0aynyhxk/m04-1-1-26a.pdf#page=51 diff --git a/policyengine_us/parameters/gov/states/va/dmas/medicaid/lifc/income_limit/max_household_size.yaml b/policyengine_us/parameters/gov/states/va/dmas/medicaid/lifc/income_limit/max_household_size.yaml new file mode 100644 index 00000000000..66a047ed9d1 --- /dev/null +++ b/policyengine_us/parameters/gov/states/va/dmas/medicaid/lifc/income_limit/max_household_size.yaml @@ -0,0 +1,10 @@ +description: Virginia uses this household-size threshold for listed income limits under the Medicaid Low Income Families With Children program. +values: + 2025-07-01: 8 +metadata: + unit: person + period: year + label: Virginia Medicaid LIFC listed income limit household-size threshold + reference: + - title: Virginia Medical Assistance Eligibility Manual, Chapter M04, Appendix 3 + href: https://www.dmas.virginia.gov/media/0aynyhxk/m04-1-1-26a.pdf#page=51 diff --git a/policyengine_us/parameters/gov/states/va/dmas/medicaid/lifc/localities/group1.yaml b/policyengine_us/parameters/gov/states/va/dmas/medicaid/lifc/localities/group1.yaml new file mode 100644 index 00000000000..c6a9a3848ec --- /dev/null +++ b/policyengine_us/parameters/gov/states/va/dmas/medicaid/lifc/localities/group1.yaml @@ -0,0 +1,103 @@ +description: Virginia assigns these localities to Group I under the Medicaid Low Income Families With Children program. +values: + 2017-07-01: + # Counties + - ACCOMACK_COUNTY_VA + - ALLEGHANY_COUNTY_VA + - AMELIA_COUNTY_VA + - AMHERST_COUNTY_VA + - APPOMATTOX_COUNTY_VA + - BATH_COUNTY_VA + - BEDFORD_COUNTY_VA + - BLAND_COUNTY_VA + - BOTETOURT_COUNTY_VA + - BRUNSWICK_COUNTY_VA + - BUCHANAN_COUNTY_VA + - BUCKINGHAM_COUNTY_VA + - CAMPBELL_COUNTY_VA + - CAROLINE_COUNTY_VA + - CARROLL_COUNTY_VA + - CHARLES_CITY_COUNTY_VA + - CHARLOTTE_COUNTY_VA + - CLARKE_COUNTY_VA + - CRAIG_COUNTY_VA + - CULPEPER_COUNTY_VA + - CUMBERLAND_COUNTY_VA + - DICKENSON_COUNTY_VA + - DINWIDDIE_COUNTY_VA + - ESSEX_COUNTY_VA + - FAUQUIER_COUNTY_VA + - FLOYD_COUNTY_VA + - FLUVANNA_COUNTY_VA + - FRANKLIN_COUNTY_VA + - FREDERICK_COUNTY_VA + - GILES_COUNTY_VA + - GLOUCESTER_COUNTY_VA + - GOOCHLAND_COUNTY_VA + - GRAYSON_COUNTY_VA + - GREENE_COUNTY_VA + - GREENSVILLE_COUNTY_VA + - HALIFAX_COUNTY_VA + - HANOVER_COUNTY_VA + - HENRY_COUNTY_VA + - HIGHLAND_COUNTY_VA + - ISLE_OF_WIGHT_COUNTY_VA + - JAMES_CITY_COUNTY_VA + - KING_GEORGE_COUNTY_VA + - KING_AND_QUEEN_COUNTY_VA + - KING_WILLIAM_COUNTY_VA + - LANCASTER_COUNTY_VA + - LEE_COUNTY_VA + - LOUISA_COUNTY_VA + - LUNENBURG_COUNTY_VA + - MADISON_COUNTY_VA + - MATHEWS_COUNTY_VA + - MECKLENBURG_COUNTY_VA + - MIDDLESEX_COUNTY_VA + - NELSON_COUNTY_VA + - NEW_KENT_COUNTY_VA + - NORTHAMPTON_COUNTY_VA + - NORTHUMBERLAND_COUNTY_VA + - NOTTOWAY_COUNTY_VA + - ORANGE_COUNTY_VA + - PAGE_COUNTY_VA + - PATRICK_COUNTY_VA + - PITTSYLVANIA_COUNTY_VA + - POWHATAN_COUNTY_VA + - PRINCE_EDWARD_COUNTY_VA + - PRINCE_GEORGE_COUNTY_VA + - PULASKI_COUNTY_VA + - RAPPAHANNOCK_COUNTY_VA + - RICHMOND_COUNTY_VA + - ROCKBRIDGE_COUNTY_VA + - RUSSELL_COUNTY_VA + - SCOTT_COUNTY_VA + - SHENANDOAH_COUNTY_VA + - SMYTH_COUNTY_VA + - SOUTHAMPTON_COUNTY_VA + - SPOTSYLVANIA_COUNTY_VA + - STAFFORD_COUNTY_VA + - SURRY_COUNTY_VA + - SUSSEX_COUNTY_VA + - TAZEWELL_COUNTY_VA + - WASHINGTON_COUNTY_VA + - WESTMORELAND_COUNTY_VA + - WISE_COUNTY_VA + - WYTHE_COUNTY_VA + - YORK_COUNTY_VA + # Cities + - BRISTOL_CITY_VA + - BUENA_VISTA_CITY_VA + - DANVILLE_CITY_VA + - EMPORIA_CITY_VA + - FRANKLIN_CITY_VA + - GALAX_CITY_VA + - NORTON_CITY_VA + - SUFFOLK_CITY_VA +metadata: + unit: list + period: year + label: Virginia Medicaid LIFC Group I localities + reference: + - title: Virginia Medical Assistance Eligibility Manual, Chapter M04, Appendix 4 + href: https://www.dmas.virginia.gov/media/0aynyhxk/m04-1-1-26a.pdf#page=52 diff --git a/policyengine_us/parameters/gov/states/va/dmas/medicaid/lifc/localities/group2.yaml b/policyengine_us/parameters/gov/states/va/dmas/medicaid/lifc/localities/group2.yaml new file mode 100644 index 00000000000..86df20b422d --- /dev/null +++ b/policyengine_us/parameters/gov/states/va/dmas/medicaid/lifc/localities/group2.yaml @@ -0,0 +1,40 @@ +description: Virginia assigns these localities to Group II under the Medicaid Low Income Families With Children program. +values: + 2017-07-01: + # Counties + - ALBEMARLE_COUNTY_VA + - AUGUSTA_COUNTY_VA + - CHESTERFIELD_COUNTY_VA + - HENRICO_COUNTY_VA + - LOUDOUN_COUNTY_VA + - ROANOKE_COUNTY_VA + - ROCKINGHAM_COUNTY_VA + - WARREN_COUNTY_VA + # Cities + - CHESAPEAKE_CITY_VA + - COVINGTON_CITY_VA + - HARRISONBURG_CITY_VA + - HOPEWELL_CITY_VA + - LEXINGTON_CITY_VA + - LYNCHBURG_CITY_VA + - MARTINSVILLE_CITY_VA + - NEWPORT_NEWS_CITY_VA + - NORFOLK_CITY_VA + - PETERSBURG_CITY_VA + - PORTSMOUTH_CITY_VA + - POQUOSON_CITY_VA + - RADFORD_CITY_VA + - RICHMOND_CITY_VA + - ROANOKE_CITY_VA + - SALEM_CITY_VA + - STAUNTON_CITY_VA + - VIRGINIA_BEACH_CITY_VA + - WILLIAMSBURG_CITY_VA + - WINCHESTER_CITY_VA +metadata: + unit: list + period: year + label: Virginia Medicaid LIFC Group II localities + reference: + - title: Virginia Medical Assistance Eligibility Manual, Chapter M04, Appendix 4 + href: https://www.dmas.virginia.gov/media/0aynyhxk/m04-1-1-26a.pdf#page=52 diff --git a/policyengine_us/parameters/gov/states/va/dmas/medicaid/lifc/localities/group3.yaml b/policyengine_us/parameters/gov/states/va/dmas/medicaid/lifc/localities/group3.yaml new file mode 100644 index 00000000000..d525fdf7027 --- /dev/null +++ b/policyengine_us/parameters/gov/states/va/dmas/medicaid/lifc/localities/group3.yaml @@ -0,0 +1,25 @@ +description: Virginia assigns these localities to Group III under the Medicaid Low Income Families With Children program. +values: + 2017-07-01: + # Counties + - ARLINGTON_COUNTY_VA + - FAIRFAX_COUNTY_VA + - MONTGOMERY_COUNTY_VA + - PRINCE_WILLIAM_COUNTY_VA + # Cities + - ALEXANDRIA_CITY_VA + - CHARLOTTESVILLE_CITY_VA + - COLONIAL_HEIGHTS_CITY_VA + - FALLS_CHURCH_CITY_VA + - FREDERICKSBURG_CITY_VA + - HAMPTON_CITY_VA + - MANASSAS_CITY_VA + - MANASSAS_PARK_CITY_VA + - WAYNESBORO_CITY_VA +metadata: + unit: list + period: year + label: Virginia Medicaid LIFC Group III localities + reference: + - title: Virginia Medical Assistance Eligibility Manual, Chapter M04, Appendix 4 + href: https://www.dmas.virginia.gov/media/0aynyhxk/m04-1-1-26a.pdf#page=52 diff --git a/policyengine_us/tests/policy/baseline/gov/hhs/medicaid/eligibility/categories/parent/medicaid_parent_income_limit.yaml b/policyengine_us/tests/policy/baseline/gov/hhs/medicaid/eligibility/categories/parent/medicaid_parent_income_limit.yaml new file mode 100644 index 00000000000..fa7926c9eaa --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/hhs/medicaid/eligibility/categories/parent/medicaid_parent_income_limit.yaml @@ -0,0 +1,30 @@ +- name: Case 1, California uses the national parent Medicaid income limit table. + period: 2026 + input: + people: + person1: + age: 30 + households: + household: + members: [person1] + state_code: CA + output: + medicaid_parent_income_limit: 1.14 + absolute_error_margin: 0.0001 + +- name: Case 2, Virginia Group II uses the regional LIFC income limit. + period: 2026 + input: + people: + person1: + age: 30 + medicaid_household_size: 3 + households: + household: + members: [person1] + state_code: VA + state_group_str: CONTIGUOUS_US + county: HENRICO_COUNTY_VA + output: + medicaid_parent_income_limit: 0.336896 + absolute_error_margin: 0.0001 diff --git a/policyengine_us/tests/policy/baseline/gov/states/va/dmas/medicaid/lifc/integration.yaml b/policyengine_us/tests/policy/baseline/gov/states/va/dmas/medicaid/lifc/integration.yaml new file mode 100644 index 00000000000..56bb7ae6086 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/va/dmas/medicaid/lifc/integration.yaml @@ -0,0 +1,152 @@ +- name: Case 1, Group II parent at the LIFC threshold is parent eligible. + period: 2026 + input: + people: + person1: + age: 30 + employment_income: 9_204 + is_tax_unit_head: true + person2: + age: 7 + is_tax_unit_dependent: true + person3: + age: 5 + is_tax_unit_dependent: true + tax_units: + tax_unit: + members: [person1, person2, person3] + tax_unit_count_dependents: 2 + households: + household: + members: [person1, person2, person3] + state_code: VA + state_group_str: CONTIGUOUS_US + county: HENRICO_COUNTY_VA + output: + is_parent_for_medicaid: [true, false, false] + +- name: Case 2, Group II parent above the LIFC threshold is not parent eligible. + period: 2026 + input: + people: + person1: + age: 30 + employment_income: 9_205 + is_tax_unit_head: true + person2: + age: 7 + is_tax_unit_dependent: true + person3: + age: 5 + is_tax_unit_dependent: true + tax_units: + tax_unit: + members: [person1, person2, person3] + tax_unit_count_dependents: 2 + households: + household: + members: [person1, person2, person3] + state_code: VA + state_group_str: CONTIGUOUS_US + county: HENRICO_COUNTY_VA + output: + is_parent_for_medicaid: [false, false, false] + +- name: Case 3, Group III parent qualifies at income above the Group II threshold. + period: 2026 + input: + people: + person1: + age: 30 + employment_income: 12_576 + is_tax_unit_head: true + person2: + age: 7 + is_tax_unit_dependent: true + person3: + age: 5 + is_tax_unit_dependent: true + tax_units: + tax_unit: + members: [person1, person2, person3] + tax_unit_count_dependents: 2 + households: + household: + members: [person1, person2, person3] + state_code: VA + state_group_str: CONTIGUOUS_US + county: ARLINGTON_COUNTY_VA + output: + is_parent_for_medicaid: [true, false, false] + +- name: Case 4, Group I parent with the same income is not parent eligible. + period: 2026 + input: + people: + person1: + age: 30 + employment_income: 12_576 + is_tax_unit_head: true + person2: + age: 7 + is_tax_unit_dependent: true + person3: + age: 5 + is_tax_unit_dependent: true + tax_units: + tax_unit: + members: [person1, person2, person3] + tax_unit_count_dependents: 2 + households: + household: + members: [person1, person2, person3] + state_code: VA + state_group_str: CONTIGUOUS_US + county: ACCOMACK_COUNTY_VA + output: + is_parent_for_medicaid: [false, false, false] + +- name: Case 5, Group III household above the listed table size uses the additional-person amount. + period: 2026 + input: + people: + person1: + age: 30 + employment_income: 26_052 + is_tax_unit_head: true + person2: + age: 7 + is_tax_unit_dependent: true + person3: + age: 6 + is_tax_unit_dependent: true + person4: + age: 5 + is_tax_unit_dependent: true + person5: + age: 4 + is_tax_unit_dependent: true + person6: + age: 3 + is_tax_unit_dependent: true + person7: + age: 2 + is_tax_unit_dependent: true + person8: + age: 1 + is_tax_unit_dependent: true + person9: + age: 0 + is_tax_unit_dependent: true + tax_units: + tax_unit: + members: [person1, person2, person3, person4, person5, person6, person7, person8, person9] + tax_unit_count_dependents: 8 + households: + household: + members: [person1, person2, person3, person4, person5, person6, person7, person8, person9] + state_code: VA + state_group_str: CONTIGUOUS_US + county: ARLINGTON_COUNTY_VA + output: + is_parent_for_medicaid: [true, false, false, false, false, false, false, false, false] diff --git a/policyengine_us/tests/policy/baseline/gov/states/va/dmas/medicaid/lifc/va_medicaid_lifc_income_limit.yaml b/policyengine_us/tests/policy/baseline/gov/states/va/dmas/medicaid/lifc/va_medicaid_lifc_income_limit.yaml new file mode 100644 index 00000000000..61efe9d574d --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/va/dmas/medicaid/lifc/va_medicaid_lifc_income_limit.yaml @@ -0,0 +1,71 @@ +- name: Case 1, Group I household of three. + period: 2026 + input: + people: + person1: + age: 30 + medicaid_household_size: 3 + households: + household: + members: [person1] + state_code: VA + state_group_str: CONTIGUOUS_US + county: ACCOMACK_COUNTY_VA + output: + # $7,584 / 2026 FPG for 3 ($27,320). + va_medicaid_lifc_income_limit: 0.277599 + absolute_error_margin: 0.0001 + +- name: Case 2, Group II household of three. + period: 2026 + input: + people: + person1: + age: 30 + medicaid_household_size: 3 + households: + household: + members: [person1] + state_code: VA + state_group_str: CONTIGUOUS_US + county: HENRICO_COUNTY_VA + output: + # $9,204 / 2026 FPG for 3 ($27,320). + va_medicaid_lifc_income_limit: 0.336896 + absolute_error_margin: 0.0001 + +- name: Case 3, Group III household of three. + period: 2026 + input: + people: + person1: + age: 30 + medicaid_household_size: 3 + households: + household: + members: [person1] + state_code: VA + state_group_str: CONTIGUOUS_US + county: ARLINGTON_COUNTY_VA + output: + # $12,576 / 2026 FPG for 3 ($27,320). + va_medicaid_lifc_income_limit: 0.460322 + absolute_error_margin: 0.0001 + +- name: Case 4, Group III household above the listed table size. + period: 2026 + input: + people: + person1: + age: 30 + medicaid_household_size: 9 + households: + household: + members: [person1] + state_code: VA + state_group_str: CONTIGUOUS_US + county: ARLINGTON_COUNTY_VA + output: + # ($23,796 + $2,256) / 2026 FPG for 9 ($61,400). + va_medicaid_lifc_income_limit: 0.4243 + absolute_error_margin: 0.0001 diff --git a/policyengine_us/tests/policy/baseline/gov/states/va/dmas/medicaid/lifc/va_medicaid_lifc_locality_group.yaml b/policyengine_us/tests/policy/baseline/gov/states/va/dmas/medicaid/lifc/va_medicaid_lifc_locality_group.yaml new file mode 100644 index 00000000000..74d840664e7 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/va/dmas/medicaid/lifc/va_medicaid_lifc_locality_group.yaml @@ -0,0 +1,55 @@ +- name: Case 1, Group I county. + period: 2026 + input: + people: + person1: + age: 30 + households: + household: + members: [person1] + state_code: VA + county: ACCOMACK_COUNTY_VA + output: + va_medicaid_lifc_locality_group: GROUP_I + +- name: Case 2, Group II county. + period: 2026 + input: + people: + person1: + age: 30 + households: + household: + members: [person1] + state_code: VA + county: HENRICO_COUNTY_VA + output: + va_medicaid_lifc_locality_group: GROUP_II + +- name: Case 3, Group III county. + period: 2026 + input: + people: + person1: + age: 30 + households: + household: + members: [person1] + state_code: VA + county: ARLINGTON_COUNTY_VA + output: + va_medicaid_lifc_locality_group: GROUP_III + +- name: Case 4, Group III city. + period: 2026 + input: + people: + person1: + age: 30 + households: + household: + members: [person1] + state_code: VA + county: ALEXANDRIA_CITY_VA + output: + va_medicaid_lifc_locality_group: GROUP_III diff --git a/policyengine_us/variables/gov/hhs/medicaid/eligibility/categories/parent/is_parent_for_medicaid_fc.py b/policyengine_us/variables/gov/hhs/medicaid/eligibility/categories/parent/is_parent_for_medicaid_fc.py index 55ff04fce80..5ffd84349ab 100644 --- a/policyengine_us/variables/gov/hhs/medicaid/eligibility/categories/parent/is_parent_for_medicaid_fc.py +++ b/policyengine_us/variables/gov/hhs/medicaid/eligibility/categories/parent/is_parent_for_medicaid_fc.py @@ -8,8 +8,6 @@ class is_parent_for_medicaid_fc(Variable): definition_period = YEAR def formula(person, period, parameters): - ma = parameters(period).gov.hhs.medicaid.eligibility.categories.parent income = person("medicaid_income_level", period) - state = person.household("state_code_str", period) - income_limit = ma.income_limit[state] - return income < income_limit + income_limit = person("medicaid_parent_income_limit", period) + return income <= income_limit diff --git a/policyengine_us/variables/gov/hhs/medicaid/eligibility/categories/parent/medicaid_parent_income_limit.py b/policyengine_us/variables/gov/hhs/medicaid/eligibility/categories/parent/medicaid_parent_income_limit.py new file mode 100644 index 00000000000..232f100fe54 --- /dev/null +++ b/policyengine_us/variables/gov/hhs/medicaid/eligibility/categories/parent/medicaid_parent_income_limit.py @@ -0,0 +1,23 @@ +from policyengine_us.model_api import * + + +class medicaid_parent_income_limit(Variable): + value_type = float + entity = Person + label = "Medicaid parent income limit" + unit = "/1" + definition_period = YEAR + reference = ( + "https://www.law.cornell.edu/cfr/text/42/435.110", + "https://www.dmas.virginia.gov/media/0aynyhxk/m04-1-1-26a.pdf#page=51", + ) + + def formula(person, period, parameters): + p = parameters(period).gov.hhs.medicaid.eligibility.categories.parent + state = person.household("state_code_str", period) + state_code = person.household("state_code", period) + return where( + state_code == StateCode.VA, + person("va_medicaid_lifc_income_limit", period), + p.income_limit[state], + ) diff --git a/policyengine_us/variables/gov/states/va/dmas/medicaid/lifc/va_medicaid_lifc_income_limit.py b/policyengine_us/variables/gov/states/va/dmas/medicaid/lifc/va_medicaid_lifc_income_limit.py new file mode 100644 index 00000000000..49814fc523e --- /dev/null +++ b/policyengine_us/variables/gov/states/va/dmas/medicaid/lifc/va_medicaid_lifc_income_limit.py @@ -0,0 +1,42 @@ +from policyengine_us.model_api import * +from policyengine_us.variables.gov.hhs.tax_unit_fpg import fpg + + +class va_medicaid_lifc_income_limit(Variable): + value_type = float + entity = Person + label = "Virginia Medicaid LIFC income limit" + unit = "/1" + definition_period = YEAR + defined_for = StateCode.VA + reference = "https://www.dmas.virginia.gov/media/0aynyhxk/m04-1-1-26a.pdf#page=51" + + def formula(person, period, parameters): + p = parameters(period).gov.states.va.dmas.medicaid.lifc.income_limit + size = person("medicaid_household_size", period) + capped_size = min_(size, p.max_household_size).astype(int) + additional_people = max_(size - p.max_household_size, 0) + + group1_limit = ( + p.group1.main[capped_size] + additional_people * p.group1.additional + ) + group2_limit = ( + p.group2.main[capped_size] + additional_people * p.group2.additional + ) + group3_limit = ( + p.group3.main[capped_size] + additional_people * p.group3.additional + ) + + locality_group = person.household("va_medicaid_lifc_locality_group", period) + groups = locality_group.possible_values + income_limit = select( + [ + locality_group == groups.GROUP_I, + locality_group == groups.GROUP_II, + locality_group == groups.GROUP_III, + ], + [group1_limit, group2_limit, group3_limit], + default=group1_limit, + ) + state_group = person.household("state_group_str", period) + return income_limit / fpg(size, state_group, period, parameters) diff --git a/policyengine_us/variables/gov/states/va/dmas/medicaid/lifc/va_medicaid_lifc_locality_group.py b/policyengine_us/variables/gov/states/va/dmas/medicaid/lifc/va_medicaid_lifc_locality_group.py new file mode 100644 index 00000000000..f38b8b4f960 --- /dev/null +++ b/policyengine_us/variables/gov/states/va/dmas/medicaid/lifc/va_medicaid_lifc_locality_group.py @@ -0,0 +1,35 @@ +from policyengine_us.model_api import * + + +class VAMedicaidLIFCLocalityGroup(Enum): + GROUP_I = "Group I" + GROUP_II = "Group II" + GROUP_III = "Group III" + + +class va_medicaid_lifc_locality_group(Variable): + value_type = Enum + possible_values = VAMedicaidLIFCLocalityGroup + default_value = VAMedicaidLIFCLocalityGroup.GROUP_I + entity = Household + label = "Virginia Medicaid LIFC locality group" + definition_period = YEAR + defined_for = StateCode.VA + reference = "https://www.dmas.virginia.gov/media/0aynyhxk/m04-1-1-26a.pdf#page=52" + + def formula(household, period, parameters): + county = household("county_str", period) + p = parameters(period).gov.states.va.dmas.medicaid.lifc.localities + return select( + [ + np.isin(county, p.group1), + np.isin(county, p.group2), + np.isin(county, p.group3), + ], + [ + VAMedicaidLIFCLocalityGroup.GROUP_I, + VAMedicaidLIFCLocalityGroup.GROUP_II, + VAMedicaidLIFCLocalityGroup.GROUP_III, + ], + default=VAMedicaidLIFCLocalityGroup.GROUP_I, + ) From 0ae7029b8dcec9f4930a2c92549d25df12dea576 Mon Sep 17 00:00:00 2001 From: Daphne Hansell <128793799+daphnehanse11@users.noreply.github.com> Date: Fri, 29 May 2026 16:11:03 -0400 Subject: [PATCH 2/6] Fix Virginia Medicaid LIFC fallback --- .../states/va/dmas/medicaid/lifc/in_effect.yaml | 13 +++++++++++++ .../parent/medicaid_parent_income_limit.yaml | 17 +++++++++++++++++ .../parent/medicaid_parent_income_limit.py | 13 ++++++++----- 3 files changed, 38 insertions(+), 5 deletions(-) create mode 100644 policyengine_us/parameters/gov/states/va/dmas/medicaid/lifc/in_effect.yaml diff --git a/policyengine_us/parameters/gov/states/va/dmas/medicaid/lifc/in_effect.yaml b/policyengine_us/parameters/gov/states/va/dmas/medicaid/lifc/in_effect.yaml new file mode 100644 index 00000000000..6269811db1a --- /dev/null +++ b/policyengine_us/parameters/gov/states/va/dmas/medicaid/lifc/in_effect.yaml @@ -0,0 +1,13 @@ +description: Virginia uses this indicator to determine whether regional Low Income Families With Children income limits apply under Medicaid. + +values: + 0000-01-01: false + 2025-07-01: true + +metadata: + unit: bool + period: year + label: Virginia Medicaid LIFC regional income limits in effect + reference: + - title: Virginia Medical Assistance Eligibility Manual, Chapter M04, Appendix 3 + href: https://www.dmas.virginia.gov/media/0aynyhxk/m04-1-1-26a.pdf#page=51 diff --git a/policyengine_us/tests/policy/baseline/gov/hhs/medicaid/eligibility/categories/parent/medicaid_parent_income_limit.yaml b/policyengine_us/tests/policy/baseline/gov/hhs/medicaid/eligibility/categories/parent/medicaid_parent_income_limit.yaml index fa7926c9eaa..0e7ca838948 100644 --- a/policyengine_us/tests/policy/baseline/gov/hhs/medicaid/eligibility/categories/parent/medicaid_parent_income_limit.yaml +++ b/policyengine_us/tests/policy/baseline/gov/hhs/medicaid/eligibility/categories/parent/medicaid_parent_income_limit.yaml @@ -28,3 +28,20 @@ output: medicaid_parent_income_limit: 0.336896 absolute_error_margin: 0.0001 + +- name: Case 3, Virginia keeps the statewide parent income limit before LIFC tables. + period: 2024 + input: + people: + person1: + age: 30 + medicaid_household_size: 3 + households: + household: + members: [person1] + state_code: VA + state_group_str: CONTIGUOUS_US + county: ARLINGTON_COUNTY_VA + output: + medicaid_parent_income_limit: 0.54 + absolute_error_margin: 0.0001 diff --git a/policyengine_us/variables/gov/hhs/medicaid/eligibility/categories/parent/medicaid_parent_income_limit.py b/policyengine_us/variables/gov/hhs/medicaid/eligibility/categories/parent/medicaid_parent_income_limit.py index 232f100fe54..0b95be16658 100644 --- a/policyengine_us/variables/gov/hhs/medicaid/eligibility/categories/parent/medicaid_parent_income_limit.py +++ b/policyengine_us/variables/gov/hhs/medicaid/eligibility/categories/parent/medicaid_parent_income_limit.py @@ -14,10 +14,13 @@ class medicaid_parent_income_limit(Variable): def formula(person, period, parameters): p = parameters(period).gov.hhs.medicaid.eligibility.categories.parent + va_lifc = parameters(period).gov.states.va.dmas.medicaid.lifc state = person.household("state_code_str", period) state_code = person.household("state_code", period) - return where( - state_code == StateCode.VA, - person("va_medicaid_lifc_income_limit", period), - p.income_limit[state], - ) + if va_lifc.in_effect: + return where( + state_code == StateCode.VA, + person("va_medicaid_lifc_income_limit", period), + p.income_limit[state], + ) + return p.income_limit[state] From f1fda867129c91ec0f41b3d0d1c1412e93c893ce Mon Sep 17 00:00:00 2001 From: Daphne Hansell <128793799+daphnehanse11@users.noreply.github.com> Date: Fri, 29 May 2026 16:12:08 -0400 Subject: [PATCH 3/6] Add Virginia Medicaid changelog fragment --- changelog.d/fixed/8524.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/fixed/8524.md diff --git a/changelog.d/fixed/8524.md b/changelog.d/fixed/8524.md new file mode 100644 index 00000000000..8a6606746d1 --- /dev/null +++ b/changelog.d/fixed/8524.md @@ -0,0 +1 @@ +Prevent Virginia Medicaid LIFC locality limits from applying before their effective date. From 544722c76435fa70d00513346a795b7cb269b128 Mon Sep 17 00:00:00 2001 From: Daphne Hansell <128793799+daphnehanse11@users.noreply.github.com> Date: Mon, 1 Jun 2026 15:50:34 -0400 Subject: [PATCH 4/6] Fix Virginia Medicaid review findings --- changelog.d/{fixed/5798.md => 5798.fixed.md} | 0 changelog.d/{fixed/8524.md => 8524.fixed.md} | 0 .../va/dmas/medicaid/lifc/localities/group3.yaml | 1 + .../lifc/va_medicaid_lifc_locality_group.yaml | 14 ++++++++++++++ 4 files changed, 15 insertions(+) rename changelog.d/{fixed/5798.md => 5798.fixed.md} (100%) rename changelog.d/{fixed/8524.md => 8524.fixed.md} (100%) diff --git a/changelog.d/fixed/5798.md b/changelog.d/5798.fixed.md similarity index 100% rename from changelog.d/fixed/5798.md rename to changelog.d/5798.fixed.md diff --git a/changelog.d/fixed/8524.md b/changelog.d/8524.fixed.md similarity index 100% rename from changelog.d/fixed/8524.md rename to changelog.d/8524.fixed.md diff --git a/policyengine_us/parameters/gov/states/va/dmas/medicaid/lifc/localities/group3.yaml b/policyengine_us/parameters/gov/states/va/dmas/medicaid/lifc/localities/group3.yaml index d525fdf7027..c555807449d 100644 --- a/policyengine_us/parameters/gov/states/va/dmas/medicaid/lifc/localities/group3.yaml +++ b/policyengine_us/parameters/gov/states/va/dmas/medicaid/lifc/localities/group3.yaml @@ -10,6 +10,7 @@ values: - ALEXANDRIA_CITY_VA - CHARLOTTESVILLE_CITY_VA - COLONIAL_HEIGHTS_CITY_VA + - FAIRFAX_CITY_VA - FALLS_CHURCH_CITY_VA - FREDERICKSBURG_CITY_VA - HAMPTON_CITY_VA diff --git a/policyengine_us/tests/policy/baseline/gov/states/va/dmas/medicaid/lifc/va_medicaid_lifc_locality_group.yaml b/policyengine_us/tests/policy/baseline/gov/states/va/dmas/medicaid/lifc/va_medicaid_lifc_locality_group.yaml index 74d840664e7..843ae75c14a 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/va/dmas/medicaid/lifc/va_medicaid_lifc_locality_group.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/va/dmas/medicaid/lifc/va_medicaid_lifc_locality_group.yaml @@ -53,3 +53,17 @@ county: ALEXANDRIA_CITY_VA output: va_medicaid_lifc_locality_group: GROUP_III + +- name: Case 5, Fairfax City maps to Group III. + period: 2026 + input: + people: + person1: + age: 30 + households: + household: + members: [person1] + state_code: VA + county: FAIRFAX_CITY_VA + output: + va_medicaid_lifc_locality_group: GROUP_III From fc30e63ac5f4cd868855845b3ff19e3485b26b87 Mon Sep 17 00:00:00 2001 From: Daphne Hansell <128793799+daphnehanse11@users.noreply.github.com> Date: Mon, 1 Jun 2026 16:59:51 -0400 Subject: [PATCH 5/6] Keep batched test output alive --- policyengine_us/tests/test_batched.py | 45 +++++++++++++++++++++------ 1 file changed, 36 insertions(+), 9 deletions(-) diff --git a/policyengine_us/tests/test_batched.py b/policyengine_us/tests/test_batched.py index 8abce5f7260..28a9d38c4fb 100644 --- a/policyengine_us/tests/test_batched.py +++ b/policyengine_us/tests/test_batched.py @@ -11,6 +11,7 @@ import time import argparse import re +import select from pathlib import Path from typing import List, Dict @@ -285,6 +286,9 @@ def run_batch(test_paths: List[str], batch_name: str) -> Dict: python_exe = sys.executable start_time = time.time() + last_output_time = start_time + last_heartbeat_time = start_time + heartbeat_interval = 30 # Build command - direct policyengine-core with timeout protection cmd = ( @@ -307,39 +311,62 @@ def run_batch(test_paths: List[str], batch_name: str) -> Dict: cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, - text=True, - bufsize=1, # Line buffered + bufsize=0, ) try: test_completed = False test_passed = False output_lines = [] + output_text = "" # Monitor output line by line while True: - line = process.stdout.readline() - if not line: - # No more output, check if process is done + ready, _, _ = select.select([process.stdout], [], [], 0.1) + if not ready: poll_result = process.poll() if poll_result is not None: # Process terminated break + now = time.time() + if now - last_heartbeat_time >= heartbeat_interval: + elapsed = now - start_time + quiet_for = now - last_output_time + print( + f" Still running {batch_name} after " + f"{elapsed:.0f}s ({quiet_for:.0f}s since last output)...", + flush=True, + ) + last_heartbeat_time = now # Process still running but no output time.sleep(0.1) continue - # Print line in real-time - print(line, end="") + chunk = os.read(process.stdout.fileno(), 4096) + if not chunk: + if process.poll() is not None: + break + continue + + # Print output in real-time, including partial pytest progress + # lines such as dots that do not end with newlines. + line = chunk.decode(errors="replace") + print(line, end="", flush=True) + last_output_time = time.time() + last_heartbeat_time = last_output_time output_lines.append(line) + output_text += line # Detect pytest completion # Look for patterns like "====== 5638 passed in 491.24s ======" # or "====== 2 failed, 5636 passed in 500s ======" - if re.search(r"=+.*\d+\s+(passed|failed).*in\s+[\d.]+s.*=+", line): + if re.search( + r"=+.*\d+\s+(passed|failed).*in\s+[\d.]+s.*=+", + output_text, + ): test_completed = True # Check if tests passed by parsing actual failure count - failed_match = re.search(r"(\d+) failed", line) + failed_match = re.search(r"(\d+) failed", output_text) if failed_match: failed_count = int(failed_match.group(1)) test_passed = failed_count == 0 From 2d4ded29da7c038ef61e05ada498d20522fc4c34 Mon Sep 17 00:00:00 2001 From: Daphne Hansell <128793799+daphnehanse11@users.noreply.github.com> Date: Mon, 1 Jun 2026 17:28:27 -0400 Subject: [PATCH 6/6] Split expensive RCC contrib tests --- .../integration.yaml | 141 ------------------ .../integration_flat_refundable_credit.yaml | 27 ++++ .../integration_toggles_default.yaml | 20 +++ .../integration_zero_amounts.yaml | 26 ++++ .../taxpayer_head_of_household.yaml | 18 +++ .../taxpayer_separate.yaml | 16 ++ .../taxpayer_surviving_spouse.yaml | 18 +++ policyengine_us/tests/test_batched.py | 28 ++-- 8 files changed, 140 insertions(+), 154 deletions(-) delete mode 100644 policyengine_us/tests/policy/contrib/refundable_credit_conversion/integration.yaml create mode 100644 policyengine_us/tests/policy/contrib/refundable_credit_conversion/integration_flat_refundable_credit.yaml create mode 100644 policyengine_us/tests/policy/contrib/refundable_credit_conversion/integration_toggles_default.yaml create mode 100644 policyengine_us/tests/policy/contrib/refundable_credit_conversion/integration_zero_amounts.yaml create mode 100644 policyengine_us/tests/policy/contrib/refundable_credit_conversion/taxpayer_head_of_household.yaml create mode 100644 policyengine_us/tests/policy/contrib/refundable_credit_conversion/taxpayer_separate.yaml create mode 100644 policyengine_us/tests/policy/contrib/refundable_credit_conversion/taxpayer_surviving_spouse.yaml diff --git a/policyengine_us/tests/policy/contrib/refundable_credit_conversion/integration.yaml b/policyengine_us/tests/policy/contrib/refundable_credit_conversion/integration.yaml deleted file mode 100644 index 8075415ba34..00000000000 --- a/policyengine_us/tests/policy/contrib/refundable_credit_conversion/integration.yaml +++ /dev/null @@ -1,141 +0,0 @@ -- name: Reform integration — flat refundable credit fires end-to-end - # Smoke test that the reform applies and the flat credit computes to - # its expected value across taxpayer + CTC-dependent + household - # components. - period: 2026 - reforms: policyengine_us.reforms.refundable_credit_conversion.refundable_credit_conversion - input: - gov.contrib.refundable_credit_conversion.in_effect: true - gov.contrib.refundable_credit_conversion.credit.use_taxpayer_credit: true - gov.contrib.refundable_credit_conversion.credit.use_ctc_dependent_credit: true - gov.contrib.refundable_credit_conversion.credit.use_household_credit: true - gov.contrib.refundable_credit_conversion.credit.per_taxpayer: 1_000 - gov.contrib.refundable_credit_conversion.credit.per_ctc_dependent: 500 - gov.contrib.refundable_credit_conversion.credit.per_household: 200 - people: - filer: - age: 35 - spouse: - age: 33 - child: - age: 5 - tax_units: - tax_unit: - members: [filer, spouse, child] - filing_status: JOINT - output: - flat_refundable_credit: 2_700 # 2 * 1_000 + 1 * 500 + 200 - -# Note on testing scope: we intentionally do NOT assert on -# income_tax_refundable_credits anywhere in this file. Evaluating that -# variable forces every federal refundable credit (eitc / refundable_ctc -# / aotc / recovery rebate / refundable payroll tax credit / -# cdcc-in-2021) to materialize, which (a) requires loading HuggingFace- -# backed datasets the YAML test runner does not need otherwise, and (b) -# pushes peak memory past the CI runner cap, surfacing as a "shutdown -# signal" mid-batch. The override's wiring is exercised in production -# via the federal income-tax calculation; the unit tests above pin down -# the flat_refundable_credit values themselves. - -- name: All use_* toggles default to false — flat credit stays zero - # Force-applies the reform but leaves every use_* toggle at its default - # false. Confirms the formula returns zero with no components - # contributing. The `in_effect: false` here does NOT gate the reform - # (the YAML `reforms:` directive bypasses the autoloader); the - # autoloader's None return is exercised in test_refundable_credit_conversion_autoloader.py. - period: 2026 - reforms: policyengine_us.reforms.refundable_credit_conversion.refundable_credit_conversion - input: - gov.contrib.refundable_credit_conversion.in_effect: false - people: - filer: - age: 35 - employment_income: 40_000 - tax_units: - tax_unit: - members: [filer] - filing_status: SINGLE - output: - flat_refundable_credit: 0 - -- name: HEAD_OF_HOUSEHOLD filer counts as one taxpayer - period: 2026 - reforms: policyengine_us.reforms.refundable_credit_conversion.refundable_credit_conversion - input: - gov.contrib.refundable_credit_conversion.in_effect: true - gov.contrib.refundable_credit_conversion.credit.use_taxpayer_credit: true - gov.contrib.refundable_credit_conversion.credit.per_taxpayer: 1_000 - people: - filer: - age: 35 - child: - age: 5 - tax_units: - tax_unit: - members: [filer, child] - filing_status: HEAD_OF_HOUSEHOLD - output: - flat_refundable_credit: 1_000 # 1 * 1_000 — HoH is one taxpayer - -- name: SEPARATE (MFS) filer counts as one taxpayer - period: 2026 - reforms: policyengine_us.reforms.refundable_credit_conversion.refundable_credit_conversion - input: - gov.contrib.refundable_credit_conversion.in_effect: true - gov.contrib.refundable_credit_conversion.credit.use_taxpayer_credit: true - gov.contrib.refundable_credit_conversion.credit.per_taxpayer: 1_000 - people: - filer: - age: 35 - tax_units: - tax_unit: - members: [filer] - filing_status: SEPARATE - output: - flat_refundable_credit: 1_000 # MFS is one taxpayer - -- name: SURVIVING_SPOUSE filer counts as one taxpayer - period: 2026 - reforms: policyengine_us.reforms.refundable_credit_conversion.refundable_credit_conversion - input: - gov.contrib.refundable_credit_conversion.in_effect: true - gov.contrib.refundable_credit_conversion.credit.use_taxpayer_credit: true - gov.contrib.refundable_credit_conversion.credit.per_taxpayer: 1_000 - people: - filer: - age: 35 - child: - age: 5 - tax_units: - tax_unit: - members: [filer, child] - filing_status: SURVIVING_SPOUSE - output: - flat_refundable_credit: 1_000 # SURVIVING_SPOUSE is one taxpayer - -- name: All toggles on with all amounts at zero — sanity case, returns zero - # Guards against NaN, sign flips, or divide-by-zero anomalies if all - # five component toggles are simultaneously enabled but every amount - # / rate / cap is zero. A future formula change that introduces a - # division should fail this case before it ships. - period: 2026 - reforms: policyengine_us.reforms.refundable_credit_conversion.refundable_credit_conversion - input: - gov.contrib.refundable_credit_conversion.in_effect: true - gov.contrib.refundable_credit_conversion.credit.use_taxpayer_credit: true - gov.contrib.refundable_credit_conversion.credit.use_ctc_dependent_credit: true - gov.contrib.refundable_credit_conversion.credit.use_other_dependent_credit: true - gov.contrib.refundable_credit_conversion.credit.use_household_credit: true - gov.contrib.refundable_credit_conversion.earnings_credit.use_earnings_credit: true - people: - filer: - age: 35 - employment_income: 10_000 - child: - age: 5 - tax_units: - tax_unit: - members: [filer, child] - filing_status: HEAD_OF_HOUSEHOLD - output: - flat_refundable_credit: 0 diff --git a/policyengine_us/tests/policy/contrib/refundable_credit_conversion/integration_flat_refundable_credit.yaml b/policyengine_us/tests/policy/contrib/refundable_credit_conversion/integration_flat_refundable_credit.yaml new file mode 100644 index 00000000000..bacce915d9a --- /dev/null +++ b/policyengine_us/tests/policy/contrib/refundable_credit_conversion/integration_flat_refundable_credit.yaml @@ -0,0 +1,27 @@ +- name: Reform integration — flat refundable credit fires end-to-end + # Smoke test that the reform applies and the flat credit computes to + # its expected value across taxpayer + CTC-dependent + household + # components. + period: 2026 + reforms: policyengine_us.reforms.refundable_credit_conversion.refundable_credit_conversion + input: + gov.contrib.refundable_credit_conversion.in_effect: true + gov.contrib.refundable_credit_conversion.credit.use_taxpayer_credit: true + gov.contrib.refundable_credit_conversion.credit.use_ctc_dependent_credit: true + gov.contrib.refundable_credit_conversion.credit.use_household_credit: true + gov.contrib.refundable_credit_conversion.credit.per_taxpayer: 1_000 + gov.contrib.refundable_credit_conversion.credit.per_ctc_dependent: 500 + gov.contrib.refundable_credit_conversion.credit.per_household: 200 + people: + filer: + age: 35 + spouse: + age: 33 + child: + age: 5 + tax_units: + tax_unit: + members: [filer, spouse, child] + filing_status: JOINT + output: + flat_refundable_credit: 2_700 # 2 * 1_000 + 1 * 500 + 200 diff --git a/policyengine_us/tests/policy/contrib/refundable_credit_conversion/integration_toggles_default.yaml b/policyengine_us/tests/policy/contrib/refundable_credit_conversion/integration_toggles_default.yaml new file mode 100644 index 00000000000..27247f8ebbf --- /dev/null +++ b/policyengine_us/tests/policy/contrib/refundable_credit_conversion/integration_toggles_default.yaml @@ -0,0 +1,20 @@ +- name: All use_* toggles default to false — flat credit stays zero + # Force-applies the reform but leaves every use_* toggle at its default + # false. Confirms the formula returns zero with no components + # contributing. The `in_effect: false` here does NOT gate the reform + # (the YAML `reforms:` directive bypasses the autoloader); the + # autoloader's None return is exercised in test_refundable_credit_conversion_autoloader.py. + period: 2026 + reforms: policyengine_us.reforms.refundable_credit_conversion.refundable_credit_conversion + input: + gov.contrib.refundable_credit_conversion.in_effect: false + people: + filer: + age: 35 + employment_income: 40_000 + tax_units: + tax_unit: + members: [filer] + filing_status: SINGLE + output: + flat_refundable_credit: 0 diff --git a/policyengine_us/tests/policy/contrib/refundable_credit_conversion/integration_zero_amounts.yaml b/policyengine_us/tests/policy/contrib/refundable_credit_conversion/integration_zero_amounts.yaml new file mode 100644 index 00000000000..85898207428 --- /dev/null +++ b/policyengine_us/tests/policy/contrib/refundable_credit_conversion/integration_zero_amounts.yaml @@ -0,0 +1,26 @@ +- name: All toggles on with all amounts at zero — sanity case, returns zero + # Guards against NaN, sign flips, or divide-by-zero anomalies if all + # five component toggles are simultaneously enabled but every amount + # / rate / cap is zero. A future formula change that introduces a + # division should fail this case before it ships. + period: 2026 + reforms: policyengine_us.reforms.refundable_credit_conversion.refundable_credit_conversion + input: + gov.contrib.refundable_credit_conversion.in_effect: true + gov.contrib.refundable_credit_conversion.credit.use_taxpayer_credit: true + gov.contrib.refundable_credit_conversion.credit.use_ctc_dependent_credit: true + gov.contrib.refundable_credit_conversion.credit.use_other_dependent_credit: true + gov.contrib.refundable_credit_conversion.credit.use_household_credit: true + gov.contrib.refundable_credit_conversion.earnings_credit.use_earnings_credit: true + people: + filer: + age: 35 + employment_income: 10_000 + child: + age: 5 + tax_units: + tax_unit: + members: [filer, child] + filing_status: HEAD_OF_HOUSEHOLD + output: + flat_refundable_credit: 0 diff --git a/policyengine_us/tests/policy/contrib/refundable_credit_conversion/taxpayer_head_of_household.yaml b/policyengine_us/tests/policy/contrib/refundable_credit_conversion/taxpayer_head_of_household.yaml new file mode 100644 index 00000000000..9f175cef30e --- /dev/null +++ b/policyengine_us/tests/policy/contrib/refundable_credit_conversion/taxpayer_head_of_household.yaml @@ -0,0 +1,18 @@ +- name: HEAD_OF_HOUSEHOLD filer counts as one taxpayer + period: 2026 + reforms: policyengine_us.reforms.refundable_credit_conversion.refundable_credit_conversion + input: + gov.contrib.refundable_credit_conversion.in_effect: true + gov.contrib.refundable_credit_conversion.credit.use_taxpayer_credit: true + gov.contrib.refundable_credit_conversion.credit.per_taxpayer: 1_000 + people: + filer: + age: 35 + child: + age: 5 + tax_units: + tax_unit: + members: [filer, child] + filing_status: HEAD_OF_HOUSEHOLD + output: + flat_refundable_credit: 1_000 # 1 * 1_000 — HoH is one taxpayer diff --git a/policyengine_us/tests/policy/contrib/refundable_credit_conversion/taxpayer_separate.yaml b/policyengine_us/tests/policy/contrib/refundable_credit_conversion/taxpayer_separate.yaml new file mode 100644 index 00000000000..704c3276d33 --- /dev/null +++ b/policyengine_us/tests/policy/contrib/refundable_credit_conversion/taxpayer_separate.yaml @@ -0,0 +1,16 @@ +- name: SEPARATE (MFS) filer counts as one taxpayer + period: 2026 + reforms: policyengine_us.reforms.refundable_credit_conversion.refundable_credit_conversion + input: + gov.contrib.refundable_credit_conversion.in_effect: true + gov.contrib.refundable_credit_conversion.credit.use_taxpayer_credit: true + gov.contrib.refundable_credit_conversion.credit.per_taxpayer: 1_000 + people: + filer: + age: 35 + tax_units: + tax_unit: + members: [filer] + filing_status: SEPARATE + output: + flat_refundable_credit: 1_000 # MFS is one taxpayer diff --git a/policyengine_us/tests/policy/contrib/refundable_credit_conversion/taxpayer_surviving_spouse.yaml b/policyengine_us/tests/policy/contrib/refundable_credit_conversion/taxpayer_surviving_spouse.yaml new file mode 100644 index 00000000000..cd5883273ef --- /dev/null +++ b/policyengine_us/tests/policy/contrib/refundable_credit_conversion/taxpayer_surviving_spouse.yaml @@ -0,0 +1,18 @@ +- name: SURVIVING_SPOUSE filer counts as one taxpayer + period: 2026 + reforms: policyengine_us.reforms.refundable_credit_conversion.refundable_credit_conversion + input: + gov.contrib.refundable_credit_conversion.in_effect: true + gov.contrib.refundable_credit_conversion.credit.use_taxpayer_credit: true + gov.contrib.refundable_credit_conversion.credit.per_taxpayer: 1_000 + people: + filer: + age: 35 + child: + age: 5 + tax_units: + tax_unit: + members: [filer, child] + filing_status: SURVIVING_SPOUSE + output: + flat_refundable_credit: 1_000 # SURVIVING_SPOUSE is one taxpayer diff --git a/policyengine_us/tests/test_batched.py b/policyengine_us/tests/test_batched.py index 28a9d38c4fb..4070ff9c480 100644 --- a/policyengine_us/tests/test_batched.py +++ b/policyengine_us/tests/test_batched.py @@ -89,17 +89,10 @@ def split_into_batches( "eitc", "crfb", "congress", - # refundable_credit_conversion's override of - # income_tax_refundable_credits pulls every federal - # refundable credit (eitc / refundable_ctc / aotc / - # recovery rebate / refundable payroll tax credit / - # cdcc-in-2021) into the subprocess's variable graph when - # integration.yaml asserts on it. Bundled with the catch-all - # this pushes peak memory past the runner cap, surfacing as - # "shutdown signal" mid-batch. Its own batch keeps the - # light batch under the cap. - "refundable_credit_conversion", } + # Some folders contain individual YAMLs that are each expensive + # enough to need their own subprocess. + PER_FILE = {"refundable_credit_conversion"} subdirs = sorted( item @@ -108,14 +101,23 @@ def split_into_batches( ) root_files = sorted(base_path.glob("*.yaml")) - # One batch per heavy subdir (if present). - batches = [[str(subdir)] for subdir in subdirs if subdir.name in HEAVY] + # One batch per file for per-file folders, then one batch per + # heavy subdir (if present). + batches = [ + [str(file)] + for subdir in subdirs + if subdir.name in PER_FILE + for file in sorted(subdir.rglob("*.yaml")) + ] + batches += [[str(subdir)] for subdir in subdirs if subdir.name in HEAVY] # Catch-all batch for everything else — light subdirs and root # yaml files. Auto-collects any newly added folders/files so # they're exercised without extra config. light_paths = [ - str(subdir) for subdir in subdirs if subdir.name not in HEAVY + str(subdir) + for subdir in subdirs + if subdir.name not in HEAVY and subdir.name not in PER_FILE ] + [str(f) for f in root_files] if light_paths: batches.append(light_paths)