Skip to content

Give Michigan's qualified disabled veteran exemption to every veteran with a service-connected disability - #10714

Open
MaxGhenis wants to merge 1 commit into
mainfrom
fix-mi-disabled-veteran-exemption
Open

MaxGhenis wants to merge 1 commit into
mainfrom
fix-mi-disabled-veteran-exemption

Conversation

@MaxGhenis

@MaxGhenis MaxGhenis commented Nov 8, 2026 •

Copy link
Copy Markdown
Contributor

What was wrong

PolicyEngine-US gave Michigan's qualified disabled veteran exemption only to veterans who set is_fully_disabled_service_connected_veteran. Michigan law gives it to every veteran with a service-connected disability, at any rating, so a veteran rated 30% or 70% got no exemption.

The law

  • MCL 206.30(3)(b) allows "$250.00 for each taxpayer and every dependent of the taxpayer who is a qualified disabled veteran", and defines:
    • (i) "Qualified disabled veteran" means a veteran with a service-connected disability.
    • (ii) "Service-connected disability" means a disability incurred or aggravated in the line of duty in the active military, naval, or air service as described in 38 USC 101(16).
    • (iii) "Veteran" means an individual who served in the active military, naval, marine, coast guard, or air service and who was discharged or released with an honorable or general discharge.
  • MCL 206.30(7) adjusts the subsection (3) exemptions by the ratio of the CPI to its 1998-1999 state fiscal year level, rounded to the nearest $100. The 2025 MI-1040 instructions (line 9c) give $500 and repeat the definition: a veteran "who received an honorable or general discharge and has a disability incurred or aggravated in the line of duty as described in 38 USC 101(16)". No rating threshold appears in either.
  • The home heating credit counts the same exemption: the MI-1040CR-7 instructions list "an additional exemption … for qualified disabled veterans" among the exemptions a claimant counts.

Change

  • New Person input has_service_connected_disability (variables/household/demographic/person/): a veteran with a disability incurred or aggravated in the line of duty (38 U.S.C. 101(16)), at any rating. Its formula defaults it to is_fully_disabled_service_connected_veteran, so every existing input that marks a fully disabled service-connected veteran keeps the exemption.
  • mi_exemptions and mi_exemptions_count read has_service_connected_disability in place of is_fully_disabled_service_connected_veteran.
  • disabled/amount/veteran.yaml cites the 2025 MI-1040 instructions and describes the qualified disabled veteran.
  • The head/spouse swap-invariance property test draws the new input as well.

This PR leaves the other readers of is_fully_disabled_service_connected_veteran as they are: Missouri's property tax credit, Illinois's CTA military service pass, Virginia's energy assistance, military_disabled_head and military_disabled_spouse.

Assumptions

PolicyEngine-US does not record discharge status, so the input stands for a veteran with an honorable or general discharge, as (3)(b)(iii) requires.

Invariants

For every input:

  • has_service_connected_disability is true whenever is_fully_disabled_service_connected_veteran is, unless a user sets it false;
  • each tax-unit member adds at most one veteran exemption, so mi_exemptions rises by at most $500 times the unit's size;
  • a unit with no member marked has_service_connected_disability gets the same mi_exemptions and mi_exemptions_count as before this change, since the formula then returns is_fully_disabled_service_connected_veteran;
  • swapping head and spouse leaves Michigan income tax unchanged (test_state_tax_head_spouse_swap_invariance.py, now drawing the new input).

Tests

  • has_service_connected_disability.yaml, 3 cases: the default from is_fully_disabled_service_connected_veteran, true and false, and a partial rating set directly.
  • mi_exemptions.yaml, 4 new 2026 cases: a partial rating ($5,900 + $500 = $6,400), a fully disabled veteran counted once ($6,400), a joint return with two veterans ($11,800 + $1,000 = $12,800), and a VA beneficiary without a service-connected disability ($5,900).
  • mi_exemptions_count.yaml, 1 new case: two people, one with a partial rating, count 3.

All 727 cases under tests/policy/baseline/gov/states/mi and the new file pass. tests/core/test_state_tax_head_spouse_swap_invariance.py and tests/code_health give 539 passed and 1 skipped locally. The two with_coverage=True cases of test_selective_test_batches.py fail the same way on the base commit, because the local environment lacks the coverage package; CI installs it.

Before and after

A single Michigan filer in 2026, a veteran with a 50% service-connected disability rating:

mi_exemptions
Before $5,900
After $6,400

At Michigan's 4.25% rate, the exemption lowers the filer's income tax by $21.25.

axiom: us-mi/statute/206.30/3 queued (TheAxiomFoundation/rulespec-us#2076)

🤖 Generated with Claude Code

… with a service-connected disability

MCL 206.30(3)(b) allows the exemption for each qualified disabled veteran,
defined as a veteran with a service-connected disability (38 U.S.C. 101(16)),
with no rating threshold. PolicyEngine-US read is_fully_disabled_service_connected_veteran,
so veterans with partial ratings got nothing. Add a has_service_connected_disability
input that defaults to the fully disabled flag, and read it in mi_exemptions and
mi_exemptions_count (the home heating credit's count).

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

Copy link
Copy Markdown
Contributor Author

David, can you check the Michigan exemption here? MCL 206.30(3)(b) and the 2025 MI-1040 instructions give a $500 exemption for each veteran on the return with a service-connected disability, at any rating. So the PR adds a person input, has_service_connected_disability, which defaults to is_fully_disabled_service_connected_veteran and lets a user flag a veteran with a partial rating. Would you model it another way? If you can look by Tuesday afternoon, we can merge it before the Veterans Day post (PolicyEngine/policyengine-app-v2#1523).

@codecov

codecov Bot commented Nov 9, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (aae7f98) to head (4a67d2b).
⚠️ Report is 93 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff            @@
##              main    #10714   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files            1         2    +1     
  Lines           37        36    -1     
=========================================
- Hits            37        36    -1     
Flag Coverage Δ
unittests 100.00% <100.00%> (ø)

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

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

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

@DTrim99

DTrim99 commented Nov 9, 2026

Copy link
Copy Markdown
Collaborator

PR Review: Give Michigan's qualified disabled veteran exemption to every veteran with a service-connected disability

Summary

The law supports the fix. MCL 206.30(3)(b) gives an additional exemption "for each taxpayer and every dependent of the taxpayer who is a qualified disabled veteran". It defines that as "a veteran with a service-connected disability": a disability "incurred or aggravated in the line of duty ... as described in 38 USC 101(16)", held by a veteran with an honorable or general discharge. Neither the statute nor the 2023-2025 MI-1040 or MI-1040CR-7 instructions set a VA rating threshold. Before this PR, the model gave the exemption only to is_fully_disabled_service_connected_veteran, which is wrong for a veteran rated below 100% in every year.

The model now counts the exemption per person, across the head, the spouse and each dependent. It uses the right amounts ($400 for 2023, $500 for 2024-2026) and feeds both readers: mi_taxable_income and the home heating credit's exemption count. No existing variable records a service-connected disability at any rating, so adding a person variable doesn't duplicate one. The tests pass locally (33/33 in the three YAML files), and 15 hand-checks match.

One issue remains. The new variable has a default formula, so PolicyEngine treats it as computed, not as an input, and the web calculator's household builder won't offer it. That leaves the fix out of reach for calculator users, including readers of the Veterans Day post that cites this PR. Making it a plain input fixes this.

Critical (must fix)

None.

Should address

  1. has_service_connected_disability is not an input variable, so the web calculator can't set it (policyengine_us/variables/household/demographic/person/has_service_connected_disability.py:19-20).
    • policyengine-core's Variable.is_input_variable() returns true only when a variable has no formula and no adds/subtracts (no_specified_formulas = len(self.formulas) == 0). The API sends that value as isInputVariable.
    • policyengine-app-v2's household builder builds its picker from getInputVariables(metadata). That function keeps only variables with v.isInputVariable && !v.hiddenInput (app/src/utils/VariableResolver.ts, used by HouseholdBuilderForm.tsx).
    • Because of the formula that defaults to is_fully_disabled_service_connected_veteran, the new variable will not appear there. Calculator users will still see only the 100%-rating input, which already gave the exemption, so the partial-rating fix is reachable only through the API or Python.
    • The PR body calls it a "new Person input" that "lets a user flag a veteran with a partial rating". The Veterans Day post (policyengine-app-v2#1523) waits on this PR and plans a calculator link.
    • Fix: drop the formula so the variable is a plain bool input (default False), and have Michigan count a person who is has_service_connected_disability | is_fully_disabled_service_connected_veteran. That can be an inline OR in mi_exemptions plus a person-level helper for mi_exemptions_count (which uses adds), such as mi_qualified_disabled_veteran.
    • This keeps the invariant that every existing fully-disabled input still gets the exemption, and it keeps "counted once". Move the default case in has_service_connected_disability.yaml to the Michigan tests: mi_exemptions case 2 already pins that a 100%-rated veteran is counted once.
    • The same consideration applies to the "defaults to X; set to override" inputs in Follow the statute for the NYC school tax rate reduction, IN supplemental homestead, VA 529 and WA senior/disabled property tax exemption #10330 (see Suggestions).

Suggestions

  1. Record the discharge assumption in the code, not just the PR body. (3)(b)(iii) requires "an honorable or general discharge". The new variable's documentation says only "a veteran", and a general-purpose variable would naturally be read with the 38 USC 101(2) meaning ("other than dishonorable"). That meaning admits some veterans Michigan excludes. Add one line to the mi_exemptions comment: the model has no discharge input, so the variable stands for a veteran with an honorable or general discharge.
  2. Name or label clarity. has_service_connected_disability doesn't say "veteran", but the label and Michigan's test require veteran status. An active-duty member with a line-of-duty disability is not yet a veteran. A name like is_service_connected_disabled_veteran would match is_fully_disabled_service_connected_veteran and prevent misuse.
    • Also note in the documentation that the variable doesn't set is_veteran. That variable derives from veterans_benefits > 0, so programs that read it won't see a veteran flagged only through this input.
  3. Coordinate with Follow the statute for the NYC school tax rate reduction, IN supplemental homestead, VA 529 and WA senior/disabled property tax exemption #10330. That open PR adds Washington-scoped VA inputs: wa_pte_va_service_connected_rating (a combined rating as a fraction) and wa_pte_receives_va_disability_compensation, which defaults to the 100% input. If both merge, a rated veteran in Washington would need two unrelated inputs.
    • A bool fits Michigan better than a rating. A 0%-rated service-connected condition is still "a disability incurred or aggravated in the line of duty", and a "rating > 0" test would miss it.
    • A later shared federal rating input could still feed both states.
  4. Clean up the veteran.yaml reference list while you're editing it (pre-existing problems, not introduced here):
    • The "Michigan 2025 Income Tax Withholding Guide (Form 446)" entry (#page=2) doesn't show the $500 veteran amount. The whole guide only says Michigan "has additional special exemptions that are claimed on a taxpayer's Michigan income tax return". Drop it; the new 2025 MI-1040 line 9c entry corroborates 2025.
    • "Michigan Taxpayer's Guide 2022 - page 12" and "2021 Michigan Taxpayer Assistance Manual - page 10" put page text in the title. CLAUDE.md wants the page only in the href.
  5. Description wording. "for each qualified disabled veteran in the household" would be more accurate as "on the return" (taxpayer, spouse and dependents), since a non-dependent household member doesn't count. The house template ("Michigan provides this amount ... under the ... exemption") could also be used.

Verified correct

  • Statute (MCL 206.30(3)(b), per the official text and rulespec-us#2076's verbatim quote).
    • "$250.00 for each taxpayer and every dependent of the taxpayer who is a qualified disabled veteran".
    • (i) "a veteran with a service-connected disability".
    • (ii) "a disability incurred or aggravated in the line of duty in the active military, naval, or air service as described in 38 USC 101(16)".
    • (iii) "an honorable or general discharge".
    • No rating threshold appears.
    • (3)(a) and (3)(b) are separate exemptions, so one person can take both. The model adds both (probe E).
    • The "taxpayer or dependent, but not both" rule can't double count here, because a PolicyEngine dependent sits only in the claimer's unit.
  • Instructions.
    • 2025 MI-1040 book p. 3 ("The exemption allowance for qualified disabled veterans is $500") and line 9c, p. 10. The same definition and "veteran of the active military, naval, marine, coast guard, or air service who received an honorable or general discharge" also appear in the 2023 book (line 9c, $400).
    • Schedule NR Worksheet 6 (2025 book, file p. 35) uses "Number of ... qualified disabled veteran exemption from MI-1040, line 9c ___ x $500", confirming a per-person count.
    • 2025 MI-1040CR-7 line 15c (file p. 7) uses the same definition and counts it as a home heating credit exemption ("An additional exemption is available for qualified disabled veterans").
  • Amounts.
    • 2023 $400 (2023 MI-1040 book p. 3), 2024 $500 (2024 instructions p. 3), 2025 $500 (2025 book pp. 3 and 10).
    • 2026 $500 follows from (7): $250 times the CPI ratio to SFY 1998-99, about 1.9-2.0, gives roughly $480-$495, which rounds to $500. A $600 amount would need a ratio of 2.2.
  • Per-person counting. add(tax_unit, period, ["has_service_connected_disability"]) counts the head, the spouse and every dependent, matching "each taxpayer and every dependent".
  • Readers.
    • mi_exemptions feeds mi_taxable_income.
    • mi_exemptions_count feeds mi_standard_home_heating_credit (Table A allowance) and mi_alternate_home_heating_credit_eligible (resource cap). Both changes are correct per CR-7 line 15c.
    • The other readers of is_fully_disabled_service_connected_veteran correctly stay on the 100% input: MO PTC exclusion and military_disabled_head/spouse, IL CTA pass, VA EAP.
  • Reuse. No variable at the merge base or on current main records a service-connected disability below 100%. is_permanently_disabled_veteran (NC medical retirement) and is_veteran (from veterans_benefits) are different concepts.
  • Mutation rule. Every changed term fails a test if reverted or deleted:
    • mi_exemptions case 1 pins the new reader (5,900 without it).
    • The count case pins mi_exemptions_count (2 without it).
    • has_service_connected_disability case 1 and mi_exemptions case 2 pin the default and the no-double-count behavior (6,900 if both were added).
  • Other checks.
  • Hand-checks (hand arithmetic, confirmed on the head tree):
    1. mi_exemptions case 1 (2026, partial rating): 5,900 + 1 x 500 = 6,400.
    2. Case 2 (100% rating via the default): 5,900 + 500 = 6,400, counted once.
    3. Case 3 (joint, both veterans): 11,800 + 2 x 500 = 12,800.
    4. Case 4 (VA beneficiary, no service-connected disability): 5,900.
    5. mi_exemptions_count case: 2 + 0 + 1 = 3.
    6. Single 2026 filer, $40,000 wages: (40,000 - 6,400) x 4.25% = $1,428.00 vs (40,000 - 5,900) x 4.25% = $1,449.25. The $21.25 drop matches the PR body.
    7. Same filer, 2023: exemptions 5,400 + 400 = 5,800; (40,000 - 5,800) x 4.05% = $1,385.10.
    8. Same filer, 2025: 5,800 + 500 = 6,300; 33,700 x 4.25% = $1,432.25.
    9. 2026 couple plus a qualifying adult dependent, with the head and the dependent both veterans: 3 x 5,900 + 2 x 500 = 18,700; count 3 + 2 = 5; 41,300 x 4.25% = $1,755.25.
    10. Blind veteran: 5,900 + 3,400 + 500 = 9,800; count 3.
    11. Claimable filer (MCL 206.30(4)) who is a veteran: 1,500 + 500 = 2,000. MI-1040 line 9 adds lines 9a-9e, and (4) bars only the subsection (2) exemption.
    12. Home heating credit, single 70-year-old: the count goes from 1 to 2, the standard allowance rises by (815 - 604) x 2026 uprating ≈ $215.78, and the credit goes from $115.09 to $244.55.
    13. A 100% veteran whose new input is set false: 5,900.
    14. A veteran in OH: all MI variables are 0.
    15. 2026 amount from (7): $500 (see Amounts above).

Axiom line assessment

axiom: us-mi/statute/206.30/3 queued (TheAxiomFoundation/rulespec-us#2076) is a valid queued line: it has a status word and links the issue.

rulespec-us#2076 is OPEN and labelled pe-parity, and it is dispatch-ready:

  • module us-mi/statutes/206.30/3.yaml;
  • corpus path with sha256;
  • verbatim (3)(b)(i)-(iii) and (7);
  • the encoding gap (the undefined ..._claimable_by_tax_unit relation);
  • required outputs, including the person-level qualified_disabled_veteran and the "not both" rule;
  • companion tests drawn from the statute and instructions (a 30% rating counts, a non-service-connected veteran doesn't, a 100% rating counts once, a dependent veteran counts, $500 for 2025 and 2026).

It matches the head's behavior. The line's legal id says statute, while the issue's module path says statutes; that difference is cosmetic.

If the "Should address" item changes the variable to a plain input, the issue body's description of has_service_connected_disability "(defaulting to is_fully_disabled_service_connected_veteran)" should be updated to match. The legal encoding asks are unaffected.

CI status

At the current head 4a67d2b, most checks pass, including:

  • PR validation (lock, guard tests, lint, changelog);
  • Quick Feedback (selective tests and coverage);
  • Package and compatibility;
  • Household API Partners;
  • Baseline household, irs, reform, rest-a, rest-b, contrib-hhs, ssa-usda and states-shards 2-4;
  • Python Rest shards 1-9;
  • codecov patch and project.

Pending at the last check: Microsimulation and Baseline states-shard-1. No failures so far.

Locally on the head tree, the three changed YAML files pass 33/33.

On the tree merged with main:

Branch status

The branch is 1 commit ahead and 46 behind main. git merge-tree --write-tree with main is clean.

Main has changed none of this PR's files since the merge base. #10398 (Michigan military survivor exemptions and mi_military_retirement_subtraction) is already in the merge base and touches different files, and the PR doesn't read military retirement income.

The PR shares no files with the sibling military retirement PRs (#10702 UT, #10713 MT, #10715 ID, #10716 DE, #10717 ID). #10330 (open, not in this batch) adds Washington-scoped VA rating inputs: a design overlap with no file conflict (Suggestion 3).

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.

Requesting changes per the review above: 0 critical, 1 should (has_service_connected_disability has a default formula, so it isn't an input variable and the web calculator can't set a partial rating; make it a plain bool input and OR it with is_fully_disabled_service_connected_veteran in Michigan), 5 suggestions. The law supports the fix and the amounts check out.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants