Skip to content

Fix WA property tax exemption crash for households outside Washington - #10113

Open
MaxGhenis wants to merge 1 commit into
mainfrom
fix-wa-pte-county-lookup
Open

MaxGhenis wants to merge 1 commit into
mainfrom
fix-wa-pte-county-lookup

Conversation

@MaxGhenis

@MaxGhenis MaxGhenis commented Oct 11, 2026 •

Copy link
Copy Markdown
Contributor

Status. The code and tests are final. Still pending: the independent review, and the full-population microsim for 2026 and 2027, which is queued behind the shared heavy lock. The runner checks every record: non-WA results are zero, tier is 0–3, and the exemption is between 0 and the property tax. Results will go in a comment.

Problem

A full-population Microsimulation on the default dataset (populace_us_2024) crashes when it computes wa_senior_disabled_property_tax_exemption:

File ".../senior_disabled/income/wa_pte_income_eligible.py", line 24, in formula
    return income <= p.tier_3[county]
ParameterNotFoundError: The parameter 'gov.states.wa.dor.property_tax_exemption.senior_disabled.income.thresholds.tier_3.MARSHALL_COUNTY_SD' was not found in the 2027-01-01 tax and benefit system.

The crash turned up while running the #10103 microsim on 2026-10-11. The tree it ran on was the #10085 head, close to main.

wa_pte_income_eligible and wa_pte_tier index the county-keyed WA income thresholds with every household's county_str. defined_for = StateCode.WA does not stop the formula from running on other households. In policyengine-core 3.32.15, Simulation._calculate runs the formula over the whole population and only applies the defined_for mask afterwards. It skips the formula only when no household passes the mask. So any simulation with at least one WA household and one household elsewhere raised the error. A WA household whose county is UNKNOWN raised it too.

This is not specific to 2027. A 51-household simulation, one household per state plus DC, raised the same error for 2024, 2026 and 2027 on upstream/main (ae3782f). Full runs have not hit it because nothing outside the property_tax_exemption folder uses these variables. The exemption does not feed household_net_income.

Fix

Mask the county before the lookup, the same way md_flat_rate_county_tax, md_applicable_local_tax_rate and in_county_tax do:

county = tax_unit.household("county_str", period)
in_wa = tax_unit.household("state_code_str", period) == "WA"
county = where(in_wa & (county != "UNKNOWN"), county, "ADAMS_COUNTY_WA")
  • Households outside WA get Adams County thresholds, and the defined_for mask then sets their result to zero, as before.
  • WA households with an UNKNOWN county get Adams County thresholds instead of a crash. Adams County is what the model's county formula (via first_county_in_state) assigns a WA household that gives no county. This matches the MD precedent (issue md_withheld_income_tax: ParameterNotFoundError for MD household with county=UNKNOWN (no UNKNOWN guard) #8975, Allegany County).
  • WA households with a real county are unchanged: all 39 WA counties are keys in every tier file, so the guard never fires for them.

Tests

The new vectorized YAML cases put WA and non-WA households in one simulation. A single out-of-state household would not test the guard, because an all-false defined_for mask skips the formula entirely.

  • wa_pte_income_eligible Case 12 (2024): King County, South Dakota, and two UNKNOWN-county households at and $1 over the Adams County Tier 3 threshold. Expected [true, false, true, false].
  • wa_pte_tier Case 15 (2027): King County at the 2027 Tier 1 threshold after the $7,500 standard deduction, South Dakota, and UNKNOWN falling to Adams County Tier 2. Expected [1, 0, 2].
  • wa_senior_disabled_property_tax_exemption Cases 19 and 20: the 2026 and 2027 King County households from Cases 16 and 17, each beside a Marshall County, SD household. Expected [4_320, 0] and [5_340, 0].

All four cases fail on upstream/main with the ParameterNotFoundError above and pass with the fix. All WA YAML tests pass locally (824).

Invariants

  • Unchanged for WA households with a real county. county_key == county_str whenever state_code_str == "WA" and the county isn't UNKNOWN. Every WA county in the County enum (39) is a key of tier_1, tier_2 and tier_3.
  • Zero outside WA. wa_pte_tier == 0, wa_pte_income_eligible == false and the exemption is zero for non-WA households (the defined_for mask, unchanged).
  • No unknown key reaches the lookup when each household's county is in its own state or is UNKNOWN. In that case every key passed to the threshold lookup is a WA county key, so ParameterNotFoundError can't be raised for any household mix. In the default dataset every household's county FIPS matches its state. A household whose county is in a different state from its state_code (say WA with an Oregon county) still raises, as it does in the MD and IN formulas.

Other county-keyed lookups (checked, not changed here)

I probed the other formulas that index a county-keyed parameter, with one household per state and DC in one 2026 simulation, then again with UNKNOWN-county households added:

  • Already guarded against other states' counties: MN CCAP, MD local tax, MD CCS, IN county tax, CO CCAP, WI Shares, and the county-group formulas (PA and KS TANF; NY, IL and TN CCAP; VA CCSP).
  • vt_renter_credit has the same out-of-state crash. It indexes VT county tables with the county enum and has no guard. Only taxsim_state_property_tax_credit uses it, so full runs don't compute it. It also crashes for tax units larger than 8, which needs a policy decision.
  • MN CCAP, WI Shares and MD CCS crash for an in-state household whose county is UNKNOWN. They mask other states' counties but not UNKNOWN. In the default dataset every household has a valid county FIPS that matches its state, so only household input with county: UNKNOWN reaches this.

I'm proposing these as a separate follow-up.

axiom: n/a: microsim crash guard, no change to any WA rule; real-county WA results are unchanged

🤖 Generated with Claude Code

wa_pte_income_eligible and wa_pte_tier index the Washington county-keyed
income thresholds with every household's county_str. defined_for masks
the output but policyengine-core still runs the formula over the whole
population, so any simulation with a household outside Washington (or a
Washington household with an UNKNOWN county) raised ParameterNotFoundError
in every year. Full-population microsimulations crashed on
wa_senior_disabled_property_tax_exemption.

Look those households up under Adams County, the county the model
assigns a Washington household that gives none, as the MD and IN
county-tax formulas already do. Adds vectorized YAML cases that put a
Washington household and a South Dakota household in one simulation.

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

codecov Bot commented Oct 11, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (909176a) to head (2228eb7).
⚠️ Report is 404 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff            @@
##              main    #10113   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files            4         2    -2     
  Lines           76        30   -46     
  Branches         2         0    -2     
=========================================
- Hits            76        30   -46     
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 Harness.
📢 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.

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