Skip to content

Guard county-keyed lookups in VT renter credit, MN CCAP, WI Shares and MD CCS - #10116

Open
MaxGhenis wants to merge 3 commits into
mainfrom
fix-county-lookup-guards
Open

MaxGhenis wants to merge 3 commits into
mainfrom
fix-county-lookup-guards

Conversation

@MaxGhenis

@MaxGhenis MaxGhenis commented Oct 11, 2026 •

Copy link
Copy Markdown
Contributor

Problem

defined_for masks a formula's output, but policyengine-core 3.32.15 runs the formula over the whole population and skips it only when the mask is all false. A county key missing from a county-keyed parameter table therefore raises ParameterNotFoundError whenever the simulation also holds a household the table doesn't cover. This follows #10113 (the WA property tax exemption) and comes from the same 51-state probe.

Variable Crashes on main when
vt_renter_credit any non-VT household shares the simulation; a VT household has county UNKNOWN; a tax unit has 9+ people (the tables cover sizes 1–8), even a lone VT household
mn_ccap_provider_rate an MN household has county UNKNOWN
wi_shares_hourly_max_rate, wi_shares_monthly_max_rate a WI household has county UNKNOWN
md_ccs_payment_rate an MD household has county UNKNOWN

Changes

  • County guard. Each formula masks the county with the md_flat_rate_county_tax.py pattern, where(in_state & (county != "UNKNOWN"), county, "<FIRST>_COUNTY_<ST>"). MN keeps its existing Hennepin fallback. VT switches from the county enum to county_str to match the other guards; the keys are the same.
  • The UNKNOWN fallbacks are placeholders, not estimates. For an in-state household with county UNKNOWN, VT, WI and MD use their first-listed county and MN uses Hennepin. The sources prescribe the actual county's rate and give no fallback, so these return an arbitrary in-state value instead of crashing. A household with no county input takes the variable's default county instead (for MN, Aitkin), which can differ: an MN infant in 45 hours a week of family care gets $888.33 a month with county omitted and $1,083.33 with UNKNOWN. No household in the default dataset has an unknown or mismatched county.
  • Size cap before 2023. The calculators document the size-8 income-limit cap for TY2023–2025. This PR applies the same cap to 2021–2022; no contrary rule was found for those years.
  • VT household size. A new parameter, gov.states.vt.tax.income.credits.renter.max_household_size = 8, caps the table lookup. Tax units of 9 or more use the 8-person income limits and fair market rent.

Why 8 for Vermont

  • The department caps at 8. Its Renter Credit Website Calculators for TY2023, TY2024 and TY2025 (cited in the parameter files) share these Calculator-sheet formulas:
    • G11 and G12, the partial and full credit income limits: MATCH(TEXT(IF(Household_Size>8,8,Household_Size),0),...).
    • G13, the credit amount: IF(Household_Size>4,4,Household_Size). PE's fair_market_rent table already repeats the 4-person value through size 8, so capping at 8 is exact.
    • G5, family size, is validated to 1–10.
  • The calculator reproduces the expectations. I recalculated the TY2025 calculator in LibreOffice for the test households. It gives 1,162 (Chittenden, size 2), 846 (Addison, size 2), 976 (Chittenden, size 7) and 1,250 (Chittenden, sizes 8, 9 and 10), exactly the expectations in the new tests.
  • The statute is silent on size. 32 V.S.A. § 6061(19)–(20) defines the limits as HUD's, and § 6066(b)(2) charges the Commissioner with calculating the credit. The TY2025 booklet (RCC-146, file pages 37–38) prints family sizes 1–8 only.
  • Alternative reading, not adopted. HUD does define larger households. Its FY 2024 income-limit methodology adds 8% of the 4-person limit per person over 8, and its FMR convention (68 Fed. Reg. 56705) adds 15% per bedroom over 4. A literal reading of § 6061 could import those values. The department caps instead, and PE's tables already transcribe its 4-bedroom cap, so this PR follows the department.

Tests

New vectorized YAML cases put an in-state household, an in-state UNKNOWN-county household and (for the crash guards) an out-of-state household in one simulation, so the mask is not all false:

  • vt_renter_credit.yaml test 11: Chittenden, New Hampshire, and VT UNKNOWN (falls back to Addison).
  • vt_renter_credit.yaml test 12: VT tax units of 8, 9 and 12.
  • mn_ccap_provider_rate.yaml Case 13, wi_shares_hourly_max_rate.yaml Case 9, wi_shares_monthly_max_rate.yaml Case 6, and the new md_ccs_payment_rate.yaml informal-provider case.

Fails on main, passes here. With the five formulas restored from upstream/main and the new tests kept, the six new cases fail with ParameterNotFoundError (keys …thirty_percent.9, the out-of-state VT key, …weekly.CHILD_CARE_CENTER.UNKNOWN, …licensed_group.hourly.UNKNOWN ×2, …informal.rates.UNKNOWN). The 44 existing cases in those files pass. With the fix, all 50 pass.

Broader runs. All VT, MN, WI and MD baseline YAML tests plus household/state_property_tax_credits pass (1,920). code_health/test_parameter_yaml_keys.py, code_health/test_reference_urls.py and test_md_ccs_service_unit_enum_default.py pass (69).

51-state probe (2026). Five multi-household simulations: 51 states with real counties; the same plus UNKNOWN in every state; plus VT tax units of 9 and 12; a lone VT unit of 9; and every county of VT (14), MN (87), WI (72) and MD (24) plus UNKNOWN and out-of-state. Every scenario raised for at least one variable on main; all five variables compute in every scenario here. Script and outputs: ~/reviews/county-lookup-guards-2026-10/evidence/.

Invariants

The probe runs every county of the four states and checks that each variable computes. It does not itself assert main/head equality, size-cap equality, out-of-state zeros or calculator agreement; the YAML cases assert representative values for those:

  • No crash. Each variable computes for any mix of households whose state and county agree, including UNKNOWN. (A VT household paired with an NH county still raises.)
  • No change where main computed. In-state households with a real county use the same table key as before; VT sizes 1–8 use the same size key.
  • Size cap. The VT credit for any size n ≥ 8 equals the size-8 credit.
  • Out of state is zero. Out-of-state output is 0 (defined_for).
  • Differential. The VT credit equals the department calculator for sizes 2, 7, 8, 9 and 10.

No Python test is added, per the repo's YAML-first rule; the YAML cases cover each path.

Hub microsimulation impact (current head)

A real microsimulation on the default dataset, main at the merge base 065d0e9560 against this PR's head 8fde198e14, for 2025 and 2026. The runs went one at a time under the shared heavy-job lock. Sums are weighted.

Output 2025 change 2026 change 2025 records changed (weighted) 2026 records changed (weighted)
state_income_tax +0 +0 0 (0) 0 (0)
income_tax +0 +0 0 (0) 0 (0)
household_net_income +0 +0 0 (0) 0 (0)
household_benefits +0 +0 0 (0) 0 (0)

Run on the standard outputs only: on main, requesting vt_renter_credit or the MN/WI/MD child-care rate variables across the whole population crashes (the crash this PR fixes), so a base run that names them cannot complete. No record changes in either year.

Comparison files are in fixes/10116-impact/ on the hub host.

Impact

Only cases that crashed on main get new values. In the default dataset every household has a real county matching its state. vt_renter_credit's only consumer is taxsim_state_property_tax_credit, through gov.states.household.state_property_tax_credits.

axiom: us-vt/statute/32/6066 TheAxiomFoundation/rulespec-us#1678 queued (corpus ingest TheAxiomFoundation/axiom-corpus#816; the rulespec-us corpus pin predates the VT § 6061/§ 6066 rows). The county guards are n/a: pure crash guards.

Follow-up (not in this PR)

The TY2025 booklet prints Washington County's 7-person partial-credit limit as $88,880; the calculator has $88,990. PE uses the booklet value.

🤖 Generated with Claude Code

MaxGhenis and others added 3 commits October 11, 2026 15:38
…D CCS

defined_for masks a formula's output, but policyengine-core runs the formula
over the whole population, so county keys missing from a county-keyed
parameter table raise ParameterNotFoundError in multi-household simulations.

- vt_renter_credit: mask non-VT and UNKNOWN counties to Addison, and cap the
  table lookup at household size 8 (new max_household_size parameter), as the
  Department of Taxes renter credit calculator does for 9+ person households.
- mn_ccap_provider_rate, wi_shares_{hourly,monthly}_max_rate,
  md_ccs_payment_rate: also mask an in-state UNKNOWN county.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each case puts an in-state household, an in-state UNKNOWN-county household
and (for the crash guards) an out-of-state household in one simulation, so
the defined_for mask is not all false and the formula runs over every
county. VT test 12 covers tax units of 8, 9 and 12. All six cases raise
ParameterNotFoundError on upstream/main and pass with the fix.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@MaxGhenis

Copy link
Copy Markdown
Contributor Author

The CI run on this pull request was cancelled at 23:27Z on 2026-10-11 to free the organization's shared GitHub runners for an urgent fix in another repository. The US + core hub will re-run it automatically (cancelled jobs only; jobs that had passed are kept). Nothing in this pull request changed. Please don't push or reopen just to restart CI.

This branch has not been deployed

No deployments
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.

1 participant