Skip to content

Follow the statute for the NYC school tax rate reduction, IN supplemental homestead, VA 529 and WA senior/disabled property tax exemption - #10330

Merged
MaxGhenis merged 14 commits into
mainfrom
statute-bugs-nyc-in-va-wa
Nov 10, 2026
Merged

MaxGhenis merged 14 commits into
mainfrom
statute-bugs-nyc-in-va-wa

Conversation

@MaxGhenis

@MaxGhenis MaxGhenis commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #8947

What and why

This PR makes four state and local rules follow the statutes that govern them.

1. NYC school tax credit rate reduction amount: statutory second-band bases (2017+)

  • Source. N.Y. Tax Law § 606(ggg)(4-b)(A)-(C), from L.2017, ch. 59, Part C, § 2 (S.2009-C, PDF pages 8-9); § 7 applies it to tax years beginning on and after January 1, 2017 (PDF page 14). "Over $14,400 but not over $500,000: $25 plus 0.228% of excess over $14,400." The IT-201-I line 69a tables give the same values (2025, PDF page 21).
  • Before. Five marginal-rate scales (0.171% below the threshold, 0.228% above) started the second band from the first band's amount at the threshold: 36.936 joint and surviving spouse, 24.624 head of household, 20.52 single and separate.
  • Change. rate_reduction/amount/{threshold,base} (by filing status) and {first_band_rate,excess_rate} replace the scales from 2017-01-01. The amount equals 0.171% x T for T not over the threshold, and base + 0.228% x (T - threshold) above it. Income equal to the threshold stays in the first band. Eligibility and the $500,000 limits don't change.
  • Witness. Head of household, 2017, city taxable income $20,000: $25 + 0.228% x 5,600 = $37.768 (main: $37.392). Joint 2025, $30,000: $56.152 (main $56.088). Single 2025, $20,000: $39.24 (main $38.76).
  • Tests. The 2022 upper-branch cases lose their $1 tolerances, and new 2017 and 2025 cases cover every filing status, both sides of each threshold, negative income and a mixed vector. income_tax_elimination/integration.yaml restores the nyc_school_tax_credit and nyc_income_tax assertions that this bug had kept commented out. The differential invariant test's tolerance drops from 0.5 to 0.001. Tests that had pinned the old carry-in to 0.01 now use the statutory bases:
    • nyc_school_tax_credit.yaml: single $441,000 from 998.64 to 999.12, single $272,000 from 613.32 to 613.80, and joint $482,950 from 1,088.81 to 1,088.878;
    • the two NYC school tax credit reform files under contrib/local/nyc/stc/;
    • spm_unit_local_tax.yaml: single with $100,000 of wages, NYC tax down $0.48.

2. Indiana supplemental homestead credit: qualification, homestead base, referendum exclusion (2026+)

  • Source. IC 6-1.1-20.6-7.7(b)-(d), from SEA 1 (2025), SECTION 74 (enrolled act, PDF page 124). Subsection (b) entitles a person who qualifies for the IC 6-1.1-12-37 homestead standard deduction, or survives a spouse who qualified, to the credit "against the person's property tax liability on the same homestead". Subsection (c) sets it at the lesser of 0.1 x that liability or $300, and (d) leaves out taxes that voters approved by referendum or local public question. DLGF's June 18, 2025 FAQ, questions 26-30 and 33 (PDF pages 7-9), agrees and orders this credit after the Over 65 and Blind/Disabled credits.
  • Before. min(10% x all tax-unit real_estate_taxes, $300), with no qualification gate, no homestead-only base and no referendum exclusion.
  • Change. This PR adds three tax-unit variables: in_supplemental_homestead_credit_eligible (7.7(b) qualification), in_homestead_property_tax_liability (the homestead's liability after earlier credits and before this one) and in_homestead_referendum_property_tax. Credit = eligible x min(0.1 x max(liability - referendum, 0), $300). The first two have overridable defaults:
    • qualification defaults to real_estate_taxes > 0, the Over 65 credit's homeowner proxy, since 7.7(e) requires no application;
    • the liability defaults to real_estate_taxes less the Over 65 and Blind/Disabled credits, floored at 0, the order IC 6-1.1-20.6-3(3) (SEA 1 SECTION 73) sets.
    • The referendum tax defaults to 0.
  • Witness.
    • 2026, real_estate_taxes $3,000, qualification set to false: $0 (main: $300).
    • Eligible, homestead liability $2,000, referendum $1,000: $100 (main: $200 on $2,000 of taxes).
    • Defaults: $2,000 of taxes, under 65 and not disabled → $200. Disabled, $1,000 of taxes → 1,000 − 125 = 875 → $87.50 (main: $100).
  • Microsimulation. From 2026, an Indiana tax unit with real_estate_taxes gets min(10% × (taxes − Over 65 − Blind/Disabled credits), $300). This PR lowers main's amount by up to 10% of the earlier credits, and leaves it unchanged where both amounts reach the $300 cap. The referendum exclusion stays at 0 until the data supplies it.

3. Virginia Commonwealth Savers deduction: per-account cap and carryforward (2024+)

  • Source. Va. Code § 58.1-322.03(7)(a) limits the deduction to $4,000 for each prepaid tuition contract or college savings trust account, and lets the owner carry the excess forward until they have deducted all of it. Under (7)(b), an owner aged 70 or older deducts "the full amount ... less any amounts previously deducted". The 2024 Form 760 instructions, code 104 (PDF page 28) set the same per-account cap.
  • Before. Allocate Virginia's 529 deduction to the owner of record and deduct it after VAGI #9875 had already fixed owner allocation and placement after VAGI. Under 70 the model pooled the cap as min(total contributions, $4,000 x beneficiary count), and it had no carryforward.
  • Change. This PR adds two Person inputs: va_529_plan_deduction_capped_amount, the sum over owned accounts of min($4,000, contributions + undeducted carryforward), and va_529_plan_carryforward.
    • The default. Without account-level amounts, the capped amount defaults to min(contributions + carryforward, $4,000 x accounts): an even split across accounts, the largest deduction any split of the same totals allows. An account holding only a carryforward counts as one account.
    • The deduction. Under 70 an owner deducts the capped amount, up to contributions plus carryforward. From 70 the owner deducts contributions plus carryforward.
    • The instructions. They agree on the per-account cap. Their age-70 sentence speaks only of the year's contributions and says nothing about a carryforward, so PolicyEngine follows (7)(b) for the carryforward.
    • The owner and dependent masks don't change.
  • Witness.
    • 2024, age 45, $7,000 and $1,000 to two accounts (capped amount $5,000): $5,000 (main: $8,000).
    • $3,000 carried forward into one account, no new contribution: $3,000 (main: $0), with or without a beneficiary count.
    • A supplied capped amount of $9,000 against $2,500 available: $2,500.
    • Age 70, $1,000 plus $6,000 carried forward: $7,000.

4. Washington senior and disabled persons property tax exemption: statutory disability and veteran pathways (2008+)

  • Source. RCW 84.36.381(3)(a)(i): at least the age threshold, "or ... retired from regular gainful employment by reason of disability" (RCW 84.36.383(6) incorporates 42 U.S.C. 423(d)(1)(A)); unchanged since 2008 c 6 § 706 (PDF page 85). (3)(a)(ii): a veteran "entitled to and receiving compensation" from the VA at a total disability rating qualifies from collection year 2016 (2015 3rd sp.s. c 30). From 2020, a combined rating of 80% or more also qualifies (2019 c 453 §§ 1, 9, PDF pages 3, 13). 2025 c 200 §§ 1, 3, PDF pages 3, 5 lowers that rating to 40% from 2027. WAC 458-16A-130 agrees. 38 CFR 3.340(a)(2) authorizes a total rating for a 100% schedular evaluation.
  • Before. age >= threshold | is_disabled: a working 45-year-old with is_disabled qualified, and a compensated 80% veteran without is_disabled did not.
  • Change. This PR adds four Person inputs: wa_pte_retired_due_to_disability, wa_pte_receives_va_disability_compensation, wa_pte_va_service_connected_rating and wa_pte_va_total_disability_rating. It adds two parameters, veteran/combined_rating_threshold (none before 2020, 0.8 from 2020, 0.4 from 2027) and veteran/rating_requirement_in_effect (from 2016). Every VA pathway requires is_veteran.
    • Overridable defaults. wa_pte_retired_due_to_disability defaults to SSDI receipt (social_security_disability > 0), SSA's finding under the 42 U.S.C. 423(d)(1)(A) definition that RCW 84.36.383(6) adopts. wa_pte_receives_va_disability_compensation and wa_pte_va_total_disability_rating default to is_fully_disabled_service_connected_veteran. The combined rating defaults to 0.
    • Ratchet. This PR removes the wa_pte_categorical_eligible.py entry from the source-conflict ratchet's KNOWN_UNMARKED, since it deletes the "in practice" comment.
  • Witness.
    • 2024, age 45, is_disabled, not retired, $5,000 tax on a $500,000 home: not eligible, $0 (main: $3,600). Retired by reason of disability: $3,600.
    • Compensated veteran rated 40%: not eligible in 2026, eligible in 2027.
    • Defaults: age 45 with SSDI → eligible. A fully disabled service-connected veteran → eligible.
  • Before collection year 2016 (PolicyEngine ruling). RCW 84.36.381(3)(b) then admitted a veteran "with one hundred percent service-connected disability as provided in 42 U.S.C. Sec. 423 (d)(1)(A)", as that section read before January 1, 2005 (2008 c 6 § 706, PDF page 85). PolicyEngine reads that as a wholly service-connected disability that meets the Social Security definition, and records the reading as us-wa:pte-veteran-pathway-pre-2016 in source_conflicts/candidates.yaml. The model has no service-connection input for that definition, so is_disabled stands in for it.
    • The new input. wa_pte_wholly_service_connected_disability makes the proxy visible and overridable: it defaults to is_disabled or is_fully_disabled_service_connected_veteran.
    • Its limit. The proxy over-includes a veteran whose disability isn't service-connected; setting the input to false removes them.
    • The non-veteran disability-retirement rule applies to every year from 2008.
  • Microsimulation. With the defaults, the disability pathway opens to SSDI recipients. From 2016 the veteran pathway admits people the data marks both is_veteran and is_fully_disabled_service_connected_veteran. Before 2016 it admits veterans the data marks is_disabled or is_fully_disabled_service_connected_veteran. The combined-rating route below a total rating stays closed until the data supplies ratings.

Invariants

  • NYC:
    • amount >= 0, and amount = 0.171% x T at T <= threshold;
    • continuous except for the statutory step at the threshold, base - 0.171% x threshold: $0.064 joint, $0.376 HoH and $0.48 single;
    • it matches the § 606(ggg)(4-b) tables for every filing status (differential test, tolerance 0.001).
  • IN: 0 <= credit <= min($300, 0.1 x homestead liability); zero when not qualified or outside Indiana.
  • VA: deduction >= 0; under 70, deduction = capped amount <= contributions + carryforward when the default applies; from 70, deduction = contributions + carryforward; dependents get 0 (existing test_va_529_invariants.py).
  • WA: eligibility never falls when a pathway input turns on, and is_disabled alone never qualifies a non-veteran.

Test plan

  • YAML tests from the statute and agency examples, written failing first. I computed the "main" witness values above on main ce9377ee80 before the fix.
  • Run here at b403e928f1 (main merged), one file per process:
    • wa_pte_categorical_eligible.yaml: 26 passed (5 new default and pre-2016 cases); d46ac9be1d later adds Cases 27-29, which pin the 2016 rating requirement, the 2020 80% threshold and the VA-compensation default;
    • WA integration.yaml: 14 passed; wa_pte_eligible.yaml: 9; wa_senior_disabled_property_tax_exemption.yaml: 18;
    • in_supplemental_homestead_credit.yaml: 14 passed (3 new default cases);
    • in_supplemental_homestead_credit_in_aggregate.yaml and in_blind_disabled_property_tax_credit_in_aggregate.yaml: 2 and 2 passed (liability now derived: 1,850 and 1,725);
    • va_529_plan_deduction.yaml: 18 passed (2 new); VA plan_529/integration.yaml: 3;
    • nyc_school_tax_credit_rate_reduction_amount.yaml: 16; nyc_school_tax_credit.yaml: 5; income_tax_elimination/integration.yaml: 6; spm_unit_local_tax.yaml: 4;
    • the two contrib/local/nyc/stc/ files: 9 and 2;
    • test_nyc_school_tax_credit_income_test_invariants.py: 6; test_va_529_invariants.py: 2; test_source_conflicts.py: 103.
  • Merged main at ba9bfdea75 (merge 8fe868beb4).
  • Pull request run 42773096205 at 8fe868beb4 passed all 36 jobs on GitHub's merge with main 833c28bb47.
  • Source conflicts: in items 1-3 the agency's form or guidance agrees with the statute, or, on Virginia's age-70 carryforward, says nothing. Item 4's pre-2016 veteran pathway takes one of two readings of RCW 84.36.381(3)(b), which source_conflicts/candidates.yaml records as the ruled candidate us-wa:pte-veteran-pathway-pre-2016. The adjacent NYC fixed amount ($62.50 in § 606(ggg)(4-a)(B), $63 on the IT-201-I) now has its own candidate, us-ny:nyc-school-tax-credit-fixed-amount-rounding.
  • Removed parameter paths: this PR deletes the five rate_reduction/amount/{single,joint,separate,head_of_household,surviving_spouse} scales, and the changelog says so. Saved policies that edit them need the new threshold/base parameters.
  • ruff format / ruff check on changed files.
  • Changelog fragment in changelog.d/.

Microsimulation impact

We estimate that this PR raises New York City's school tax credit rate reduction amount by $0.64m (0.16%) and lowers Indiana's supplemental homestead credit by $0.77m (0.65%) in 2026 on the default dataset. We compared main at ba9bfdea75 with this branch at 8fe868beb4.

The rate reduction amount rises for 874 sampled tax units (2.03m weighted), by at most $0.48 each, and Indiana's credit falls for 21 sampled tax units (56,952 weighted), by at most $27.50. Household net income rises by $619,180 for 638 sampled households (1.61m weighted), and federal income tax rises by $18,649 for 162 sampled tax units (342,550 weighted), by at most $0.20 each. Only the taxsim_state_property_tax_credit total reads Indiana's credit, and no household's net income falls. Virginia's Commonwealth Savers deduction comes to $0 on main and on this branch, so Virginia income tax doesn't change. State income tax and household benefits don't change. No other variable reads wa_senior_disabled_property_tax_exemption, so we left it out of the comparison.

Output Main total Change Records changed (weighted)
nyc_school_tax_credit_rate_reduction_amount $398.3m +$639,928 874 (2,028,713)
in_supplemental_homestead_credit $118.5m −$765,500 21 (56,952)
va_529_plan_deduction_person $0 $0 0 (0)
va_income_tax $23.11bn $0 0 (0)
state_income_tax $577.3bn $0 0 (0)
income_tax $2,595.3bn +$18,649 162 (342,550)
household_net_income $17,198.7bn +$619,180 638 (1,613,300)
household_benefits $2,060.5bn $0 0 (0)

Proposed follow-up (not in this PR)

  • WA age threshold and its period. age_threshold.yaml uses 62 before 2020 and 61 from 2020. The 2008 and 2015 texts of RCW 84.36.381 require a claimant aged sixty-one or older on December 31 of the filing year, and exempt the taxes due the following year. In a collection-year period, which the veteran parameters use, the same claimant reaches 62 by the end of the period. So the follow-up first has to settle which year the model's period stands for, because under a collection-year period the 61 used from 2020 may need to change.

impact: NYC school tax credit rate reduction amount +$0.64m (+0.16%) and Indiana supplemental homestead credit −$0.77m (−0.65%) in 2026 (874 and 21 sampled tax units); household net income +$0.62m; state income tax and household benefits don't change
axiom: us-ny:policies/tax/it-201-instructions/nyc-school-tax-credit-rate-reduction#nyc_school_tax_credit_rate_reduction_amount encoded-correct (TY2025+ amount tables only; companion tests joint_return_first_band, single_second_band, head_of_household_second_band, above_final_bounded_row); TheAxiomFoundation/rulespec-us#1452 queued, extended with a comment (TY2017-2024 statutory amount tables; dated-source ingestion and re-pin); TheAxiomFoundation/rulespec-us#1510 queued, extended with two comments (per-account caps and carryforward; encoder approval and form-source re-pin); TheAxiomFoundation/rulespec-us#1862 queued, extended with a comment (IN 2026 liability-source ingestion and re-pin); TheAxiomFoundation/rulespec-us#1863 queued, extended with a comment (WA dated-source ingestion and re-pin).

🤖 Generated with Claude Code

MaxGhenis and others added 7 commits October 20, 2026 20:07
… reduction amount

Tax Law § 606(ggg)(4-b)(A)-(C), as enacted by L.2017, ch. 59, Part C, § 2
for tax years from 2017, gives the amount over each threshold as a fixed
base ($37 joint and surviving spouse, $25 head of household, $21 single
and separate) plus 0.228% of the excess, not the first band carried
forward at 0.171%. Fixes #8947.

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

IC 6-1.1-20.6-7.7(b)-(d) (SEA 1 2025, SECTION 74) gives the credit only to
a person who qualifies for the IC 6-1.1-12-37 homestead standard deduction
(or that person's surviving spouse), computes it on the liability of that
homestead, and leaves out referendum-approved taxes. Generic
real_estate_taxes establish none of these, so qualification, the homestead
liability and its referendum component become tax-unit inputs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…forward excess

Va. Code § 58.1-322.03(7)(a) limits the deduction to $4,000 per contract or
account and carries forward the excess until fully deducted; (7)(b) lets an
owner aged 70 or older deduct the full amount less amounts previously
deducted. Add va_529_plan_deduction_capped_amount (account-level result,
defaulting to an even split across accounts) and va_529_plan_carryforward.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…led property tax exemption

RCW 84.36.381(3)(a) admits a claimant under the age threshold only if
retired from regular gainful employment by reason of disability, or as a
veteran entitled to and receiving VA compensation at a total disability
rating or, from collection year 2020, a combined rating of 80% (40% from
2027). Before 2016 the veteran text can be read more than one way, so
generic disability is kept for veterans in those years.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
nyc_school_tax_credit.yaml and the NYC school tax credit reform tests
asserted the first band carried forward at 0.171% (20.52 single, 36.936
joint) to 0.01; they now use the § 606(ggg)(4-b) bases of $21 and $37.

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

A single filer with $100,000 of wages has city taxable income of $92,000,
so the rate reduction amount is $21 + 0.228% x 80,000 = $203.40, $0.48
more than before; NYC tax, SPM taxes and SPM net income move by $0.48.

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

DTrim99 commented Oct 21, 2026

Copy link
Copy Markdown
Collaborator

PR Review: Follow the statute for the NYC school tax rate reduction, IN supplemental homestead, VA 529 and WA senior/disabled property tax exemption

PR #10330 (author: MaxGhenis), head 29778a23d8. This is a first review. The PR makes four model changes:

  • NYC school tax credit, rate reduction amount (2017+). The five marginal-rate scales become rate_reduction/amount/{threshold,base} by filing status plus first_band_rate and excess_rate. Above the threshold, the amount is now the statute's fixed base ($37 / $25 / $21) plus 0.228% of the excess, instead of the first band carried forward at 0.171%.
  • Indiana supplemental homestead credit (2026+). Three new tax-unit inputs: homestead deduction qualification, the homestead's liability after earlier credits, and its referendum taxes. The credit is now eligible × min(10% × max(liability − referendum, 0), $300), instead of 10% of all real_estate_taxes.
  • Virginia Commonwealth Savers deduction. Two new Person inputs: a per-account capped amount and an undeducted carryforward. Under 70 the deduction is the capped amount; from 70 it is contributions plus carryforward.
  • Washington senior/disabled property tax exemption (2008+). is_disabled no longer qualifies anyone by itself. The disability pathway needs the new wa_pte_retired_due_to_disability input. The veteran pathway needs is_veteran, receipt of VA compensation and a qualifying rating, dated by two new parameters. Before 2016, veterans keep is_disabled.
  • Tests and housekeeping. 15 YAML files and one Python test change. The WA file leaves the ratchet's KNOWN_UNMARKED, and there is a fixed changelog fragment.

The PR has no comments or reviews yet. It touches no partner test file.

Path prefixes:

  • IN/ = policyengine_us/variables/gov/states/in/tax/property/supplemental_homestead_credit/
  • VA/ = policyengine_us/variables/gov/states/va/tax/income/deductions/plan_529/
  • WA/ = policyengine_us/variables/gov/states/wa/dor/property_tax_exemption/senior_disabled/
  • WAT/ = policyengine_us/tests/policy/baseline/gov/states/wa/dor/property_tax_exemption/senior_disabled/

Sources read for this review (all fetched 2026-10-21):

Summary

What is right. All four changes follow the governing statute in the years the PR claims. None of them is a statute-versus-form conflict: in each case the agency's form or guidance agrees with the statute, and the old model matched neither.

  • 1. NYC (2017+): statute and forms agree on fixed bases.
    • The statute. § 606(ggg)(4-b), as amended by L.2017, ch. 59, Part C, § 2 (file pages 8-9):
      • (A) joint and surviving spouse: "Over $21,600 but not over $500,000: $37 plus 0.228% of excess over $21,600";
      • (B) head of household: "Over $14,400 but not over $500,000: $25 plus 0.228% of excess over $14,400";
      • (C) single and separate: "Over $12,000 but not over $500,000: $21 plus 0.228% of excess over $12,000".
    • Years. (4): "For taxable years beginning after two thousand sixteen, the credit shall equal the 'fixed' amount ... plus the 'rate reduction' amount". § 7: "shall apply to taxable years beginning on and after January 1, 2017" (file page 14). The current § 606 text is unchanged.
    • The forms. IT-201-I 2025 (file page 21) prints "$ 37 plus 0.228% of the excess over $21,600", "$ 21 plus 0.228% of the excess over $12,000" and "$ 25 plus 0.228% of the excess over $14,400". The 2017 (file page 30) and 2018 (file page 31) tables are identical, with ".228%".
    • No conflict. The old test comments ("The $37 amount is rounded in the instructions") were wrong: the bases are in the statute itself. The jump at each threshold ($0.064 joint, $0.376 head of household, $0.48 single) is statutory.
  • 2. Indiana (2026+): statute and DLGF agree.
    • The credit. IC 6-1.1-20.6-7.7 (SECTION 74, file page 124):
      • (a) "applies for property taxes first due and payable in calendar years beginning after December 31, 2025";
      • (b) a person who "qualifies for a standard deduction from the assessed value of the person's homestead under IC 6-1.1-12-37 (or is married at the time of death to a deceased spouse who qualifies ...) is also entitled to receive a supplemental homestead credit ... against the person's property tax liability on the same homestead";
      • (c) "the lesser of: (1) ... the property tax liability first due and payable on the homestead property for the calendar year; multiplied by ... one-tenth (0.1); or (2) three hundred dollars ($300)";
      • (d) "Property taxes imposed after being approved by the voters in a referendum or local public question shall not be considered".
    • The order of credits is statutory. IC 6-1.1-20.6-3(3), as amended by SECTION 73 (file pages 123-124), defines the liability for section 7.7 as "determined after application of all credits and deductions under this article or IC 6-3.6, including the credit granted by section 7 or 7.5 of this chapter, but not including the credits granted under section 7.7 or 8.5". So the over-65 and blind/disabled credits come first.
    • DLGF agrees. Question 27: "The credit should be calculated based on the tax liability attributable to the homestead property that is receiving the Homestead Deduction." Question 29: it "would be calculated after the application of the new Over 65 and Blind/Disabled Credits." The question 33 table lists them as steps 5 and 6.
    • Not the deduction. The brief for this review framed this item as the IC 6-1.1-12-37.5 supplemental homestead deduction. The PR touches only the 7.7 credit; DLGF question 26 stresses that they are different. PE-US doesn't model the 37.5 deduction.
  • 3. Virginia: statute and instructions agree on the cap and carryforward.
    • (7)(a): "the amount deducted on any individual income tax return in any taxable year shall be limited to $ 4,000 per prepaid tuition contract or college savings trust account ... If the purchase price or annual contribution to a college savings trust account exceeds $ 4,000, the remainder may be carried forward and subtracted in future taxable years until the purchase price or college savings trust contribution has been fully deducted". The transferee "shall succeed to the transferor's tax attributes ..., including ... carryover".
    • (7)(b): an owner who "has attained age 70 shall not be subject to the limitation ... Such taxpayer shall be allowed a deduction for the full amount paid for the contract or contributed to a college savings trust account, less any amounts previously deducted."
    • The instructions (2024 and 2025, code 104): "enter the lesser of $4,000 or the amount contributed during the taxable year to each Commonwealth Savers account ... If you contributed more than $4,000 per account during the taxable year, you may carry forward any undeducted amounts until the contribution has been fully deducted."
    • The age-70 sentence differs slightly (Suggestion G2), but it is silent rather than contrary, and the statute controls.
    • Keep claimable filers' own exemptions under the HI and VA dependent-exemption repeal #10271 doesn't interact. It changes only the HI/VA dependent-exemption repeal reform and its tests. The 529 deduction still masks to head and spouse, and the two PRs share no file or variable.
  • 4. Washington: statute, session laws and WAC agree.
    • 2008 text (2008 c 6 § 706, file page 85), RCW 84.36.381(3): "(a) sixty-one years of age or older on December 31st of the year in which the exemption claim is filed, or must have been, at the time of filing, retired from regular gainful employment by reason of disability, or (b) a veteran of the armed forces of the United States with one hundred percent service-connected disability as provided in 42 U.S.C. Sec. 423 (d)(1)(A) as amended prior to January 1, 2005."
    • 2015 (3rd sp.s. c 30 § 2): "(ii) A veteran ... entitled to and receiving compensation from the United States department of veterans affairs at a total disability rating for a service-connected disability". § 4: "This act applies to taxes levied for collection in 2016 and thereafter."
    • 2019 (c 453 § 1, file page 3): "(A) A combined service-connected evaluation rating of eighty percent or higher; or (B) A total disability rating for a service-connected disability without regard to evaluation percent." § 9 (file page 13): "applies for taxes levied for collection in 2020 and thereafter."
    • 2025 (c 200 § 1, file page 3): "A combined service-connected evaluation rating of ((80)) 40 percent or higher". § 3 (file page 5): "applies to taxes levied for collection in 2027 and thereafter."
    • Disability. RCW 84.36.383(6): "'Disability' has the same meaning as provided in 42 U.S.C. Sec. 423(d)(1)(A)". WAC 458-16A-130(2)(b): "retired from regular gainful employment and unable to engage in any substantial gainful activity by reason of any medically determinable physical or mental impairment".
    • WAC timing. WAC 458-16A-130(2)(c) already prints 40 percent. The amendment took effect 2/20/26, for claims filed in 2026 for 2027 collections, so it is consistent with § 3.
    • The parameters (.inf for 2016-2019, 0.8 from 2020, 0.4 from 2027; the rating requirement from 2016) match these dates, keyed by collection year.
  • Tests. I hand-checked 22 expectations, listed under Verified correct. CI passed every changed file on a tree that contains Count the FL and SD LIHEAP comments in the source-conflict ratchet baseline #10334.
  • Batch checks.

There are no critical findings and three should-address items:

There are also six suggestions: Virginia default and guard, Virginia age-70 wording, Indiana citation and pre-2026 gating, NYC follow-ups including an unrecorded $62.50/$63 conflict, the Washington age threshold, and the PR body.

Status.

Critical (must fix)

None.

Should address

S1. The Axiom line axiom: pending: hub files parity is not a valid form

  • Where: the last line of the PR body.
  • The rule (CLAUDE.md, "Axiom Parity"): a PR that adds, updates or fixes policy needs one of encoded-correct, <rulespec PR> encoded, <rulespec issue> queued or n/a: <reason>. pending is not one of them. We flagged the same form on Fix the 2021 CTC split and surviving-spouse phaseout threshold #10306, Fix pre-2018 AMT child exemption and income base #10308, Fix Arkansas tax indexing and Kentucky's 2021 dependent care credit #10324 and Fix Montana disability exclusion repeal, elderly credit receipt timing and occupancy tests #10331.
  • Why n/a doesn't fit. All four provisions are statutory, and the PR changes results in 2017+ (NYC), 2026+ (IN), every modelled VA year, and 2008+ (WA).
  • What rulespec-us has (main cea8379e0b):
    • NYC. us-ny/policies/tax/it-201-instructions/nyc-school-tax-credit-rate-reduction.yaml, rule nyc_school_tax_credit_rate_reduction_amount, computes ..._second_band_base_amount + ..._excess_rate * max(0, nyc_taxable_income - ..._first_band_upper_bound) with bases 37, 21 and 25 and rates 0.00171 and 0.00228.
    • Virginia. us-va/statutes/58.1/58/1-322/03.yaml holds only college_savings_annual_deduction_limit = 4000 and college_savings_no_limit_age = 70.
    • Indiana and Washington. There is no module or issue for IC 6-1.1-20.6-7.7 or RCW 84.36.381.
  • Severity: should. An invalid Axiom line is a merge blocker, not a model error.
  • Fix: change the line to axiom: us-ny:policies/tax/it-201-instructions/nyc-school-tax-credit-rate-reduction#nyc_school_tax_credit_rate_reduction_amount encoded-correct; TheAxiomFoundation/rulespec-us#<ny> queued; TheAxiomFoundation/rulespec-us#1510 queued; TheAxiomFoundation/rulespec-us#<in> queued; TheAxiomFoundation/rulespec-us#<wa> queued. Alternatively, cite encoder PRs as encoded. Before relying on these:
    • <ny>. Extend What program participation can affect one's eligibility for the Affordable Connectivity Program? #1452, or file a new issue, for TY2017-2024 under § 606(ggg)(4-b), for example us-ny/statutes/TAX/606/ggg.yaml. Cite L.2017 ch. 59 Part C §§ 2 and 7. Companion cases from the statute's tables:
      • 2017 head of household, $20,000 → 37.768;
      • 2017 surviving spouse, $21,601 → 37.00228;
      • 2017 separate, $12,000 → 20.52;
      • 2017 joint, $21,600 → 36.936.
    • Allow data tests to work without a policyengine_us package being installed #1510. Comment with this PR's carryforward and per-account cases, which follow from (7)(a)-(b):
      • under 70, $7,000 and $1,000 to two accounts → 5,000, with 3,000 carried forward;
      • a $3,000 carryforward and no new contribution → 3,000;
      • age 70, $1,000 plus a $6,000 carryforward → 7,000.
    • <in>. A new issue modelled on Add AMT parameters for 2020-2023 #1467:
      • Target: for example us-in/statutes/6-1.1-20.6-7.7.yaml, citing 7.7(a)-(d) and § 3(3) verbatim (quoted above).
      • Required outputs: qualification, the homestead base net of referendum taxes, and the credit.
      • Companion cases:
        • liability 2,000 → 200; 3,000 → 300; 5,000 → 300;
        • not qualified → 0;
        • liability 2,000 with 1,000 of referendum tax → 100;
        • gross homestead tax 2,000 after the $150 over-65 and $125 blind/disabled credits → 1,725 → 172.50.
    • <wa>. A new issue:
      • Target: for example us-wa/statutes/84/84.36/84/36/381.yaml, with the 2008, 2015, 2019 and 2025 texts and their application sections.
      • Companion cases:
        • age 45, retired by reason of disability → eligible; generic disability and not retired → ineligible;
        • compensated 40% veteran → ineligible for 2026 and eligible for 2027;
        • 2024, 80% → eligible; 70% → ineligible;
        • 2019, 90% without a total rating → ineligible; with a total rating → eligible;
        • a total rating without receipt of compensation → ineligible.
      • Blocker: each new issue must name it.

S2. The new inputs default to "off", so the Indiana credit and Washington's disability and veteran pathways vanish unless a user sets them

  • Where:
    • IN/in_supplemental_homestead_credit_eligible.py and IN/in_homestead_property_tax_liability.py: inputs with no formula, defaulting to false and 0;
    • WA/wa_pte_retired_due_to_disability.py, WA/wa_pte_receives_va_disability_compensation.py, WA/wa_pte_va_service_connected_rating.py and WA/wa_pte_va_total_disability_rating.py: inputs defaulting to false and 0.
  • What changes for users who don't set them:
    • Indiana (2026+). A homeowner with real_estate_taxes gets $0. The PR's own Case 7 shows it: $3,000 of taxes gives 0, where main gave $300. Yet 7.7(e) says "A person is not required to file an application for the credit", and the county auditor applies it to every homestead with the IC 6-1.1-12-37 standard deduction.
    • Washington. A 45-year-old SSDI recipient who owns a home gets nothing unless wa_pte_retired_due_to_disability is set. So does a 100% service-connected veteran unless three new inputs are set. The household calculator and the microdata supply is_disabled, not these.
    • Microsimulation. The PR body says so: "this credit is zero in microsimulation until the data supplies them" and "only the age pathway remains".
  • The statute does give a basis for defaults:
    • Washington disability. RCW 84.36.383(6) adopts 42 U.S.C. 423(d)(1)(A), the Social Security disability definition. An SSDI award (social_security_disability > 0) is SSA's finding under that definition and requires inability to engage in substantial gainful activity, which is WAC 458-16A-130(2)(b)'s test.
    • Washington veterans. is_fully_disabled_service_connected_veteran already exists ("a fully disabled veteran who became so as a result of an injury during service"). It implies a total rating.
    • Indiana. The sibling credits in the same umbrella still use real_estate_taxes > 0 as the homestead proxy (in_over_65_property_tax_credit_eligible) and real_estate_taxes as the base. Section 3(3) fixes the order: liability after the over-65 and blind/disabled credits. The PR's aggregate tests compute exactly that by hand ($2,000 − $150 − $125 = $1,725).
  • Reach. Neither output feeds household net income. The Indiana credit is read only by taxsim_state_property_tax_credit (via state_property_tax_credits.yaml), and wa_senior_disabled_property_tax_exemption has no consumer. Both outputs are now zero or understated for most households that would qualify under the law.
  • Severity: should. Input defaults give wrong results under ordinary use where the model already has statute-grounded values (compare Fix Montana disability exclusion repeal, elderly credit receipt timing and occupancy tests #10331 S3).
  • Fix: keep the inputs as overrides, but give them formulas:
    • in_supplemental_homestead_credit_eligible: add(tax_unit, period, ["real_estate_taxes"]) > 0, the over-65 credit's homeowner proxy.
    • in_homestead_property_tax_liability: max_(real_estate_taxes − in_over_65_property_tax_credit − in_blind_disabled_property_tax_credit, 0), citing § 3(3).
    • wa_pte_retired_due_to_disability: social_security_disability > 0. Optionally add adult is_ssi_disabled without earnings above SGA, since SSI uses the same adult definition.
    • wa_pte_va_total_disability_rating: is_fully_disabled_service_connected_veteran.
    • Tests. The explicit-input cases still pass. Add one default case per state, and let the IN aggregate cases assert the derived 1,725 and 1,850.
    • Caveat. As on Fix Montana disability exclusion repeal, elderly credit receipt timing and occupancy tests #10331 S4, a value supplied for one unit gives other units the default array, not the formula. If that matters, split each override into its own input.

S3. Before 2016, Washington veterans still qualify on generic is_disabled, which both of the PR's readings reject

  • Where:
    • WA/wa_pte_categorical_eligible.py:43: earlier_veteran = person("is_disabled", period);
    • WAT/wa_pte_categorical_eligible.yaml:347, Case 20 (2015, age 45, is_disabled, is_veteran, not retired): expected true;
    • the PR body, "Left as is (more than one defensible reading)".
  • The 2008 text (file page 85): a veteran "with one hundred percent service-connected disability as provided in 42 U.S.C. Sec. 423 (d)(1)(A)".
  • The PR's two readings: "a 100% VA rating, or ... a wholly service-connected disability meeting the Social Security definition".
    • Both require service connection, and the first also needs a rating.
    • Generic disability isn't enough. is_disabled establishes neither: the PR itself says it covers non-service-connected and still-working cases.
  • The result. Case 20's veteran is under 61, is not retired by reason of disability (so (3)(a) fails), and has no service-connected disability on record (so (3)(b) fails). The model says eligible, and a new test now pins that.
  • A fitting input exists. is_fully_disabled_service_connected_veteran is common to both readings: 100% and service-connected. A person fully disabled in that sense also meets 423(d)(1)(A).
  • Severity: should. The reach is limited to veterans under 61, not retired by reason of disability, in collection years 2008-2015. But the deferral's stated reason doesn't support keeping is_disabled, and the PR adds a test that pins it.
  • Fix:
    • Use is_fully_disabled_service_connected_veteran for earlier_veteran.
    • Change Case 20 to expect false with is_disabled alone.
    • Add a 2015 case with the service-connected input → true.
    • Update the PR body's "Left as is" paragraph.

Suggestions

G1. Virginia: the default ignores carryforward-only accounts, and a supplied capped amount is not bounded

  • Where: VA/va_529_plan_deduction_capped_amount.py:34-35: min_(contributions + carryforward, p.cap * accounts), with accounts = count_529_contribution_beneficiaries.
  • The gap. That input is an int with no default (0), labelled "Number of beneficiaries to 529 college savings plan contributions". An owner with a carryforward and no contribution this year naturally has 0 beneficiaries of contributions, so the default gives min(3,000, 0) = 0.
    • The witness depends on it. The PR body's "$3,000 carried forward into one account, no new contribution: $3,000" holds only because the test supplies count 1 and the capped amount (va_529_plan_deduction.yaml:195).
  • No bound. Under 70, a supplied va_529_plan_deduction_capped_amount above contributions plus carryforward passes straight through (VA/va_529_plan_deduction_person.py:35).
  • Fix (optional):
    • In the default, count at least one account when the carryforward is positive, or document that accounts holding only a carryforward must be counted.
    • Bound the under-70 branch by contributions + carryforward.
    • Add a carryforward-only case with count 0.

G2. Virginia: the instructions' age-70 sentence covers only this year's contributions

  • The instructions (2024 and 2025, code 104, file page 28): "However, if you are age 70 or older on or before December 31 of the taxable year, you may deduct the entire amount contributed during the taxable year."
  • The statute (7)(b): "the full amount paid for the contract or contributed ..., less any amounts previously deducted", which includes an undeducted carryforward.
  • Which source controls. The statute does: it is explicit, and the instructions are silent on carryforward at 70 rather than contrary. The PR follows the statute (contributions plus carryforward).
  • The wording. The PR body and the YAML header say the instructions "agree" on (7)(b).
  • Fix (optional):
    • Say that the instructions are silent on the age-70 carryforward.
    • If you have evidence that Tax limits age-70 filers to the current year, add a candidates.yaml entry (follows: statute).

G3. Indiana: cite the statute for the order, and gate the credit before 2026

  • The citation. IN/in_homestead_property_tax_liability.py cites DLGF questions 27, 29 and 33 for "after the credits applied before the supplemental homestead credit". IC 6-1.1-20.6-3(3), as amended by SECTION 73 (file pages 123-124, quoted under Summary), is the statutory source. Add it.
  • The pre-2026 credit. 7.7(a) starts the credit with taxes first due in 2026, and the new formula comment says so. But rate.yaml and cap.yaml extrapolate back, so the standalone variable is nonzero in 2025 when the inputs are supplied. Only the umbrella list excludes it (the aggregate file's 2025 case says so). This predates the PR.
  • Fix (optional): add an in-effect gate, or zero values before 2026.

G4. NYC follow-ups

  • (a) Pre-2017 extrapolation. The new parameters start 2017-01-01 and extrapolate back. § 606(ggg)(4) starts the rate reduction amount "for taxable years beginning after two thousand sixteen"; for 2016, (3) gave only a flat $125 / $62.50. This predates the PR, because the old scales also extrapolated back. Consider a zero before 2017.
  • (b) Removed parameter paths. The PR deletes five parameter paths (rate_reduction/amount/{single,joint,separate,head_of_household,surviving_spouse}). Saved policies that edit them would break. Say so in the PR body or changelog.
  • (c) An unrecorded conflict in the same credit.
    • The statute: § 606(ggg)(4-a)(B) sets the fixed amount for unmarried, head-of-household and separate filers at "sixty-two dollars and fifty cents".
    • The form and the model: fixed/amount.yaml uses the IT-201-I's $63, and its own comment says "IT-201-I Table 1 rounds the statutory $62.50 ... to $63".
    • The record. This statute-versus-form conflict is in neither candidates.yaml nor the register.
    • Why mention it here. It is outside this diff, but it is the mirror of item 1: there the PR rejects a form rounding in favour of the statute.
    • Fix: record it as a candidate, or fix it in a follow-up.

G5. Washington age threshold

  • The text. The PR body notes that age_threshold.yaml uses 62 before 2020 although the 2008 and 2015 texts say sixty-one.
  • The timing. The test is "on December 31st of the year in which the exemption claim is filed", and the exemption applies to taxes "due and payable in the year following the year in which a claim is filed". So in a collection-year period, which is how the new veteran parameters are keyed, a claimant is 62 by the end of the collection year.
  • Fix (optional): file the follow-up, and settle the period convention in it, since the 2020+ value of 61 may then be the one that's off.

G6. PR body

  • Stale test plan. It lists files "Not run here ... CI covers these". CI has now run all of them (see CI status).
  • Bill names. The reference titles call the 2019 and 2025 bills "SSB 5160" and "HB 1106". The session laws are ESSB 5160 and EHB 1106.
  • Fix (optional): update both, and fix the Axiom line (S1).

Verified correct

  • Hand-checks (22 expectations).
    • NYC:
      1. 2017 head of household, $20,000: 25 + 0.00228 × 5,600 = 37.768.
      2. 2025 joint, $30,000: 37 + 0.00228 × 8,400 = 56.152.
      3. nyc_school_tax_credit.yaml, single $441,000: 21 + 0.00228 × 429,000 = 999.12.
      4. 2017 separate at $12,000: "not over", so 0.00171 × 12,000 = 20.52. Joint at $21,600: 36.936.
      5. Mixed vector: 0.00171 × 10,000 = 17.1; 25 + 0.00228 = 25.00228; 37 + 0.00228 × 78,400 = 215.752.
      6. spm_unit_local_tax.yaml Case 1:
        • Taxable income. 100,000 − 8,000 = 92,000.
        • Amount. 21 + 0.00228 × 80,000 = 203.40, up $0.48.
        • NYC tax. 3,175.1702 − 0.48 = 3,174.6902.
        • SPM net income. It is the float32 output (62,902.109 against 62,902.113 by hand).
      7. Contrib Case 5, joint $263,950: 37 + 0.00228 × 242,350 = 589.558, plus 125 = 714.558.
      8. Elimination-credit Cases 1 and 3:
        • Case 1: 63 + 25 + 0.00228 × 8,400 = 107.152.
        • Case 3: 125 + 37 + 0.00228 × 18,896 = 205.08288, so nyc_income_tax = 1,375.71552 − 192.25944 − 205.08288 = 978.3732.
    • Indiana (2026):
      9. Case 9: min(0.1 × (2,000 − 1,000), 300) = 100.
      10. Case 10: max(1,000 − 1,200, 0) = 0, so 0.
      11. Case 6, joint, liability 4,000: min(400, 300) = 300.
      12. Blind/disabled aggregate:
      - Liability. 2,000 − 150 − 125 = 1,725.
      - Credit. 172.50.
      - Umbrella. 150 + 125 + 172.50 = 447.50.
      13. Over-65 aggregate: 0.1 × 1,850 = 185; umbrella 335.
    • Virginia (2024):
      14. Uneven accounts, capped amount supplied (5,000): 5,000; main gave min(8,000, 8,000) = 8,000.
      15. Default with a $2,000 carryforward and one account: min(3,000 + 2,000, 4,000) = 4,000.
      16. Age 70: 1,000 + 6,000 = 7,000, with the supplied cap ignored.
      17. Spouses: [7,000, 500 + 2,500 = 3,000], so 10,000. Dependent owner: [0, 0].
    • Washington:
      18. Case 8 (2024): generic disability and not retired, so ineligible and 0. Main gave 1,500 + 300,000 × 0.007 = 3,600.
      19. Case 9: 5,000 × 0.30 + max(60,000, 300,000) × (3,500 / 500,000) = 3,600.
      20. Integration Case 2 (King County lookup): 4,800 × 0.30 = 1,440; 300,000 × 0.00672 = 2,016; total 3,456.
      21. Case 10: 0.4 < 0.8 in 2026, so false; 0.4 ≥ 0.4 in 2027, so true. Cases 16-18 (2019): 0.9 against .inf without a total rating → false; rating 1 → total → true; total flag → true.
      22. Case 21 vector:
      - 61 ≥ 61 → true;
      - generic disability only → false (is_veteran is veterans_benefits > 0, which is 0);
      - retired → true;
      - compensated veteran at 0.8 → true.
  • References.
    • NYC. S.2009-C file pages 8-9 hold the (4-b) tables and file page 14 holds § 7, so the title "#page 8-9,14" follows CLAUDE.md. IT-201-I 2017 p. 30, 2018 p. 31 and 2025 p. 21 show line 69a.
    • Washington. The 2008 session law p. 85 shows (3). 2019 c 453 file pages 3 and 13 show § 1(3)(a)(ii) and § 9. 2025 c 200 file pages 3 and 5 show "((80)) 40" and § 3.
    • Indiana. SEA 1 p. 124 shows 7.7, and the DLGF FAQ pp. 7-9 show questions 26-33. The variable tuples carry # PDF pages 7-9 comments.
    • Virginia. File page 28 shows code 104.
  • Code.
    • NYC. p.threshold[filing_status] and p.base[filing_status] cover all five statuses. max_(nyc_taxable_income, 0) handles negative income. where(taxable_income > threshold, ...) keeps the threshold itself in the lower band, which is the statute's "not over". The $500,000 limits stay in the unchanged eligibility variable.
    • Washington. where on the scalar rating_requirement_in_effect broadcasts. The .inf threshold makes rating >= threshold false for 2016-2019. rating >= 1 implements 38 CFR 3.340(a)(2).
    • Vectorization. Everything is vectorized, and the mixed NYC and WA cases exercise it.
  • Callers at the head.
    • No orphaned paths. No code reads the deleted NYC scales; the two NYC STC reforms read the variable, not the parameters.
    • in_supplemental_homestead_credit is read only through state_property_tax_credits.yaml (2026 block).
    • wa_pte_categorical_eligible is read only by wa_pte_eligible.
    • va_529_plan_deduction_person is unchanged downstream.
  • Batch lessons:
  • Changelog. changelog.d/statute-bugs-nyc-in-va-wa.fixed.md is a top-level fragment, and fixed is the right type.

Axiom line assessment

Invalid: axiom: pending: hub files parity (S1).

  • Recommended form: axiom: us-ny:policies/tax/it-201-instructions/nyc-school-tax-credit-rate-reduction#nyc_school_tax_credit_rate_reduction_amount encoded-correct; TheAxiomFoundation/rulespec-us#<ny> queued; TheAxiomFoundation/rulespec-us#1510 queued; TheAxiomFoundation/rulespec-us#<in> queued; TheAxiomFoundation/rulespec-us#<wa> queued.
  • Not n/a. These are statutory provisions with changed results in claimed years.

CI status

27 pass, 3 skipping, 0 pending, 0 failed. I re-checked gh pr checks 10330 at the end of this review, and the head is still 29778a23d8.

  • The green run. Run 39617825642 (pull_request, created 2026-10-21 14:33 UTC) tested merge commit 78ef47c3, which merges the head into fec98b87c8 (Count the FL and SD LIHEAP comments in the source-conflict ratchet baseline #10334).
  • Where the changed tests ran:
    • Rest-a: the NYC files (nyc_school_tax_credit_rate_reduction_amount.yaml 16 cases, nyc_school_tax_credit.yaml, the elimination integration.yaml 6, and ..._eligible.yaml).
    • States-shard-1: the Indiana unit file (11) and both aggregate files.
    • States-shard-2: the Washington files (categorical 21, integration 14, eligible, exemption 18).
    • States-shard-4: the Virginia files (va_529_plan_deduction.yaml 16, integration.yaml 3).
    • Household: spm_unit_local_tax.yaml.
    • Contrib other-shard-1: both NYC STC reform files.
    • Rest (Python + variables): the NYC invariant tests and test_va_529_invariants.py.
    • Full suites. Microsimulation and Household API Partners passed.
  • Locally: I ran no tests or model probes. CI is green at the current head. The expectations and the S2/S3/G1 traces were checked by hand from the code, parameters and sources. The ratchet regex was applied statically.

Branch status

0 critical, 3 should and 6 suggestions are open at this head.

Verdict: REQUEST_CHANGES

@DTrim99 DTrim99 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

All four changes follow the governing statute in the claimed years (NYC 606(ggg)(4-b), IN 6-1.1-20.6-7.7, VA 58.1-322.03(7), WA 84.36.381(3)(a)), and 22 expectations hand-check. Three should-address items: (1) the 'pending: hub files parity' axiom line is invalid; (2) the new IN and WA inputs default to off, so the IN credit is $0 and the WA disability and veteran pathways close for every household (microsim included) unless they're set, so derive the defaults; (3) pre-2016 WA veterans still qualify on generic is_disabled. Six suggestions. Full report in the comment above.

@MaxGhenis

Copy link
Copy Markdown
Contributor Author

Picking this up for the US hub. I'll post a hand-off comment here when it's ready.


Generated by Claude Code

The new inputs defaulted to off, so the Indiana supplemental homestead
credit was $0 and Washington's disability and veteran pathways closed
for every household that didn't set them, microsimulation included.
Each is now a formula that a supplied value overrides:

- IN qualification: real_estate_taxes > 0, the Over 65 credit's
  homeowner proxy (the credit needs no application).
- IN homestead liability: real_estate_taxes less the Over 65 and
  Blind/Disabled credits, which IC 6-1.1-20.6-3(3) applies first.
- WA retired by reason of disability: SSDI receipt, SSA's finding under
  the 42 U.S.C. 423(d)(1)(A) definition RCW 84.36.383(6) adopts.
- WA VA compensation and total rating: a fully disabled
  service-connected veteran.

Before collection year 2016 the veteran pathway now reads a documented
input, wa_pte_wholly_service_connected_disability, which defaults to
the is_disabled proxy (or a fully disabled service-connected veteran),
so the assumption is visible and overridable without closing the
pathway.

Also: count a carryforward-only Virginia account and bound a supplied
capped amount by what is available; say the VA instructions are silent
on the age-70 carryforward; record the NYC fixed-amount rounding as a
source-conflict candidate; name the removed NYC parameters in the
changelog.

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

Copy link
Copy Markdown
Contributor Author

Ready for hub review

Head: b403e928f160730e92868be2891f74513e55125e

Picked up at 29778a23d8 (unchanged since 2026-10-21). Two commits on top:

  • 433fccbb1e: a clean merge of main.
  • b403e928f1: the review changes.

What the item asked for

2. The new IN and WA inputs default to off

Each input is now a formula that a supplied value overrides, with the default documented in its documentation.

Indiana:

  • Qualification. in_supplemental_homestead_credit_eligible.py:28-29 defaults to real_estate_taxes > 0, the homeowner proxy in_over_65_property_tax_credit_eligible uses. The docstring (:8-18) notes the credit needs no application (7.7(e)) and that the model has no input for the IC 6-1.1-12-37 standard deduction.
    • It now has defined_for = StateCode.IN.
  • Liability. in_homestead_property_tax_liability.py:32-39 defaults to max_(real_estate_taxes − in_over_65_property_tax_credit − in_blind_disabled_property_tax_credit, 0). IC 6-1.1-20.6-3(3), as amended by SEA 1 SECTION 73, orders the 7.7 base after the section 7 and 7.5 credits.
    • That citation is now in the reference tuple, merged with SECTION 74 as one SEA 1 entry (#page=123, # PDF pages 123-124), per the one-entry-per-document rule. It is also on the property credit (G3).
  • Referendum tax. in_homestead_referendum_property_tax stays an input with default 0.

Washington:

  • Disability retirement. wa_pte_retired_due_to_disability.py defaults to social_security_disability > 0. RCW 84.36.383(6) adopts 42 U.S.C. 423(d)(1)(A), and an SSDI award is SSA's finding under that definition.
  • VA compensation and total rating. wa_pte_receives_va_disability_compensation.py and wa_pte_va_total_disability_rating.py both default to is_fully_disabled_service_connected_veteran.
  • Combined rating. wa_pte_va_service_connected_rating stays an input with default 0.

Microsimulation-facing behaviour, before → after (no microsimulation run here; from the formulas and which inputs the household and microdata supply):

  • Indiana supplemental homestead credit, 2026+.
    • Before: $0 for every tax unit that didn't set the inputs, so all of microsim.
    • After: min(10% × (real_estate_taxes − Over 65 credit − Blind/Disabled credit), $300) for every Indiana tax unit with real_estate_taxes > 0. That is main's amount (min(10% × real_estate_taxes, $300), which I read on main), less 10% of any earlier credit.
    • The referendum exclusion stays at $0 until data supplies it.
  • Washington exemption.
    • Before: only the age pathway was open.
    • After, the disability pathway: open to anyone with social_security_disability > 0, which the household calculator and the CPS-based data supply.
    • After, the veteran pathway: open from collection year 2016 to anyone marked is_fully_disabled_service_connected_veteran. I didn't check whether the microdata populates it; if it doesn't, that pathway stays closed in microsim.
    • Combined ratings below a total rating stay unmodelled unless supplied.
    • Before 2016 see item 3.
  • Reach. Neither output feeds household net income: the IN credit feeds taxsim_state_property_tax_credit via state_property_tax_credits.yaml, and the WA exemption has no consumer (per DTrim99's caller check, which I didn't repeat).

3. Pre-2016 WA veterans on generic is_disabled

I kept the hub's ruling and made the assumption visible.

  • The new input. wa_pte_wholly_service_connected_disability.py is a Person bool used only with is_veteran and only before collection year 2016. It defaults to is_disabled | is_fully_disabled_service_connected_veteran.
  • Its documentation states the ruling's reading, that the proxy over-includes a veteran whose disability isn't service-connected, and that setting it to false removes that veteran.
  • The pathway. wa_pte_categorical_eligible.py:38-44 reads it for earlier_veteran. It isn't closed: Case 20 (2015, is_disabled veteran) still expects true.
  • New cases. Case 25 (2015, is_fully_disabled_service_connected_veteran only → true) and Case 26 (2015, is_disabled with the input set to false → false).

Suggestions

  • G1, Virginia: taken.
    • va_529_plan_deduction_capped_amount.py:36-39 counts at least one account when a carryforward remains.
    • va_529_plan_deduction_person.py:35-38 bounds the under-70 deduction by contributions plus carryforward.
    • Two new cases: carryforward-only with no beneficiary count → 3,000; supplied $9,000 against $2,500 available → 2,500.
  • G2, the VA age-70 wording: taken. The va_529_plan_deduction.yaml header (:171-180) and the PR body now say the instructions agree on the per-account cap but are silent on the age-70 carryforward, so (7)(b) governs. No candidate, since there is no evidence Tax limits age-70 filers to the current year.
  • G3, the IN citation: taken. The pre-2026 gate: skipped. It predates the PR, and the umbrella list already excludes the credit before 2026.
  • G4.
    • (a) Pre-2017 NYC zero: skipped (predates the PR).
    • (b) Removed parameter paths: taken. The changelog and body name the five removed scales.
    • (c) The $62.50 / $63 conflict: taken as a candidate, us-ny:nyc-school-tax-credit-fixed-amount-rounding (follows: conflicting-source, unreviewed), in source_conflicts/candidates.yaml.
  • G5, WA age threshold: skipped. It's a follow-up, below.
  • G6.
    • The body is updated: test plan, defaults, the pre-2016 ruling, the VA wording.
    • The bill names in veteran/combined_rating_threshold.yaml:18,20 ("SSB 5160", "HB 1106") are not changed. I couldn't reach the legislature site to confirm the ESSB / EHB designations, and the session-law file is 5160-S.SL.pdf. unverified.

Hand computations (new expectations)

  • IN Case 12 (default, under 65, $2,000 of taxes): no earlier credit, liability 2,000, credit 200.
  • IN Case 13 (default renter): not eligible, 0.
  • IN Case 14 (disabled, $1,000 of taxes): Blind/Disabled credit 125; liability 1,000 − 125 = 875; credit 10% = 87.50.
  • IN aggregates, now derived:
    • Over-65 case: 2,000 − 150 = 1,850, credit 185, umbrella 335.
    • Blind/disabled case: 2,000 − 150 − 125 = 1,725, credit 172.50, umbrella 447.50.
  • WA Case 22 (2024, age 45, SSDI 12,000): retired by reason of disability, eligible.
  • WA Case 23 (is_disabled, no SSDI): not eligible.
  • WA Case 24 (fully disabled service-connected veteran): compensated, total rating, eligible.
  • WA Cases 25 and 26 as above.
  • VA: min(0 + 3,000, 4,000 × 1) = 3,000; min(9,000, 2,000 + 500) = 2,500.

Tests run (one file per process, at this head)

  • wa_pte_categorical_eligible.yaml: 26 passed (5 new).
  • WA integration.yaml: 14 passed.
  • wa_pte_eligible.yaml: 9 passed.
  • wa_senior_disabled_property_tax_exemption.yaml: 18 passed.
  • in_supplemental_homestead_credit.yaml: 14 passed (3 new).
  • in_supplemental_homestead_credit_in_aggregate.yaml: 2 passed.
  • in_blind_disabled_property_tax_credit_in_aggregate.yaml: 2 passed.
  • va_529_plan_deduction.yaml: 18 passed (2 new).
  • VA plan_529/integration.yaml: 3 passed.
  • nyc_school_tax_credit_rate_reduction_amount.yaml: 16 passed.
  • nyc_school_tax_credit.yaml: 5 passed.
  • income_tax_elimination/integration.yaml: 6 passed.
  • spm_unit_local_tax.yaml: 4 passed.
  • contrib/local/nyc/stc/..._and_eligibility_by_children.yaml: 9 passed.
  • contrib/local/nyc/stc/..._joint_override.yaml: 2 passed.
  • test_nyc_school_tax_credit_income_test_invariants.py: 6 passed.
  • test_va_529_invariants.py: 2 passed.
  • test_source_conflicts.py: 103 passed.

Mutants (each in its own process, restored after)

Mutant Result
M1 IN liability ignores earlier credits killed (1 failed)
M2 IN default qualification off killed (2)
M3 WA SSDI default off killed (1)
M4 WA total-rating default inverted killed (2)
M5 WA pre-2016 back to bare is_disabled killed (2)
M6 VA carryforward-only account not counted killed (1)
M7 VA under-70 bound removed killed (1)

7 of 7 killed.

CI

Pull request run 41418693987 at b403e928f1: success, all 34 jobs passed, including Microsimulation and Household API Partners.

Judgment calls

  1. Defaults that read other variables. As DTrim99 notes (from Fix Montana disability exclusion repeal, elderly credit receipt timing and occupancy tests #10331), supplying one of these inputs for one unit gives other units the default array, not the formula. I left them as single variables, as he allows.
  2. The pre-2016 default also accepts is_fully_disabled_service_connected_veteran. That veteran meets the ruling's reading (wholly service-connected, under the Social Security definition), so it only adds people the statute admits.
  3. Indiana defined_for. I added defined_for = StateCode.IN to the two IN defaults, so they read Indiana-only credits and are 0 elsewhere. The credit itself was already Indiana-only.
  4. Unverified. The statutory phrasings in the new docstrings (7.7(e), § 3(3), RCW 84.36.383(6) and the 2008 text) come from DTrim99's report and the PR's references. I couldn't fetch iga.in.gov or leg.wa.gov here.

Proposed follow-up issue

Washington senior/disabled exemption: settle the age threshold and its period before 2020. age_threshold.yaml uses 62 before 2020, though the 2008 and 2015 texts of RCW 84.36.381(3)(a) say sixty-one, tested at December 31 of the filing year. Since the exemption applies to the following collection year, decide whether the model's period is the filing or the collection year, and set the pre- and post-2020 values on that basis.

Reply to DTrim99

Thanks for the review. Taking each item in turn:

  1. S1, the axiom line. The hub's.
  2. S2, defaults. Every new input now has an overridable default, as you suggested.
    • IN qualification: real_estate_taxes > 0.
    • IN liability: real_estate_taxes less the Over 65 and Blind/Disabled credits (§ 3(3)).
    • WA disability retirement: SSDI receipt.
    • WA VA compensation and total rating: is_fully_disabled_service_connected_veteran.
    • New default cases: IN Cases 12–14, WA Cases 22–24.
    • The IN aggregates now derive 1,850 and 1,725.
    • The before and after microsim behaviour is above.
  3. S3, pre-2016 veterans. The hub ruled on this: before collection year 2016 the statute means Social Security's definition, wholly service-connected. The model has no service-connection input for that, so is_disabled is the proxy, which keeps the pathway open rather than closing it for every veteran.
    • So I kept the reading but made it explicit. wa_pte_wholly_service_connected_disability defaults to is_disabled (or is_fully_disabled_service_connected_veteran), documents that it over-includes non-service-connected cases, and can be set to false.
    • Case 20 still expects true. Case 25 covers the service-connected input and Case 26 the override.
  4. G1. Both changes taken, with two cases.
  5. G2. Wording fixed in the YAML header and the body.
  6. G3. Citation added. The pre-2026 gate is skipped: it predates the PR, and the umbrella excludes the credit.
  7. G4. (a) skipped (predates the PR). (b) and (c) taken, (c) as a candidate.
  8. G5. Proposed as a follow-up issue.
  9. G6. The body is updated. The bill names are unchanged, because I couldn't verify ESSB / EHB from here.

Generated by Claude Code

@MaxGhenis

MaxGhenis commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor Author

Hub review in progress (finished)

The final review is at #10330 (comment) and supersedes these interim notes.


Generated by Claude Code

@MaxGhenis

Copy link
Copy Markdown
Contributor Author

Ready for hub review
VERDICT: APPROVE
b403e92
METHODOLOGY_CHOICE: yes

The head is unchanged since the hand-off. It merges cleanly into current main (6145ad4513, 95 commits ahead of the PR's merge at 433fccbb1e), and the source-conflict guard passes on that local merge.

METHODOLOGY_CHOICE: yes because of one item. Before collection year 2016 the PR reads RCW 84.36.381(3)(b)'s "one hundred percent service-connected disability as provided in 42 U.S.C. Sec. 423(d)(1)(A)" as a wholly service-connected disability under the Social Security definition. It proxies that with is_disabled (P3-4). The other three items follow a single statutory text.

No P1.

  • Every DTrim99 should-address item is resolved. S3 is resolved by the hub's ruling, not by the code change he proposed; see the table.
  • Every hand value I computed matches the head.
  • Every changed test file passes, one per process.
  • 10 of the 13 mutants I ran are killed. The 3 survivors are the subject of P2-2 and P3-1, and their tests are given below.

Findings

P2-1. The PR body has no impact: line

  • Where: the PR body. The axiom: line is present; there is no impact: line.
  • Why it matters: the diff moves baseline outputs (see "Impact" below), so the hub's population run is needed before the line can be written. The build brief left this line to the hub, so this is a merge gate, not an author defect.
  • Fix: after the population run, the hub adds impact: <...> covering NYC 2017+, IN 2026+ and WA 2008+. Virginia needs no entry: it can't move at baseline.

P2-2. No test pins the 2016 rating-requirement date or the 2020 80% threshold date

  • Where:

    • policyengine_us/parameters/gov/states/wa/dor/property_tax_exemption/senior_disabled/veteran/rating_requirement_in_effect.yaml:7 (2016-01-01: true);
    • .../veteran/combined_rating_threshold.yaml:6 (2020-01-01: 0.8).
  • Failure scenario: either date can move a year or more with every test still green. For example, a typo of 2018-01-01: true would let a 2016 or 2017 disabled veteran with no VA rating qualify. The tests pin 2015 (Cases 19-20, 25-26), 2019 (Cases 16-18), 2024 and the 2026/2027 pair (Case 10), but no 2016 or 2020 case.

  • Run: each mutant in its own process on a scratch worktree, against wa_pte_categorical_eligible.yaml:

    • M8, rating_requirement_in_effect 2016 → 2018: 26 passed (survives).
    • M9, combined_rating_threshold 0.8 from 2020 → 2021: 26 passed (survives).
  • Fix: add these to wa_pte_categorical_eligible.yaml.

    - name: Case 27, in 2016 a disabled veteran without a qualifying VA rating no longer qualifies.
      period: 2016
      input:
        state_code: WA
        age: 45
        is_disabled: true
        is_veteran: true
      output:
        wa_pte_categorical_eligible: false
    
    - name: Case 28, in 2020 a compensated veteran rated 80 percent qualifies.
      period: 2020
      input:
        state_code: WA
        age: 45
        is_veteran: true
        wa_pte_receives_va_disability_compensation: true
        wa_pte_va_service_connected_rating: 0.8
      output:
        wa_pte_categorical_eligible: true
    • Hand values.
      • Case 27: in 2016 the rating requirement applies. The compensation default (is_fully_disabled_service_connected_veteran) is false, there's no SSDI, and age 45 is under 62, so the result is false.
      • Case 28: 0.8 ≥ 0.8 in 2020 and age 45 is under 61, so eligibility comes from the rating alone: true.
    • Run. Both pass at the head (3 passed with Case 29 below). Case 27 kills M8 and Case 28 kills M9 (1 failed, 2 passed each).

P3-1. Nothing pins the VA-compensation default to is_fully_disabled_service_connected_veteran

  • Where: .../senior_disabled/wa_pte_receives_va_disability_compensation.py:21-22.

  • Mutant: M13 replaces the default with person("is_veteran", period), which would open the 80% route to any rated veteran. Result: 26 passed (survives).

  • Fix: add the case below. It passes at the head and kills M13 (1 failed, 2 passed).

    - name: Case 29, by default a veteran who is not a fully disabled service-connected veteran is not taken to receive VA compensation.
      period: 2024
      input:
        state_code: WA
        age: 45
        is_veteran: true
        wa_pte_va_service_connected_rating: 0.8
      output:
        wa_pte_receives_va_disability_compensation: false
        wa_pte_categorical_eligible: false

P3-2. The pre-2016 docstring paraphrases "one hundred percent" as "total"

  • Where: .../senior_disabled/wa_pte_wholly_service_connected_disability.py:9-11.

  • The problem: the 2008 text, as DTrim99's report quotes it, says "one hundred percent service-connected disability". From 2016 the statute separately admits a VA "total disability rating", so "total" here reads as that later term of art.

  • Replacement:

            "For taxes levied for collection before 2016, RCW 84.36.381(3)(b) "
            "admitted a veteran with \"one hundred percent service-connected "
            "disability as provided in 42 U.S.C. Sec. 423 (d)(1)(A)\". PolicyEngine reads "

P3-3. The 2019 and 2025 bill designations

  • Where: .../veteran/combined_rating_threshold.yaml:18,20. The titles say (SSB 5160) and (HB 1106); the hand-off left them because it couldn't verify them.
  • What I found: I couldn't reach leg.wa.gov either. Web search results (secondary) describe ESSB 5160 as becoming chapter 453, Laws of 2019, and chapter 200, Laws of 2025 as EHB 1106. That agrees with DTrim99's report.
  • Replacement, if the hub confirms on the legislature site:
    • "2019 c 453 ss 1, 9 (ESSB 5160) #page 3,13";
    • "2025 c 200 ss 1, 3 (EHB 1106) #page 3,5".

P3-4. The pre-2016 reading is recorded only as a local hub ruling

  • Where: .../wa_pte_wholly_service_connected_disability.py and wa_pte_categorical_eligible.py:38-41.
  • The rule: CLAUDE.md says that where "the text and the canons don't settle a reading, follow the administering agency's reading" and record the choice in the shared register.
  • What the PR does: it picks one reading without citing WA DOR practice. It records the choice only in the docstring and the body. Neither source_conflicts/candidates.yaml nor the register has an entry; I grepped both for pte-veteran and us-wa:.
  • Effect: none on results. It is limited to collection years 2008-2015, a veteran under 62 without SSDI, and the overridable input.
  • Suggestion: the hub records its ruling wherever it keeps statutory-reading rulings meant to be shared. If WA DOR's pre-2016 practice turns up, the agency-reading rule applies.

P3-5. Two wording points in the body and hand-off

  • IN microsimulation sentence. The body says the default is "main's amount, less 10% of the earlier credits where they apply". That holds only below the cap. At $3,000 or more of tax, both sides give $300 unless the earlier credits pull 10% of the remainder under $300. Replacement: "main's amount, less up to 10% of the earlier credits (nothing where both are capped at $300)".
  • The G3 citation. The hand-off says the § 3(3) citation "is also on the property credit". In the diff, only in_homestead_property_tax_liability.py gained it; the reference tuple of in_supplemental_homestead_credit.py is unchanged. The liability variable is the one G3 asked about, so G3 is resolved. The extra sentence just isn't borne out.

DTrim99's review: item by item (head b403e928f1)

Item Status at head Evidence
S1 axiom line Resolved in form The line now uses encoded-correct and queued entries, the forms the template allows. The NYC claim names the module and companion cases, as CONTRIBUTING requires. I can't read rulespec-us from this session, so the existence and labels of #1452, #1510, #1862 and #1863 are unchecked.
S2 defaults Resolved Every new IN and WA input except the referendum tax and the combined rating has a formula default that a supplied value overrides. Probes at the head: IN, 2026, $2,000 of tax → 200; a disabled filer with $1,000 → 87.5. WA, 2024, age 45: SSDI 12,000 → eligible; is_veteran and is_fully_disabled_service_connected_veteran → eligible. The default cases are Cases 12-14 (IN) and Cases 22-24 (WA), and the IN aggregates now derive 1,850 and 1,725. Mutants M1-M4 are killed.
S3 pre-2016 veterans Resolved by hub ruling The code implements the ruling: earlier_veteran reads wa_pte_wholly_service_connected_disability, which defaults to is_disabled | is_fully_disabled_service_connected_veteran. It is overridable (Case 26) and its over-inclusion is documented. Compared with main it is narrower: main let any is_disabled person qualify, and the head requires is_veteran (probe: 2015, is_disabled non-veteran, true on main, false at the head). DTrim99's code change (bare is_fully_disabled_service_connected_veteran, Case 20 → false) was not taken, because the hub ruled the other way. M5 and M12 are killed.
G1 VA Taken va_529_plan_deduction_capped_amount.py:39 and va_529_plan_deduction_person.py:35-38. Probe: a $3,000 carryforward alone → 3,000. M6 and M7 are killed.
G2 VA wording Taken va_529_plan_deduction.yaml:171-180 and the body.
G3 IN Citation taken; gate skipped The skip holds. main and the head both give the standalone credit 200 in 2025 for $2,000 of tax, and only the 2026 block of state_property_tax_credits.yaml lists it.
G4 NYC (a) skipped, (b) and (c) taken (a): main's scales start 2022-01-01 and extrapolate back, so pre-2017 values predate the PR. (b): the changelog names the five removed paths, and nothing in the repo references them (grep over .py, .yaml, .md, .ipynb and .json). (c): us-ny:nyc-school-tax-credit-fixed-amount-rounding is in candidates.yaml, and the guard passes.
G5 WA age Follow-up See "WA age threshold" below.
G6 body Taken, except the bill names P3-3.

Every should-address item (S1-S3) is resolved, so the hub can dismiss the CHANGES_REQUESTED review with a note. S3 is resolved by the hub's ruling, and S1's issue references can't be checked from here.

The new defaults against their statutes

I couldn't fetch IC 6-1.1-20.6-7.7, RCW 84.36.381 or RCW 84.36.383(6) (see "What I couldn't check"). This compares the docstrings with the statutory text as DTrim99's report quotes it.

  • in_supplemental_homestead_credit_eligible = real_estate_taxes > 0.
    • 7.7(b) ties the credit to the IC 6-1.1-12-37 standard deduction, and 7.7(e), per the report, says no application is required.
    • The same proxy is in in_over_65_property_tax_credit_eligible.py:44.
    • The docstring says it over-includes non-homestead tax and can be overridden. That is sound.
  • in_homestead_property_tax_liability = max_(real_estate_taxes − over-65 credit − blind/disabled credit, 0).
    • § 3(3), as quoted, uses the liability "after application of all credits and deductions under this article ..., including the credit granted by section 7 or 7.5 ..., but not including the credits granted under section 7.7 or 8.5".
    • The model's Blind/Disabled credit cites IC 6-1.1-51.3-2. That is still a credit "under this article" (article 1.1), so subtracting it fits § 3(3). The hand-off's gloss ("after the section 7 and 7.5 credits") names the wrong sections for the model's two credits, but the code and its comment are right.
  • wa_pte_retired_due_to_disability = social_security_disability > 0.
    • Per the report, RCW 84.36.383(6) gives "disability" the meaning in 42 U.S.C. 423(d)(1)(A). A web search snippet of a 2025 WA DOR form says the same. A county form there numbers the subsection (7), so check the number on the current RCW.
    • SSDI is SSA's finding under that definition, so the default under-includes rather than over-includes.
    • Probe: an SSI-only disabled adult with no SSDI goes from eligible on main to ineligible at the head. "Retired from regular gainful employment" supports leaving out someone never employed, so I treat this as a defensible default, not a defect.
  • wa_pte_receives_va_disability_compensation and wa_pte_va_total_disability_rating = is_fully_disabled_service_connected_veteran. A fully disabled veteran whose disability is service-connected is consistent with a total rating and with receipt of compensation. It is a pure input that defaults to false.

Judgment calls

  • The pre-2016 default also accepts is_fully_disabled_service_connected_veteran. This holds. Under the ruling's reading (wholly service-connected, meeting the Social Security definition), that person is the clearest case the 2008 text admits. Adding them only widens the default toward the statute. Case 25 pins it, and M12 (| → &) is killed by Cases 20 and 25.
  • defined_for = StateCode.IN on the two IN defaults. This holds. Probe: for a Washington household the eligibility default is False. The only reader, in_supplemental_homestead_credit, is itself IN-only. Supplied values still override, since inputs bypass the formula.

WA age threshold

The PR leaves age_threshold.yaml as main has it (62 before 2020, 61 from 2020). Cases 5 and 6 (2019: 61 ineligible, 62 eligible) are already on main.

What the PR changes. It removes the is_disabled route that used to mask the threshold for one group:

Probe main Head
2015, age 61, is_disabled, no SSDI true false
2019, age 61, is_disabled, no SSDI true false

Whether that is worse depends on the period convention.

  • The new veteran parameters are keyed by collection year.
  • The 2008 test, as quoted, is "sixty-one ... on December 31st of the year in which the exemption claim is filed".
  • So in collection year Y the claimant must have been 61 at the end of Y−1, which makes them 62 in Y. Under that convention 62 before 2020 is right, and the newly visible results are correct.
  • It is the post-2020 value of 61 that would then be a year early.

I found no source confirming the 2019 c 332 change that age_threshold.yaml cites; secondary search results describe 61 throughout. Not made worse by this PR. The hand-off's follow-up, "settle the age threshold and its period", is the right home for it.

Impact (baseline outputs the diff from main moves)

NYC, every year the model computes NYC tax. The head's parameters start 2017-01-01 and main's 2022-01-01; both extrapolate back.

  • What moves: nyc_school_tax_credit_rate_reduction_amount rises for eligible filers whose city taxable income is above the threshold: +$0.064 joint and surviving spouse, +$0.376 head of household, +$0.48 single and separate.
  • Downstream: nyc_school_tax_credit and the household's net income rise by the same amount, and nyc_income_tax falls by it. The two STC contrib reforms move too.
  • Probes, main → head: 2017 HoH at $20,000: 37.392 → 37.768. 2025 joint at $30,000: 56.088 → 56.152. 2025 single at $20,000: 38.76 → 39.24. At $12,000 single, 20.52 on both sides.

Indiana, 2026+.

  • What moves: in_supplemental_homestead_credit falls by up to 10% of the Over 65 and Blind/Disabled credits (at most $27.50) where the credit is under the $300 cap. Probe: disabled filer, $1,000 of tax, 100 → 87.5.
  • Unchanged: qualification adds no new zeros, because main's 10% of zero tax was already 0. Pre-2026 values are unchanged.
  • Reach: the credit feeds only taxsim_state_property_tax_credit (via state_property_tax_credits.yaml) and its alias state_property_tax_credit. Nothing reads those, so household net income doesn't move.

Virginia: no baseline change. With no carryforward and no supplied capped amount, the head computes min(contributions, 4,000 × accounts) under 70 and contributions from 70, which is main's per-person formula exactly. Probe: $8,000 across two accounts gives 8,000 on both sides.

Washington, collection years 2008+.

  • What moves: wa_pte_categorical_eligible, wa_pte_eligible and wa_senior_disabled_property_tax_exemption:
    • people under the age threshold who are is_disabled without SSDI lose eligibility, except veterans before collection year 2016, who keep it;
    • SSDI recipients without is_disabled gain it;
    • from 2016, people marked both is_fully_disabled_service_connected_veteran and is_veteran gain it.
  • Probe: 2024, age 45, is_disabled, $5,000 of tax: the exemption goes from 1,500 to 0.
  • Reach: nothing reads wa_senior_disabled_property_tax_exemption, so household net income doesn't move.

Population check for the hub:

  • NYC: the count of eligible NYC tax units above the threshold, by filing status. The total is bounded by $0.48 per unit.
  • IN 2026: the count of tax units with an Over 65 or Blind/Disabled credit and the credit under the cap.
  • WA: counts of people under the threshold with is_disabled and no SSDI (losers), with SSDI and not is_disabled (gainers), and with both is_fully_disabled_service_connected_veteran and veterans_benefits > 0.
    • is_veteran is veterans_benefits > 0. A person marked fully disabled service-connected but with no VA benefits stays closed out (probe: false).
    • Whether the microdata populates is_fully_disabled_service_connected_veteran at all decides whether the veteran pathway opens in microsimulation.

Checked and sound

  • Defect reproduced at main (6145ad4513, probes with Simulation):

    • NYC 2017 HoH at $20,000 gives 37.392, but § 606(ggg)(4-b)(B) as quoted gives 25 + 0.00228 × 5,600 = 37.768.
    • IN: no qualification gate and no earlier-credit ordering (disabled filer, $1,000: 100).
    • WA: generic is_disabled qualifies a working 45-year-old (true).
  • Hand values, computed before reading the formulas, all matched at the head:

    • NYC:
      • 37.768;
      • 56.152;
      • 39.24;
      • 20.52 at the single threshold;
      • 21.00228 at $12,001;
      • 37.00228 surviving spouse at $21,601;
      • 0 at negative income.
      • The steps at the threshold are 0.064, 0.376 and 0.48.
    • IN:
      • 200;
      • 0 when qualification is set to false;
      • 87.5;
      • min(0.1 × (2,000 − 1,000), 300) = 100.
    • VA:
      • 5,000 (capped amount supplied);
      • 3,000 (carryforward only);
      • min(9,000, 2,500) = 2,500;
      • 1,000 + 6,000 = 7,000 at age 70.
    • WA: the eligibility results listed above.
  • Code paths:

    • NYC covers all five filing statuses through p.threshold[filing_status] and p.base[filing_status], and > keeps the threshold in the first band.
    • The IN credit is eligible × min(0.1 × max(liability − referendum, 0), 300).
    • The WA where on the scalar rating_requirement_in_effect broadcasts.
    • The mixed vector cases pass (Case 21; the NYC mixed case).
  • Tests at the head, one file per process, all passing:

    • test_source_conflicts.py: 103.
    • NYC:
      • income_tax_elimination/integration.yaml: 6;
      • nyc_school_tax_credit.yaml: 5;
      • nyc_school_tax_credit_rate_reduction_amount.yaml: 16;
      • test_nyc_school_tax_credit_income_test_invariants.py: 6;
      • the STC contrib files: 9 and 2;
      • spm_unit_local_tax.yaml: 4.
    • IN:
      • the two IN aggregate files: 2 and 2;
      • in_supplemental_homestead_credit.yaml: 14.
    • VA:
      • va_529_plan_deduction.yaml: 18;
      • VA plan_529/integration.yaml: 3;
      • test_va_529_invariants.py: 2.
    • WA:
      • WA integration.yaml: 14;
      • wa_pte_categorical_eligible.yaml: 26;
      • wa_pte_eligible.yaml: 9;
      • wa_senior_disabled_property_tax_exemption.yaml: 18.
    • These match the body's counts.
  • The guard on the merge with current main. test_source_conflicts.py on a local merge of the head into 6145ad4513 gives 103 passed. main changed candidates.yaml and the guard after 433fccbb1e, and the merge is clean.

  • Mutants. Each ran in its own process on a scratch worktree, and the tree was restored and git status was clean after each.

    Mutant Result Killed by
    M1 IN liability ignores earlier credits killed IN Case 14
    M2 IN default qualification off killed IN Cases 12, 14
    M3 WA SSDI default off killed WA Case 22
    M4 WA total-rating default inverted killed WA Cases 12, 24
    M5 WA pre-2016 back to bare is_disabled killed WA Cases 25, 26
    M6 VA carryforward-only account not counted killed "Without account-level amounts, a carryforward-only account counts as one account"
    M7 VA under-70 bound removed killed "A supplied capped amount is bounded by contributions plus carryforward"
    M8 (mine) rating requirement 2016 → 2018 survives proposed Case 27 (P2-2)
    M9 (mine) 80% threshold 2020 → 2021 survives proposed Case 28 (P2-2)
    M10 (mine) NYC > → >= at the threshold killed 5 NYC threshold cases, including "2017 separate filer at the threshold stays in the lower branch"
    M11 (mine) WA drop is_veteran & killed WA Cases 15, 19
    M12 (mine) pre-2016 default | → & killed WA Cases 20, 25
    M13 (mine) compensation default → is_veteran survives proposed Case 29 (P3-1)

    The hand-off's 7 of 7 kills reproduce.

  • Readers of each changed variable (grep outside tests):

    • in_supplemental_homestead_credit → state_property_tax_credits.yaml → taxsim_state_property_tax_credit → state_property_tax_credit, which nothing reads.
    • The IN inputs are read only inside the credit's folder.
    • va_529_plan_deduction_capped_amount and va_529_plan_carryforward → va_529_plan_deduction_person → va_529_plan_deduction.
    • The WA inputs → wa_pte_categorical_eligible → wa_pte_eligible → wa_senior_disabled_property_tax_exemption, which nothing reads.
    • nyc_school_tax_credit_rate_reduction_amount → nyc_school_tax_credit and the two STC reforms.
    • Nothing references the removed NYC parameter paths.
    • No reader is made reachable and worse. The age-61 group is discussed above.
  • CI. Pull request run 41418693987 at b403e928f1: 34 jobs, all success, including Microsimulation and Household API Partners. None was skipped or cancelled.

  • Scope.

  • Changelog. There is one top-level fixed fragment, and it names the removed parameter paths.

  • Body claims checked: the witnesses (all reproduce), the invariants, the test counts, the removed paths and the candidate id. Fixes #8947 is the NYC second-band issue, and it matches item 1.

What I couldn't check


Generated by Claude Code

@MaxGhenis

Copy link
Copy Markdown
Contributor Author

Picking this up for the US hub. I'll post a hand-off comment here when it's ready.


Generated by Claude Code

Bring the branch up to date with main before the round 1 fixes; no conflicts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LwQq6FTzhwSszCc1DcqdbZ
Round 1 review found three surviving mutants: moving the rating requirement
to 2018, moving the 80% threshold to 2021, or defaulting VA compensation from
is_veteran all left the WA tests green. Cases 27-29 kill each one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LwQq6FTzhwSszCc1DcqdbZ
- wa_pte_wholly_service_connected_disability: quote RCW 84.36.381(3)(b)'s
  'one hundred percent service-connected disability' instead of calling it a
  'total' disability, since from 2016 the statute uses 'total disability
  rating' as a VA term.
- combined_rating_threshold: the enrolled session laws are ESSB 5160
  (2019 c 453) and EHB 1106 (2025 c 200).
- candidates.yaml: record the 2026-10-21 ruling on the pre-2016 veteran
  pathway (us-wa:pte-veteran-pathway-pre-2016), with is_disabled as the
  overridable proxy, so the reading is in the shared backlog.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LwQq6FTzhwSszCc1DcqdbZ
@MaxGhenis

Copy link
Copy Markdown
Contributor Author

Ready for hub review
a95f0d6

Round 1 fixes from the review at b403e928f1. The head was unchanged from that review when I picked this up.

Commits

  • 5767117861: merges origin/main (at 9f0242f6de) with a merge commit. No files conflicted.
  • d46ac9be1d: adds WA Cases 27-29.
  • a95f0d6fcb: fixes the pre-2016 docstring and the bill designations, and adds the candidates.yaml item.

Items

  1. Pick-up and merge. I posted the pick-up comment, then merged origin/main with no conflicts. I reran every test file the PR changes on the merged tree, one per process (counts below).
  2. P2-2 / P3-1, WA cases. I added the review's Cases 27, 28 and 29 verbatim at wa_pte_categorical_eligible.yaml:444, :454 and :465.
    • Hand values, worked out before running:

      • Case 27 (2016): age 45 is under 62 and there's no SSDI. The rating requirement is on in 2016, and the compensation default (is_fully_disabled_service_connected_veteran) is false, so the result is false.
      • Case 28 (2020): the threshold is 0.8, and 0.8 ≥ 0.8 with compensation supplied, so the result is true. Age 45 is under 61.
      • Case 29 (2024): the compensation default is false and there's no total rating, so both outputs are false.
    • Run: 29 passed. All three match.

    • Mutants, each in its own process, with the tree restored and git status clean after each:

      Mutant Result Killed by
      M8, rating_requirement_in_effect 2016-01-01 → 2018-01-01 1 failed, 28 passed Case 27
      M9, combined_rating_threshold 0.8 from 2020-01-01 → 2021-01-01 1 failed, 28 passed Case 28
      M13, compensation default → person("is_veteran", period) 1 failed, 28 passed Case 29
  3. P3-2, docstring. wa_pte_wholly_service_connected_disability.py:9-11 now reads: admitted a veteran with "one hundred percent service-connected disability as provided in 42 U.S.C. Sec. 423 (d)(1)(A)". The word "total" is gone. ruff format switched those two lines to single-quoted strings.
  4. P3-3, bill designations. combined_rating_threshold.yaml:18 now reads (ESSB 5160) and :20 reads (EHB 1106). The titles already carried 2019 c 453 and 2025 c 200, so I added nothing. The section and page parts are unchanged.
  5. P3-4, register item. source_conflicts/candidates.yaml:10104 adds us-wa:pte-veteran-pathway-pre-2016, placed alphabetically between us-wa:ltss-spousal-maintenance-rounding and us-wi:…. The guard test_source_conflicts.py gives 103 passed, the same as at the old head: its checks loop over entries rather than adding a test per entry.
  6. P3-5, Indiana sentence. I checked it against the formula first.
    • main gives min(0.1·T, 300). The head gives min(0.1·max(T − C, 0), 300), where C is the Over 65 plus Blind/Disabled credits (in_supplemental_homestead_credit.py, in_homestead_property_tax_liability.py).
    • The difference runs from 0, when T − C ≥ 3,000 and both sides are capped, up to 0.1·C, when both are under the cap.
    • So I replaced the sentence in the body as given. I read the body back: the new sentence is at line 27, and the axiom: line is unchanged.
  7. Left for the hub. The impact: line (P2-1) and dismissing DTrim99's review. The axiom: line is untouched.

candidates.yaml diff

@@ -10101,6 +10101,47 @@ candidates:
   state: unreviewed
   note: 'PE-US follows the rounded standards as a disputable gap-fill; verify statutory rounding requirements and
     applicability of the undated precedent. Code not yet on main: medicaid_ltss_mmmna.'
+- id: us-wa:pte-veteran-pathway-pre-2016
+  program: property-tax-exemption
+  years:
+    from: 2008
+    to: 2015
+    type: program
+    note: property tax collection year (taxes levied for collection in that year)
+  question: Before collection year 2016, does RCW 84.36.381(3)(b)'s "one hundred percent service-connected disability
+    as provided in 42 U.S.C. Sec. 423(d)(1)(A)" mean a disability wholly connected to service under Social Security's
+    definition, or a 100% VA rating?
+  sources:
+  - kind: statute
+    citation: RCW 84.36.381(3)(b), as amended by 2008 c 6 s 706
+    url: https://lawfilesext.leg.wa.gov/biennium/2007-08/Pdf/Bills/Session%20Laws/House/3104-S2.SL.pdf#page=85
+  - kind: statute
+    citation: RCW 84.36.381(3)(a)(ii), as amended by 2015 3rd sp.s. c 30 s 2, for collection in 2016 and after
+    url: https://lawfilestestext.leg.wa.gov/LawTest/RCW%20Supplement/RCW%20%2084%20%20TITLE/RCW%20%2084%20.%2036%20%20CHAPTER/RCW%20%2084%20.%2036%20.381.htm
+  - kind: statute
+    citation: 42 U.S.C. 423(d)(1)(A) (Social Security Act 223(d)(1)(A))
+    url: https://www.ssa.gov/OP_Home/ssact/title02/0223.htm
+  readings:
+    statute: A disability that meets Social Security's definition in 42 U.S.C. 423(d)(1)(A) and is wholly connected
+      to service; the cross-reference names that definition, and the 2016 amendment's move to VA-rating language marks
+      the older text as something other than a rating.
+    other: A 100% VA service-connected disability rating.
+  example: In collection year 2015 a 45-year-old veteran with a disability meeting the Social Security definition,
+    wholly service-connected but with no VA rating, qualifies under the Social Security reading and not under the
+    VA-rating reading.
+  pe_us:
+    follows: statute
+    code:
+    - policyengine_us/variables/gov/states/wa/dor/property_tax_exemption/senior_disabled/wa_pte_wholly_service_connected_disability.py
+    - policyengine_us/variables/gov/states/wa/dor/property_tax_exemption/senior_disabled/wa_pte_categorical_eligible.py
+    prs:
+    - https://github.com/PolicyEngine/policyengine-us/pull/10330
+  state: ruled
+  note: PolicyEngine ruled on 2026-10-21 for the Social Security reading. The model has no service-connection input,
+    so wa_pte_wholly_service_connected_disability defaults to is_disabled (or is_fully_disabled_service_connected_veteran)
+    as an overridable proxy, read together with is_veteran in wa_pte_categorical_eligible; it over-includes a veteran
+    whose disability is not service-connected. WA DOR's pre-2016 practice has not been found; if it turns up, the
+    agency-reading rule applies.
 - id: us-wi:claim-of-right-itemized-deduction-credit
   program: income-tax
   years:

Judgment calls

  • Two more sources. The guard requires at least two sources, so besides the 2008 statute the entry cites the 2015 amendment and 42 U.S.C. 423(d)(1)(A), all kind: statute.
  • Year type. years.type: program, with the note "property tax collection year", since the schema has no collection-year type.
  • Program name. program: property-tax-exemption is a new program value. The guard doesn't restrict program.
  • Readings. readings.statute is the Social Security reading PolicyEngine follows; readings.other is the 100% VA-rating reading. The example field describes the case where the two readings split.
  • No marker. I added no # Source conflict marker in code, because CONTRIBUTING says the marker follows registration, and this is a candidate.
  • No decision number appears anywhere.

Tests at the head (merged tree), one file per process, all passing

  • Code health: test_source_conflicts.py: 103.
  • WA:
    • integration.yaml: 14;
    • wa_pte_categorical_eligible.yaml: 29;
    • wa_pte_combined_disposable_income.yaml: 9;
    • wa_pte_eligible.yaml: 9;
    • wa_pte_income_eligible.yaml: 11;
    • wa_pte_tier.yaml: 14;
    • wa_senior_disabled_property_tax_exemption.yaml: 18.
  • NYC:
    • income_tax_elimination/integration.yaml: 6;
    • nyc_school_tax_credit.yaml: 5;
    • nyc_school_tax_credit_rate_reduction_amount.yaml: 16;
    • test_nyc_school_tax_credit_income_test_invariants.py: 6;
    • the STC contrib files: 9 and 2;
    • spm_unit_local_tax.yaml: 4.
  • IN:
    • the two IN aggregate files: 2 and 2;
    • in_supplemental_homestead_credit.yaml: 14.
  • VA: va_529_plan_deduction.yaml: 18.
  • Lint: ruff format and ruff check are clean on the changed Python.
  • Not run: full suites and microsimulations. CI is the evidence for those.

CI

The Pull request run 41738184886 at a95f0d6fcb is still in progress at 00:19Z, 60 minutes after the push.

  • 34 jobs have passed, including Microsimulation and every Baseline shard. None failed or was cancelled.
  • Still running: Contrib (other-shard-1) and Contrib (other-shard-3).
  • The hub should check the run's final conclusion before landing.

What I couldn't check

  • The 2008 session law. The proxy refused it on lawfilesext.leg.wa.gov and on web.archive.org. The docstring quote comes from the review and the hub's ruling text, not from my own reading.
  • The SSA link. The proxy also refused www.ssa.gov, so the 42 U.S.C. 423 URL in the entry (ssa.gov/OP_Home/ssact/title02/0223.htm, SSA's page for Social Security Act § 223) is unfetched.
  • The bill designations. These follow the hub's 2026-11-03 reading of the enrolled covers.

Follow-up

None new. The review's age-threshold follow-up still stands.


Generated by Claude Code

PolicyEngine follows the administering agency's reading only where the
text and the canons leave a reading open. On 2026-10-21 it settled the
pre-2016 veteran pathway from the statute's words: the cross-reference
to 42 U.S.C. 423(d)(1)(A), and the move to VA-rating language from
collection year 2016. A WA DOR practice, if one turned up, would not
override that reading.

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

MaxGhenis commented Nov 9, 2026 •

Copy link
Copy Markdown
Contributor Author

Picking this up for the US hub. I'll post a hand-off comment here when it's ready.


Generated by Claude Code

GitHub reported a conflict with main (ba9bfde). Only
source_conflicts/candidates.yaml conflicted. Main rewrote the note on
us-ny:household-credit-claimable-spouse and added
us-ny:household-credit-separate-return-halving after it. This branch
added us-ny:nyc-school-tax-credit-fixed-amount-rounding at the same
spot. The resolution keeps main's note and both new items, in id order.

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

Copy link
Copy Markdown
Contributor Author

Ready for hub review
8fe868b

This head merges main at ba9bfdea75 into fa21c15833, the head the item named. No one had pushed to the branch since then (last commit 2026-11-04), so I built on it directly. The merge is the only new commit.

What the item asked for

  1. Merge main. 8fe868beb4 merges origin/main with a merge commit; I didn't rebase. Main stood 114 commits past the branch's previous merge base, 9f0242f6de.
    • One conflict: source_conflicts/candidates.yaml. Main (Follow state statutes where forms differ: NM medical exemption, VT CDCC, NY/NYC household credit, PR EITC #10300) rewrote the note of us-ny:household-credit-claimable-spouse and added us-ny:household-credit-separate-return-halving after it. This branch added us-ny:nyc-school-tax-credit-fixed-amount-rounding at the same place, under the old note.
    • Resolution. Main's note stays (candidates.yaml:7689-7693). Main's new item follows (:7694-7723), then this branch's item, unchanged (:7724-7749). That order keeps the file's id sort within us-ny.
    • Auto-merged: policyengine_us/tests/code_health/test_source_conflicts.py. Main's KNOWN_UNMARKED additions and removals stand. This branch's removal of the wa_pte_categorical_eligible.py line also stands; no wa_pte line remains.
    • No other file changed on both sides. Main's 1,279 changed files and this branch's 43 share only those two.
    • Nothing on the merged tree still names the five removed rate_reduction/amount/{single,joint,separate,head_of_household,surviving_spouse} scales. git grep finds no reference outside the changelog.
  2. Tests. test_source_conflicts.py passes 103 on the merged tree. The table below lists everything I ran.
  3. Cases that move. None. Every case in this branch's 18 test files passes with its existing expected value. Three main files that read this branch's variables also keep their values (see the table).
  4. Body. I added one line under "Test plan": the merged main, the merge commit and this head's counts. The axiom: line is unchanged; I read the body back to check.
  5. Hand-off. This comment.

Tests

One file per process on the merged tree, at most three at a time.

File Passed
test_source_conflicts.py 103
wa_pte_categorical_eligible.yaml 29
WA senior_disabled/integration.yaml 14
wa_pte_eligible.yaml 9
wa_senior_disabled_property_tax_exemption.yaml 18
in_supplemental_homestead_credit.yaml 14
in_supplemental_homestead_credit_in_aggregate.yaml 2
in_blind_disabled_property_tax_credit_in_aggregate.yaml 2
va_529_plan_deduction.yaml 18
VA plan_529/integration.yaml 3
nyc_school_tax_credit_rate_reduction_amount.yaml 16
nyc_school_tax_credit.yaml 5
income_tax_elimination/integration.yaml 6
spm_unit_local_tax.yaml 4
contrib/local/nyc/stc/adjust_income_limit_by_filing_status_and_eligibility_by_children.yaml 9
contrib/local/nyc/stc/adjust_income_limit_by_filing_status_joint_override.yaml 2
test_nyc_school_tax_credit_income_test_invariants.py 6
test_va_529_invariants.py 2
Main's state_property_tax_refunds.yaml (an Indiana case reads in_supplemental_homestead_credit) 13
Main's contrib/federal/abolish_federal_income_tax.yaml (has a New York case) 10
Main's test_household_spm_resource_parity.py (reads in_supplemental_homestead_credit) 67 of 68

Total: 21 files, 352 passed, 1 failed.

The one failure predates this PR. test_cash_income_never_lowers_spm_net_income drew a Connecticut case: a single person with $500,000 of earnings, plus $1 of partnership income.

  • spm_unit_net_income falls by $49.41. ct_income_tax_recapture rises from 2,950 to 3,000 when CT AGI moves from 500,000 to 500,001.
  • I ran the same two simulations on a clean main worktree at ba9bfdea75 and got the same −49.40625. This PR's four rules apply only in NYC, Indiana, Virginia and Washington.
  • The test isn't derandomized (max_examples=25), so CI can draw this case on any branch.

CI

The Pull request run at this head: https://github.com/PolicyEngine/policyengine-us/actions/runs/42636677127 (in progress). It had just started when I posted this.

Judgment calls

  • A wider test set. The item asked for the files a conflict touched and test_source_conflicts.py. To look for moved cases, I also ran every test file this branch changes and three main files that read its variables.
    • I chose those main files by searching main's changed tests for nyc_school_tax_credit, nyc_income_tax, in_supplemental_homestead, va_529, wa_pte, state_property_tax_credits and spm_unit_local_tax.
    • test_county_from_dataset.py also matched, but it needs the microsimulation dataset, so I didn't run it.
  • Memory. Running three at a time, the first NYC contrib file was killed for memory (exit 137, about 6.5 GB). I reran it alone: 9 passed. I also reran abolish_federal_income_tax.yaml alone rather than beside another reform file.
  • Dev extra. The parity test needs hypothesis, from the dev extra. I ran uv sync --extra dev first, as CI installs it.

Proposed follow-up

Connecticut's recapture step can make $1 of income lower SPM net income by $49.
At $500,000 of CT AGI, $1 more raises ct_income_tax_recapture by $50, which breaks test_cash_income_never_lowers_spm_net_income on main too. Check the step boundary against Connecticut's recapture law, or exempt statutory steps from that invariant.


Generated by Claude Code

@MaxGhenis

Copy link
Copy Markdown
Contributor Author

David, here's where each item of your review stands at 8fe868beb4:

  • S1: I changed the description's axiom: line to the forms you listed: encoded-correct for the NYC amount from TY2025, and queued for Encode the NYC school tax credit income tests (IT-201-I p. 20; N.Y. Tax Law § 606(ggg)(1), (2), (4-a), (4-b)): the rate-reduction module ignores the $500,000 income disqualifier (PE #9712 parity) TheAxiomFoundation/rulespec-us#1452 (NYC 2017-2024), Allow data tests to work without a policyengine_us package being installed #1510 (Virginia), Portland, Oregon Arts Income Tax #1862 (Indiana) and Formula for ca_is_qualifying_child_for_caleitc #1863 (Washington). All four issues remain open with the pe-parity label, and Portland, Oregon Arts Income Tax #1862 and Formula for ca_is_qualifying_child_for_caleitc #1863 name what blocks them. Allow data tests to work without a policyengine_us package being installed #1510, Portland, Oregon Arts Income Tax #1862 and Formula for ca_is_qualifying_child_for_caleitc #1863 now also list the cases added since 29778a23d8.
  • S2: in b403e928f1, the inputs you named gained defaults that a supplied value overrides. Indiana qualification defaults to real_estate_taxes > 0 (in_supplemental_homestead_credit_eligible.py), and the homestead liability to real_estate_taxes less the Over 65 and Blind/Disabled credits, floored at 0 (in_homestead_property_tax_liability.py). Washington disability retirement defaults to SSDI receipt (wa_pte_retired_due_to_disability.py), and VA compensation and a total rating to is_fully_disabled_service_connected_veteran (wa_pte_receives_va_disability_compensation.py, wa_pte_va_total_disability_rating.py). Indiana Cases 12-14 and Washington Cases 22-24 test the defaults, the Indiana aggregate files now derive the liabilities of 1,850 and 1,725, and Washington Case 29 (d46ac9be1d) pins the compensation default.
  • S3: I kept PolicyEngine's reading of the pre-2016 text and made its proxy visible and overridable. In b403e928f1, wa_pte_wholly_service_connected_disability.py defaults to is_disabled or is_fully_disabled_service_connected_veteran, and wa_pte_categorical_eligible.py reads it for veterans before collection year 2016. The model has no service-connection input for that reading, and a veteran on SSDI already qualifies through disability retirement. So the bare flag you proposed would open that pathway only to veterans the data marks is_fully_disabled_service_connected_veteran. The proxy reaches collection years 2008-2015 only, and admits fewer people than main, which qualified anyone with is_disabled. Case 25 covers the flag alone, and Case 26 sets the new input to false. In a95f0d6fcb, the docstring quotes the 2008 text, and source_conflicts/candidates.yaml records the reading as us-wa:pte-veteran-pathway-pre-2016.
  • G1: in b403e928f1, va_529_plan_deduction_capped_amount.py counts one account when only a carryforward remains, and va_529_plan_deduction_person.py bounds the under-70 deduction by contributions plus carryforward. Two new cases in va_529_plan_deduction.yaml cover them: the owner of a $3,000 carryforward-only account deducts $3,000, and an owner who supplies a $9,000 capped amount against $2,500 available deducts $2,500.
  • G2: in b403e928f1, the va_529_plan_deduction.yaml header and the description say the Form 760 instructions agree on the per-account cap but say nothing about a carryforward at 70, so PolicyEngine follows (7)(b) for it.
  • G3: in b403e928f1, in_homestead_property_tax_liability.py cites IC 6-1.1-20.6-3(3), as SEA 1 SECTION 73 amends it, for the order of the credits. I left out the pre-2026 gate: the gap predates this PR, and the umbrella list in state_property_tax_credits.yaml adds the credit only from 2026.
  • G4: (a) I didn't add a zero before 2017, since the old scales also extrapolated back. (b) In b403e928f1, the changelog fragment names the five removed rate_reduction/amount scales, and the description does too. (c) In b403e928f1, source_conflicts/candidates.yaml records the $62.50 and $63 amounts as us-ny:nyc-school-tax-credit-fixed-amount-rounding.
  • G5: the description records the follow-up, and I haven't filed it yet. Since the statute tests age on December 31 of the filing year, a claimant reaches 62 by the end of the collection year. So the follow-up starts with the period convention, because the 61 used from 2020 may need to change.
  • G6: the description's test plan now lists the runs at b403e928f1 and CI at this head, and a95f0d6fcb renames the bills in veteran/combined_rating_threshold.yaml to ESSB 5160 and EHB 1106.

An independent AI review of these changes agreed at 8fe868beb4.

🤖 Generated with Claude Code

@MaxGhenis
MaxGhenis dismissed DTrim99’s stale review November 10, 2026 08:16

The changes since this review answer each item, and the reply above maps each one to its commit and file or gives the reason for declining it.

@MaxGhenis
MaxGhenis merged commit 84b58a7 into main Nov 10, 2026
72 checks passed
@MaxGhenis

Copy link
Copy Markdown
Contributor Author

US + core hub merge audit, us#10330 at 8fe868beb49ff2b15db2e495fe8d79efe0b105e9 (squash): Follow the statute for the NYC school tax rate reduction, IN supplemental homestead, VA 529 and WA senior/disabled property tax exemption.

  • Reviews:
    • DTrim99 requested changes at 29778a23d8 on three points: the form of the axiom: line, Indiana and Washington inputs that defaulted to off, and pre-2016 Washington veterans who qualified on generic is_disabled. They also made six optional suggestions.
    • I fixed the axiom: line in the description. b403e928f1 gives the new inputs overridable defaults, bounds the Virginia deduction, counts carryforward-only accounts, and adds the Indiana citation and the NYC candidate. It also turns the pre-2016 proxy into an overridable input, and I kept is_disabled in its default for the reasons the reply gives.
    • An independent AI review approved b403e928f1. It asked for an impact section, tests that pin the 2016 and 2020 dates and the compensation default, an exact quote of the 2008 text, the bill names and a register entry. d46ac9be1d adds Washington Cases 27-29, and a95f0d6fcb quotes the 2008 text, fixes the bill names and registers us-wa:pte-veteran-pathway-pre-2016. fa21c15833 trims that entry's note, and I added the impact section to the description.
    • A second independent AI review, of the changes since b403e928f1, agreed at 8fe868beb4. It recomputed Cases 27-29 from the head's formula and parameter files, matched the quote and bill names to the Washington legislature's texts, and found that both merges of main left the PR's own lines unchanged.
    • I dismissed DTrim99's change request with a reply that maps each item to its commit and file, or gives the reason for declining it.
  • Gates checked live at merge:
    • gh pr checks exits 0;
    • the latest Pull request run at the head, 42773096205, passed and tested this head merged into main at 833c28bb47;
    • MERGEABLE;
    • not a draft;
    • no reviewer's latest review requests changes;
    • the semantic-overlap guard: since 833c28bb47, main has changed no area this PR touches;
    • the merge pins the head.
  • Impact (default dataset, 8fe868beb4 against main at ba9bfdea75): we estimate that the PR raises NYC's school tax credit rate reduction amount by $0.64m in 2026, for 874 sampled tax units. The PR lowers Indiana's supplemental homestead credit by $0.77m, for 21 sampled tax units. Household net income rises by $0.62m, and state income tax and household benefits don't change.
  • Next: a follow-up on Washington's age threshold, which first settles whether the model's period stands for the filing year or the collection year. Two optional notes from the last review stay open. One asks for a capture of the 2008 Washington text under source_conflicts/sources/ if the Axiom corpus lacks it. The other asks for a sentence in the pre-2016 docstring and register note on why the proxy doesn't use SSDI. The register entry's question also drops the space the 2008 text has in 423 (d)(1)(A).
  • Axiom: NYC from TY2025 encoded-correct; TheAxiomFoundation/rulespec-us#1452, #1510, #1862 and #1863 queued. Allow data tests to work without a policyengine_us package being installed #1510, Portland, Oregon Arts Income Tax #1862 and Formula for ca_is_qualifying_child_for_caleitc #1863 now have companion rows for the cases this PR added or changed since 29778a23d8.

🤖 Generated with Claude Code

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.

NYC school tax credit rate reduction should preserve rounded second-band base amounts

3 participants