Skip to content

[codex] Model Virginia Medicaid LIFC regional limits#8524

Merged
hua7450 merged 8 commits into
PolicyEngine:mainfrom
daphnehanse11:codex/va-medicaid
Jun 3, 2026
Merged

[codex] Model Virginia Medicaid LIFC regional limits#8524
hua7450 merged 8 commits into
PolicyEngine:mainfrom
daphnehanse11:codex/va-medicaid

Conversation

@daphnehanse11
Copy link
Copy Markdown
Collaborator

Fixes #5798.

Summary

This models Virginia's Medicaid Low Income Families With Children (LIFC) regional income limits for parent/caretaker eligibility. Virginia's parent Medicaid limit is not a single statewide FPL percentage; DMAS publishes annual dollar limits by LIFC locality Group I, Group II, and Group III.

Changes

  • Adds DMAS LIFC income-limit parameters effective July 1, 2025 for Groups I, II, and III, including additional-person amounts.
  • Adds DMAS LIFC locality group parameters from Appendix 4.
  • Adds va_medicaid_lifc_locality_group and va_medicaid_lifc_income_limit.
  • Adds medicaid_parent_income_limit, so Virginia uses its regional LIFC limit while other states continue using the existing national parent Medicaid income-limit table.
  • Updates parent Medicaid financial eligibility to treat income exactly at the applicable limit as eligible, matching DMAS language that income exceeding the LIFC limit is ineligible.

Sources

Not Modeled

  • Retroactive entitlement, Marketplace referrals, LIFC Extended Medicaid, and administrative processing.
  • Full caretaker-relative relationship verification beyond the existing parent nonfinancial proxy.
  • Historical regional income tables before the current DMAS manual.

Validation

  • make format
  • .venv/bin/policyengine-core test policyengine_us/tests/policy/baseline/gov/states/va/dmas/medicaid/lifc -c policyengine_us
  • .venv/bin/policyengine-core test policyengine_us/tests/policy/baseline/gov/hhs/medicaid/eligibility/categories/parent/medicaid_parent_income_limit.yaml -c policyengine_us
  • .venv/bin/policyengine-core test policyengine_us/tests/policy/baseline/gov/hhs/medicaid/eligibility/categories/is_parent_for_medicaid.yaml -c policyengine_us

@codecov
Copy link
Copy Markdown

codecov Bot commented May 27, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (17cda0d) to head (5c607f0).
⚠️ Report is 9 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff            @@
##              main     #8524   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files            1         4    +3     
  Lines           19        67   +48     
  Branches         0         1    +1     
=========================================
+ Hits            19        67   +48     
Flag Coverage Δ
unittests 100.00% <100.00%> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link
Copy Markdown
Collaborator Author

@daphnehanse11 daphnehanse11 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blocking: medicaid_parent_income_limit now routes every Virginia year through va_medicaid_lifc_income_limit, but the new LIFC dollar tables only start at 2025-07-01. That means historical Virginia parent eligibility no longer uses the existing 0.54 FPL parent limit for pre-effective years; it is recomputed from the 2025 LIFC table instead. Repro on this branch: a VA household of 3 in Arlington returns medicaid_parent_income_limit = 0.4870643 for 2024, while the existing VA parent parameter is 0.54. Please either backfill historical LIFC tables/effective dates or gate the Virginia override so years before the modeled table keep the existing statewide parent limit.

@daphnehanse11 daphnehanse11 marked this pull request as ready for review June 1, 2026 20:07
@daphnehanse11 daphnehanse11 requested a review from hua7450 June 2, 2026 14:49
@hua7450
Copy link
Copy Markdown
Collaborator

hua7450 commented Jun 2, 2026

Program Review — PR #8524: Virginia Medicaid LIFC Regional Limits

Models Virginia's Low Income Families with Children (LIFC) regional parent income limits (Group I/II/III by locality and household size), gated behind an effective-date in_effect boolean, plus a federal Medicaid parent-eligibility refactor (new medicaid_parent_income_limit selector; <<= boundary fix) and test-infra changes (per-file batching of refundable_credit_conversion).

Source Documents

(Note: M07 Group I/II/III tables are MEDICALLY NEEDY limits — a different program. Verified the PR did not leak M07 values into the LIFC params.)

Branch Status

The PR is 6 commits ahead, 201 commits behind main. Informational only — not a finding. The review was scoped to the PR's actual changes via merge-base diff, so staleness did not affect findings. CI is green on the PR branch.

Critical (Must Fix)

None. Income-limit values are all correct (28/28 confirmed against Appendix 3), no hard-coded values in formulas, no reinvented variables (medicaid_parent_income_limit is a genuine new selector), references corroborate the stored values, CI is passing, and the <<= boundary change is law-correct per 42 CFR 435.110.

Should Address

1. City of Fairfax misclassified into Group III (not supported by Appendix 4)
File: policyengine_us/parameters/gov/states/va/dmas/medicaid/lifc/localities/group3.yaml:13 (- FAIRFAX_CITY_VA)

M04 Appendix 4 (#page=52) lists Group III as Counties (Arlington, Fairfax, Montgomery, Prince William) and Cities (Alexandria, Charlottesville, Colonial Heights, Falls Church, Fredericksburg, Hampton, Manassas, Manassas Park, Waynesboro). "Fairfax" appears exactly once, under Counties — the independent City of Fairfax is listed in no group. Virginia had 38 independent cities in 2017; the appendix enumerates 37, and Fairfax City is the single omission, so by the appendix's default rule it belongs to Group I.

This was flagged independently by the regulatory, reference, code, and PDF-locality reviewers, and the code-path reviewer confirmed it is live: the County enum does contain FAIRFAX_CITY_VA (county_enum.py, distinct from FAIRFAX_COUNTY_VA), so a live 2026 simulation with county = FAIRFAX_CITY_VA returns GROUP_III. The locality test (va_medicaid_lifc_locality_group.yaml Case 5) bakes this in.

Impact (microsim): over-generous for City of Fairfax residents (~24k people) — Group III HH1 limit ~$642/mo vs the Group I ~$329/mo the appendix supports, raising the LIFC income standard above source for that locality. Direction = over-counts eligibility; magnitude = one independent city.

Remedy: either (a) remove FAIRFAX_CITY_VA from group3.yaml so it falls through to the default=GROUP_I matching the literal appendix (and update Case 5 of the locality test accordingly), or (b) keep the assignment but add a param/code comment documenting that DMAS does not enumerate the City of Fairfax and it is being grouped with its enclosing Fairfax County — a defensible geographic interpretation, but currently unsupported by the cited source.

2. County-enum coverage gap: 7 VA independent cities silently resolve to Group I
Files: localities/group2.yaml (Covington, Martinsville, Salem, Williamsburg, Winchester), localities/group3.yaml (Colonial Heights, Manassas)

va_medicaid_lifc_locality_group.py matches county_str (the decoded enum name) against the group lists via np.isin. The upstream County enum (variables/household/demographic/geographic/county/county_enum.py) carries only 29 of Virginia's 38 independent cities. The 7 cities above have no token in the enum, so np.isin never matches and they fall through to default=GROUP_I — i.e. they silently receive the lowest income standard instead of their (higher) Group II/III limit. The code-path reviewer ruled out a naming false-positive (no alternate VA token exists for any of the 7; substring hits are non-VA, e.g. COVINGTON_COUNTY_AL, or the unrelated MANASSAS_PARK_CITY_VA).

Root cause is the upstream county_enum.py, NOT this PR's YAML — the PR's tokens follow the correct <NAME>_CITY_VA convention used by the 29 cities that do exist, and they are correctly grouped per Appendix 4 (they will work as soon as the enum gains the members). (Buena Vista and Galax are also absent from the enum but are Group I anyway, so harmless.)

Impact (microsim): under-generous for those 7 cities — residents who should be Group II/III get the Group I standard.

Remedy: extend county_enum.py upstream to add the 9 missing VA cities (the substantive fix, arguably out of scope for a single LIFC PR), OR document the limitation in this PR (variable docstring / PR description: "we don't track these VA independent cities in county_str at the moment, so they receive the Group I limit").

Related upstream robustness note (pre-existing, NOT introduced by this PR): for these cities the FIPS dataset (county_fips_2020.csv.gz) does contain entries (e.g. 51775 Salem city), so a situation supplying county_fips for one hits an unguarded KeyError in county.py via map_county_string_to_enum. The dominant is_over_dataset microsim path reads stored county arrays (always valid enum members), so it is unaffected. Worth flagging upstream, but do not attribute it to this PR.

3. Federal <<= boundary flip is untested for non-VA states and diverges from sibling categories
File: policyengine_us/variables/gov/hhs/medicaid/eligibility/categories/parent/is_parent_for_medicaid_fc.py:13

The change from income < income_limit to income <= income_limit is regulatorily correct — 42 CFR 435.110 requires Medicaid for income "at or below" the standard, and VA M04 language ("if income exceeds the limit … ineligible") agrees. But:

  • It is a federal variable, so the boundary change applies to every state, not just VA (a parent exactly at the limit was ineligible before and is eligible now).
  • It now diverges from the sibling financial-criteria files, which still use strict <: young_child/, older_child/, young_adult/, pregnant/, adult/ (infant already uses an effectively-inclusive np.isclose | <=).
  • No federal test pins the exact-boundary value: tests/.../categories/is_parent_for_medicaid.yaml brackets the CA limit (1.14) with 1.13 (eligible) / 1.15 (ineligible) but never tests exactly 1.14 — so a future revert to < would still pass CI. The VA integration tests do pin the inclusive boundary (income 9_204 == limit → eligible; 9_205 → ineligible), so VA is covered.

Remedy: add a non-VA federal boundary test (CA, medicaid_income_level: 1.14, tax_unit_count_dependents: 1, expect is_parent_for_medicaid: true) in is_parent_for_medicaid.yaml to lock the flip. Separately, raise with the author whether the sibling category files should also move to <= for consistency (a question, not a prescription).

4. No test exercises the locality default-fallback path
File: policyengine_us/tests/.../lifc/va_medicaid_lifc_locality_group.yaml

va_medicaid_lifc_locality_group returns GROUP_I via select(..., default=GROUP_I) for any county not in the group2/group3 lists. The 5 existing cases all use counties/cities explicitly listed (one Group I county that is in group1.yaml, Group II/III counties and cities), so the default arm is never distinguished from the "explicitly in group1" arm. Add one case with an unenumerated county string → expect GROUP_I to pin the fallback. This also matters because VA has 133 localities: if any listed group1 county were dropped, the default would silently catch it. (Per the maintainer guidance, explicit matching is preferred over relying on default for a known category — at minimum the default path should be tested.)

5. medicaid_parent_income_limit does not route Group I / Group III through the federal selector
File: policyengine_us/tests/.../parent/medicaid_parent_income_limit.yaml

The federal selector test covers CA (national table), VA Group II (Henrico), and VA pre-LIFC (2024 fallback), but not VA Group I or Group III through medicaid_parent_income_limit itself. Those groups are tested in the inner va_medicaid_lifc_income_limit.yaml and in integration.yaml, so coverage exists — but adding a Group I and a Group III case at the medicaid_parent_income_limit level would fully cover the state-routing where(state_code == VA, ...) wrapper for all three groups. Low effort.

Suggestions

1. Income-limit group titles could name the specific sub-table
Files: income_limit/group{1,2,3}/{main,additional}.yaml. All cite "Chapter M04, Appendix 3" (correct — Appendix 3 holds all three group tables on one page). Optional polish: append the group name (e.g., "Appendix 3, Group I LIFC Income Limits") so each title disambiguates which sub-table justifies the file.

2. Add size-1 and size-8 household boundary tests
File: va_medicaid_lifc_income_limit.yaml. The income-limit test covers size 3 (all groups) and size 9 (> max, additional-person path). Adding size 1 (smallest tabulated) and size 8 (exactly max_household_size, where min_(size, max_household_size) stops capping and additional_people == 0) would pin the table edges — size 8 is the most fragile point in the formula.

3. Add a Group II city locality test
File: va_medicaid_lifc_locality_group.yaml. Group II is tested via a county (Henrico) only; Group III is tested via both a county and cities. A Group II city case (e.g. NORFOLK_CITY_VA, RICHMOND_CITY_VA) would parallel the Group III city coverage and guard the city entries in group2.yaml.

4. test_batched.py: O(n²) output accumulation and POSIX-only select
File: policyengine_us/tests/test_batched.py:327,360,365. output_text += line grows an unbounded string and re.search re-scans the entire accumulated output on every chunk — O(n²) in total output size (fine for CI-bounded output; searching only a trailing window would avoid the quadratic cost). select.select(...) on a subprocess pipe works on Linux/macOS (the CI targets) but not Windows — worth a one-line note that this runner is POSIX-only. The per-output_text matching switch is itself a correct fix (the ===== N passed ===== summary can split across read chunks), and the PER_FILE split logic is correct. Infra-only.

PDF Audit Summary

Category Count Detail
Confirmed correct 28/28 income values + 95 counties + 37 cities Appendix 3 annual column matches param files exactly (monthly×12 == annual verified for all 27 cells); all counties and every enum-representable city in the right group; all 4 same-name county/city pairs split correctly
Mismatches confirmed 1 Fairfax City → Group III (should be Group I per Appendix 4); code-path confirmed live
Mismatches rejected naming false-positive (7 enum-gap cities) The 7 cities are correctly grouped in YAML; root cause is the upstream enum, not a mapping error
Unmodeled items 0 (in scope) All 3 group tables, sizes 1–8, additional rows, effective date modeled. PDF monthly column intentionally not stored (derivable as annual÷12, not needed)
Pre-existing issues 1 Unguarded KeyError in county.py for the 9 missing-from-enum VA cities when a county_fips situation input is supplied (microsim is_over_dataset path unaffected) — NOT introduced by this PR

Validation Summary

Dimension Result
Regulatory accuracy PASS — income limits, FPG-ratio scaling, LIFC-vs-medically-needy distinction, boundary operator (42 CFR 435.110 "at or below"), in_effect gating all verified; 1 should-address (Fairfax City)
References PASS — every income value traces to Appendix 3; page anchors (#page=51 / #page=52) verified as correct FILE pages; M04 not M07; reference format correct; 1 should-address (Fairfax City untraceable) + 1 suggestion
Code patterns PASS — no hard-coded values, correct period/entity usage, both changelog fragments present, in_effect date aligned, RCC split byte-equivalent; 2 should-address (enum gap, federal boundary scope) + suggestions
Test coverage STRONG for VA, gaps for non-VA — all three locality groups, size>8, exact <= boundary for VA covered; 1 high (non-VA boundary), 2 medium (Group I/III routing, default fallback), low items
PDF value audit PASS — 28/28 income values confirmed; 1 locality mismatch confirmed
CI status PASS — all checks green; 17 LIFC tests + split RCC tests pass

Review Severity: COMMENT

Real should-address items (one source deviation fixable in this PR, an upstream enum gap to document, and several test gaps), but nothing blocking: income values are all correct, no hard-coded logic, no reinvented variables, references corroborate, and CI is green.

Next Steps

To auto-fix: /fix-pr 8524

@daphnehanse11
Copy link
Copy Markdown
Collaborator Author

Addressed the review in 5c607f0:

  • Removed Fairfax City from Virginia LIFC Group III so it falls through to Group I.
  • Added locality fallback coverage, including Fairfax City defaulting to Group I and a Group II city case.
  • Added California exact-boundary parent eligibility coverage at medicaid_income_level: 1.14.
  • Added Virginia Group I and Group III medicaid_parent_income_limit selector coverage.
  • Documented that Appendix 4 city tokens absent from the County enum cannot match the locality lists and default to Group I.

Local verification:

  • .venv/bin/python -m policyengine_core.scripts.policyengine_command test policyengine_us/tests/policy/baseline/gov/states/va/dmas/medicaid/lifc/va_medicaid_lifc_locality_group.yaml policyengine_us/tests/policy/baseline/gov/hhs/medicaid/eligibility/categories/parent/medicaid_parent_income_limit.yaml policyengine_us/tests/policy/baseline/gov/hhs/medicaid/eligibility/categories/is_parent_for_medicaid.yaml -c policyengine_us
  • .venv/bin/ruff format --check policyengine_us/variables/gov/states/va/dmas/medicaid/lifc/va_medicaid_lifc_locality_group.py
  • .venv/bin/ruff check policyengine_us/variables/gov/states/va/dmas/medicaid/lifc/va_medicaid_lifc_locality_group.py

The new GitHub Actions run is pending: https://github.com/PolicyEngine/policyengine-us/actions/runs/26846627471

@hua7450 hua7450 merged commit 210c92a into PolicyEngine:main Jun 3, 2026
49 of 50 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Virginia Varies Medicaid Eligibility by Region

2 participants