Skip to content

Limit Kansas itemized deductions to the four K.S.A. 79-32,120(a) categories at each year's shares - #10095

Open
MaxGhenis wants to merge 3 commits into
mainfrom
ks-itemized-deductions-closed-list
Open

MaxGhenis wants to merge 3 commits into
mainfrom
ks-itemized-deductions-closed-list

Conversation

@MaxGhenis

@MaxGhenis MaxGhenis commented Oct 11, 2026 •

Copy link
Copy Markdown
Contributor

What changes

K.S.A. 79-32,120(a) defines the Kansas itemized deduction as four federal itemized categories, each at a share that has changed by year:

Tax years Charitable contributions (§170) Medical care (§213) Qualified residence interest (§163(h)) Real and personal property taxes (§164(a))
2015–2017 100% — 50% 50%
2018 100% 50% 50% 50%
2019 100% 75% 75% 75%
2020 on 100% 100% 100% 100%

Main computes itemized_deductions_less_salt + real_estate_taxes, which gives 100% of every federal itemized deduction other than SALT in every year, including investment interest (inside interest_deduction), casualty and theft losses, and pre-2018 miscellaneous deductions. This PR adds four dated share parameters under gov.states.ks.tax.income.deductions.itemized.rate and sums only those categories. Qualified residence interest is deductible_mortgage_interest_tax_unit, not interest_deduction. Personal property taxes have no separate input, so only real_estate_taxes enters, uncapped by the federal SALT limit as before.

It also applies (a)(1): before 2021, only a filer who itemizes on the federal return may take the Kansas itemized deduction; from 2021 any filer may take the larger of the two. Main let every filer take the larger in every year. The new federal_itemization_required parameter is true from 2015 to 2020.

The parameters start in 2015. The federal model does not run before 2015, so the 2013–2014 Kansas rules are not modeled.

Sources

Overlap

#10038 and #10070 each edit ks_itemized_deductions.py to subtract one more federal deduction (claim_of_right_deduction, wagering_losses_deduction) from main's sum. The closed list here never includes either, so whichever lands after this PR drops its Kansas formula edit and reference line. #10070's new Kansas cases still hold here. #10038's new case enters mortgage interest as interest_deduction: 9_000 and expects 12,000; under this PR it must use deductible_mortgage_interest_tax_unit instead.

This PR also deletes the file's KNOWN_UNMARKED line in test_source_conflicts.py, because it replaces the comment that quoted the 2021 instructions.

Tests

  • ks_itemized_deductions.yaml: 7 cases, 5 new (2017, 2018, 2019, 2021 and 2025), each with its arithmetic. 4 fail with main's formula.
  • ks_taxable_income.yaml: 6 cases, 3 new on the federal itemizing condition. 1 fails with main's formula.

Invariants

  • The Kansas itemized deduction = Σ share × category over the four categories, each share in [0, 1], so it never exceeds charity + medical + qualified residence interest + property taxes.
  • Investment interest, casualty and theft losses and miscellaneous deductions never change it.
  • Before 2021, a filer who does not itemize federally always gets the Kansas standard deduction; from 2021 the larger deduction applies.

impact: the hub will measure
axiom: (hub files pe-parity before landing)

https://claude.ai/code/session_01LwQq6Fmi4DmuA9GyXnRKdV


Generated by Claude Code

…gories

Kansas allows charitable contributions (100%), medical care, qualified
residence interest and real and personal property taxes (50% in 2015-2017
without medical, 50% in 2018, 75% in 2019, 100% from 2020). Main summed every
federal non-SALT itemized deduction, including investment interest, casualty
losses, pre-2018 miscellaneous deductions and the 2020-2021 non-itemizer
charitable deduction, at 100% in all years. Before 2021, (a)(1)(A) lets only
federal itemizers itemize in Kansas.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@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 review; the merge was clean.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LwQq6Fmi4DmuA9GyXnRKdV
This PR replaces the comment that quoted the 2021 K-40 instructions, so the
file no longer has a conflict-sounding comment and the ratchet in
test_source_conflicts.py requires its line to go.

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

Copy link
Copy Markdown
Contributor Author

Picking this up for the US hub, continuing an earlier session's work on this PR (that session pushed nothing). I'll post a hand-off comment here when it's ready.


Generated by Claude Code

@MaxGhenis

Copy link
Copy Markdown
Contributor Author

Ready for hub review

Head: 644db58a8544cb666b16c2591b732cacc95a5a35

This continues an earlier session that pushed nothing to this PR. The commit from 2026-10-11 is kept unchanged; this session added a clean merge of origin/main and one fix.

Blocker: primary sources not re-read

This session's network policy refused www.oregon.gov and web.archive.org, and I stopped trying further hosts after two refusals. So I could not fetch K.S.A. 79-32,120 for any year or the K-40 instructions, and I have not verified the share table, the 2015/2018/2019/2020/2021 effective dates or the PR's quotes. I removed the verbatim statute quotes from the PR body. Before landing, someone with access should confirm the table against each year's statute, and settle a citation question: the item that assigned this PR says 79-32,120(b), while the PR, its code comments, and #10038 and #10070 all cite (a). I could not check which subsection holds the list.

The brief also asks how the statute changed from 2013. I couldn't read it, and the parameters start in 2015. The federal model raises ParameterNotFoundError (a retirement-contribution limit) for 2013 and 2014, so no Kansas household runs in those years either way.

Items

  1. Still needed? Yes; main still returns itm_deds_less_salt + uncapped_property_taxes.
  2. Fix made: the merge with main brought in the source-conflict guard, which failed because this PR deletes the comment quoting the 2021 K-40 instructions that KNOWN_UNMARKED counted. I deleted that line (policyengine_us/tests/code_health/test_source_conflicts.py, formerly line 169), as the guard asks.
  3. Body corrected: it said Model the claim of right repayment deduction and credit (26 U.S.C. 1341) and North Carolina's rules that depend on it #10038's Kansas test still holds here. It doesn't: that case enters interest_deduction: 9_000 and expects ks_itemized_deductions: 12_000, and this formula ignores interest_deduction, so it computes 3,000. Whichever of Model the claim of right repayment deduction and credit (26 U.S.C. 1341) and North Carolina's rules that depend on it #10038 and this lands second moves that input to deductible_mortgage_interest_tax_unit. Both Model the claim of right repayment deduction and credit (26 U.S.C. 1341) and North Carolina's rules that depend on it #10038 and Count gambling winnings in federal gross income and fix the wagering losses deduction #10070 also conflict with this PR in ks_itemized_deductions.py's reference tuple (lines 10–14 here) and the return (lines 30–35 here). I did not touch either PR.
  4. Right? Read on this branch: ks_itemized_deductions.py:30-35 is the weighted sum of charitable_deduction, medical_expense_deduction, deductible_mortgage_interest_tax_unit (federal deductible mortgage interest after the acquisition-debt caps) and real_estate_taxes. ks_taxable_income.py:23-26 lets a unit itemize in Kansas only if it itemizes federally while federal_itemization_required is true.
  5. Reviews: none.
  6. Defect reproduced: with main's two formulas restored, 4 of 7 itemized cases and 1 of 6 taxable-income cases fail. Case 7 (2021 non-itemizer charity) passes on main too, so it guards nothing today; I kept it as a regression case.

Hand computations

File, case Computation Expected
itemized 3 (2025) 3,000 + 2,000 + 9,000 + 5,000 19,000
itemized 4 (2019) 1,000 + 0.75 × (2,000 + 8,000 + 4,000) 11,500
itemized 5 (2018) 1,000 + 0.5 × 14,000 8,000
itemized 6 (2017) 1,000 + 0.5 × (8,000 + 4,000) 7,000
itemized 7 (2021) 2,000 + 4,000 6,000
taxable 4 (2019, no federal itemizing) 50,000 − 3,000 − 2,250 44,750
taxable 5 (2019, federal itemizer) 50,000 − 9,000 − 2,250 38,750
taxable 6 (2021, no federal itemizing) 50,000 − 9,000 − 2,250 38,750

Tests run (merged head)

  • ks_itemized_deductions.yaml 7 passed; ks_taxable_income.yaml 6 passed; ks_integration.yaml 2 passed; ks_2025_integration.yaml 13 passed; gov/irs/salt_refund_integration.yaml 38 passed.
  • test_source_conflicts.py: 103 passed after the fix (1 failed before).
  • ruff format --check, ruff check on both variables: clean.

Mutants

  • Medical share 1 from 2015: 1 itemized failure, case 6 (killed).
  • federal_itemization_required false from 2015: 1 taxable-income failure, case 4 (killed).

CI

Draft, so Pull request CI skips it; it has not run at this head.

Judgment calls

  • No Hypothesis test. The formula is a weighted sum of four inputs, and the YAML cases pin each year's shares and the itemizing gate; CLAUDE.md asks for YAML where it can exercise the behavior. The invariants are in the body.
  • may_itemize uses tax_unit_itemizes, PolicyEngine's federal itemizing choice, for "federal taxable income is determined by itemizing", as other states' formulas do.

Proposed follow-up

  • Kansas itemized deduction for 2013–2014. Once the federal model runs before 2015, add the 2013–2014 shares from K.S.A. 79-32,120 as amended in 2013, with YAML cases from those years' K-40 instructions.

Generated by Claude Code

@MaxGhenis
MaxGhenis marked this pull request as ready for review November 3, 2026 21:40
@codecov

codecov Bot commented Nov 3, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (b540912) to head (644db58).
⚠️ Report is 94 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff            @@
##              main    #10095   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files            1         2    +1     
  Lines           54        35   -19     
  Branches         0         1    +1     
=========================================
- Hits            54        35   -19     
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.

@MaxGhenis

MaxGhenis commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor Author

Hub review in progress

This interim note is superseded by the final review: #10095 (comment)


Generated by Claude Code

@MaxGhenis

Copy link
Copy Markdown
Contributor Author

Ready for hub review
VERDICT: REQUEST_CHANGES
644db58
METHODOLOGY_CHOICE: no

The PR's head has not moved past 644db58a85. The share table, the federal itemizing gate and the property-tax handling match K.S.A. 79-32,120(a) as the hub read it. One input is wrong: the formula reads qualified residence interest from a variable that leaves out mortgage insurance premiums and ignores a direct deductible_mortgage_interest input. Main counts both, so the PR makes Kansas worse for those filers. A one-line change fixes both. I tested the fix, and every existing case and integration file still passes with it.

Findings

P1. Qualified residence interest leaves out mortgage insurance premiums

policyengine_us/variables/gov/states/ks/tax/income/deductions/ks_itemized_deductions.py:26-28 reads only deductible_mortgage_interest_tax_unit, which is interest and points after the acquisition-debt caps. 26 U.S.C. 163(h)(3)(E) treats qualified mortgage insurance premiums as qualified residence interest, and K.S.A. 79-32,120(a) counts "qualified residence interest as provided in section 163(h)". The 2019 Schedule S line C4, as the hub read it, adds "any mortgage insurance premiums you paid and reported on federal Schedule A". On main, interest_deduction adds deductible_mortgage_insurance_premiums, so main counts the premiums (at 100%). The premiums are federally deductible in 2007–2021 and again from 2026 (gov.irs.deductions.itemized.interest.mortgage_insurance_premiums.in_effect). So the PR drops them from the Kansas deduction in 2015–2021 and from 2026 on. Main has no such gap, so this PR introduces it.

The PR body also states this wrongly: "Qualified residence interest is deductible_mortgage_interest_tax_unit, not interest_deduction."

P2. A direct deductible_mortgage_interest input no longer reaches Kansas

deductible_mortgage_interest is a person-level variable that callers and tests set directly (for example tests/core/test_state_tax_head_spouse_swap_invariance.py, and nc_mortgage_and_property_tax_before_limitation.py reads it). When it is an input, deductible_mortgage_interest_tax_unit still computes from home_mortgage_interest and gives 0. Main reads it through interest_deduction.

Witness for P1 and P2. I ran a single filer in Kansas through Simulation on main (9f0242f6), on this PR, and on this PR with the fix below:

Case Statute Main PR PR + fix
2019, $8,000 interest + $1,000 premiums, wages $40,000 0.75 × 9,000 = 6,750 9,000 6,000 6,750
2021, $1,000 premiums 1,000 1,000 0 1,000
2026, $8,000 interest + $1,000 premiums 9,000 9,000 8,000 9,000
2025, deductible_mortgage_interest: 9_000 input 9,000 9,000 0 9,000

(The values are ks_itemized_deductions. Main gives 9,000 in 2019 because it applies 100%, which is the defect this PR fixes.)

Fix: replace lines 26–28 with:

        # 26 U.S.C. 163(h)(3)(E) treats deductible mortgage insurance premiums
        # as qualified residence interest.
        qualified_residence_interest = add(
            tax_unit,
            period,
            ["deductible_mortgage_interest", "deductible_mortgage_insurance_premiums"],
        )

nc_mortgage_and_property_tax_before_limitation.py reads the same two variables. With this change on a scratch copy of the head:

  • ks_itemized_deductions.yaml: 7 passed.
  • ks_taxable_income.yaml: 6 passed.
  • ks_2025_integration.yaml: 13 passed.
  • ks_integration.yaml: 2 passed.
  • The four probe cases above: 4 passed.

Cases 1 and 2 still pass with their deductible_mortgage_interest_tax_unit: 9 input, because the person-level variable derives from it.

Add these cases to ks_itemized_deductions.yaml (I hand-computed each from (a)(4) and (a)(5) and 163(h)(3)(E); the 2019 case's wages keep AGI under the $100,000 premium phase-out start):

# K.S.A. 79-32,120(a)(4) counts qualified residence interest under section
# 163(h), and 163(h)(3)(E) treats mortgage insurance premiums as such interest
# (2019 Schedule S, line C4).
- name: Case 8, 2019 mortgage insurance premiums count at the 75% share.
  period: 2019
  input:
    state_code: KS
    employment_income: 40_000
    home_mortgage_interest: 8_000
    mortgage_insurance_premiums: 1_000
  output:
    # 0.75 * (8,000 + 1,000)
    ks_itemized_deductions: 6_750

- name: Case 9, 2025 a direct deductible mortgage interest input counts.
  period: 2025
  input:
    state_code: KS
    deductible_mortgage_interest: 9_000
  output:
    ks_itemized_deductions: 9_000

Then change the body sentence to: "Qualified residence interest is federal deductible mortgage interest and points (deductible_mortgage_interest) plus deductible mortgage insurance premiums, which 26 U.S.C. 163(h)(3)(E) treats as qualified residence interest. Investment interest, the rest of interest_deduction, does not count."

P3. The parameter reference title says "lines C3"

policyengine_us/parameters/gov/states/ks/tax/income/deductions/itemized/rate/qualified_residence_interest.yaml:14: 2017 Form K-40 instructions, Schedule S Part C, lines C3 should read line C3. I could not open the 2017 booklet to confirm the line numbers in this file or the other rate files.

P3. The body leaves out (b) and has no quotes

The adopting session removed the statute quotes. From the hub's reading, the body can carry these, each under 25 words:

  • (a)(1)(A): "For all tax years prior to tax year 2021, if federal taxable income of an individual is determined by itemizing deductions ..."
  • (a)(1)(B): "regardless of whether or not such individual's federal taxable income is determined by itemizing deductions"
  • (a)(2): "(B) 50% of the amount of qualified residence interest as provided in section 163(h)"
  • (a)(2): "(C) 50% of the amount of taxes on real and personal property as provided in section 164(a)"
  • 2019 Schedule S line C2: "Real estate taxes from line 5b of federal Schedule A"

Add this sentence: "K.S.A. 79-32,120(b) reduces the total by income taxes deducted federally and by depreciation claimed for certain credits. None of the four categories includes income taxes, and the model has no input for that depreciation, so (b) changes nothing here."

The hand-off's "Draft, so Pull request CI skips it" is out of date. The PR is not a draft, and the Pull request run at this head concluded success, with every job passing (none were cancelled or skipped).

Overlap with #10038 and #10070

Both PRs conflict with this one at the same places in ks_itemized_deductions.py: the reference tuple (this PR's lines 10–14) and the return (this PR's lines 30–35). I confirmed this from their diffs. If this PR lands first, the second PR to land must make these changes:

The open-PR search for ks_itemized_deductions, ks_taxable_income and "kansas itemized" found no other overlap.

Mutants

I ran each mutant in its own process on a scratch worktree, then restored it and confirmed git status was clean.

Mutant Killed by
Medical share 1 from 2015 (hand-off) itemized Case 6, 2017
federal_itemization_required false from 2015 (hand-off) taxable Case 4, 2019 non-itemizer
2018 medical share 0.75 itemized Case 5, 2018
Federal itemizing gate still applied in 2021 (false moved to 2022) taxable Case 6, 2021, and taxable unit test 2
Mine: qualified residence interest steps to 75% in 2018 itemized Case 5, 2018

None survived. P1 survives the existing suite, since no case enters mortgage insurance premiums. Case 8 above kills it.

Checked and sound

  • Defect reproduced. I restored main's two formulas over the head and ran the PR's tests. 4 of 7 itemized cases failed (Cases 3, 4, 5, 6) and 1 of 6 taxable cases failed (Case 4), matching the body. Itemized Case 7 passes on main too.

  • Share table. Against the hub's reading of (a)(2)–(a)(5), year by year:

    • charity is 100% from 2015;
    • medical is 0 in 2015–2017, 50% in 2018, 75% in 2019 and 100% from 2020;
    • qualified residence interest and property taxes are 50% in 2015–2018, 75% in 2019 and 100% from 2020.

    Every parameter date and value matches.

  • Hand values. I computed each before reading the tests:

    • 2017: 1,000 + 0.5 × (8,000 + 4,000) = 7,000;
    • 2018: 1,000 + 0.5 × 14,000 = 8,000;
    • 2019: 1,000 + 0.75 × 14,000 = 11,500;
    • 2025: 3,000 + 2,000 + 9,000 + 5,000 = 19,000.

    The tests carry the same values. In full-household runs, a single filer with $2,000 charity, $14,000 mortgage interest and $9,000 real estate taxes gets 13,500 in 2018 (2,000 + 0.5 × 23,000) and 19,250 in 2019 (2,000 + 0.75 × 23,000). Single and joint filers both run, with no cycle through tax_unit_itemizes in 2018–2021.

  • The itemizing gate. ks_taxable_income.py:23-26 applies tax_unit_itemizes only while federal_itemization_required is true, which is 2015–2020. That matches (a)(1)(A) and (B). The filer takes the larger deduction only when allowed. tax_unit_itemizes compares federal income_tax only, as 35 other state formulas already rely on.

  • The SALT cap. "As provided in section 164(a)" names the deductible taxes. The cap sits in 164(b)(6), and line C2 takes line 5b, before line 5e's cap. The formula adds real_estate_taxes uncapped. Sound.

  • (b)'s income tax reduction. None of the four inputs includes income taxes, so the reduction cannot reach the total. I agree.

  • Personal property taxes. Main has no input for them. personal_property is a property value (quantity_type = STOCK), not a tax. The body already states the limit. Follow-up: add a personal property tax input and count it in Kansas (a) and line C3.

  • Medical. medical_expense_deduction is net of the federal AGI floor, which matches section 213 and federal Schedule A line 4.

  • Charity. charitable_deduction is the section 170 itemized amount, after ceilings and the 2026 floor. It does not include the 2021 non-itemizer deduction (Case 7).

  • 2013–2014. A 2014 Kansas household raises ParameterNotFoundError on gov.irs.gross_income.retirement_contributions.limit[401k], so the body's "does not run before 2015" holds.

  • Tests run at the head. ks_itemized_deductions.yaml 7 passed; ks_taxable_income.yaml 6 passed; test_source_conflicts.py 103 passed. The KNOWN_UNMARKED deletion is right, since the counted comment is gone.

  • Readers. ks_taxable_income → ks_income_tax_before_credits. Both variables are also listed in the state_itemized_deductions and state_taxable_incomes aggregates, which only report them. No other reader changes.

  • Scope. The PR touches nothing under .github/ or tests/policy/baseline/partners/. The changelog fragment is a top-level .fixed.md. The impact: and axiom: lines are present.

  • Baseline. The diff moves outputs at baseline in every year:

    • investment interest, casualty and theft losses and miscellaneous deductions leave the Kansas total;
    • 2018–2019 shares fall below 100%;
    • before 2021, federal non-itemizers lose the Kansas itemized deduction;
    • with the PR as it stands, mortgage insurance premiums drop from 2026.

    The hub's population check should compare ks_income_tax on the data years, split by whether the unit has deductible_mortgage_insurance_premiums or investment interest.

What I couldn't check


Generated by Claude Code

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