Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
1f0cd1b
Make head_is_dependent_elsewhere consumers independent of the head label
MaxGhenis Oct 8, 2026
3ecc675
Keep tax units without a filer eligible where only claimed filers are…
MaxGhenis Oct 9, 2026
8da8312
Address review: Hawaii worksheet earnings, NM same-filer age, childle…
MaxGhenis Oct 9, 2026
e838623
Allow a cent of rounding in the Hawaii self-employed joint pair
MaxGhenis Oct 9, 2026
41fb4a5
Address review round 2: worksheet earnings, minors' own status, input…
MaxGhenis Oct 9, 2026
527d47f
Merge remote-tracking branch 'upstream/main' into head-dependent-else…
MaxGhenis Oct 9, 2026
436805e
Merge the Hawaii deductions references after syncing main
MaxGhenis Oct 9, 2026
23c6229
Apply the federal rules for a filer who can be claimed as a dependent
MaxGhenis Oct 9, 2026
f3467ae
Apply IA, KS, LA and MD rules for a filer who can be claimed as a dep…
MaxGhenis Oct 10, 2026
704a604
Limit Maryland's aged dependent exemption by federal AGI
MaxGhenis Oct 10, 2026
4597c55
Address the federal claimable-filer review
MaxGhenis Oct 10, 2026
e4b8f36
Separate dependency facts and fix Hawaii qualified-person counts
MaxGhenis Oct 10, 2026
c7f969f
Clarify claimability outside the current tax unit
MaxGhenis Oct 10, 2026
4c8ed81
Merge commit 'c7f969f872' into claimable-filer-federal
MaxGhenis Oct 10, 2026
3f88702
Read the split dependency inputs from #10024
MaxGhenis Oct 10, 2026
b2771d2
Follow the corrected exemption count in three Hawaii credits
MaxGhenis Oct 10, 2026
163fcd4
Address the second federal claimable-filer review
MaxGhenis Oct 10, 2026
3d0864f
Address the third federal claimable-filer review
MaxGhenis Oct 10, 2026
35a2cbc
Give the spouse without an SSN a card type in the recovery rebate test
MaxGhenis Oct 10, 2026
795cb43
Merge remote-tracking branch 'upstream/claimable-filer-federal' into …
MaxGhenis Oct 11, 2026
61275e9
Read the split dependency inputs in the IA, KS, LA and MD rules
MaxGhenis Oct 11, 2026
5459eee
Test the split inputs, the filing exception and the unchanged case
MaxGhenis Oct 11, 2026
18240a3
Merge main after federal claimable-filer squash merge (#10045)
MaxGhenis Oct 16, 2026
228b34a
Merge main to finish claimable-filer verification
MaxGhenis Oct 16, 2026
0af443e
Merge remote-tracking branch 'gh/main' into claimable-filer-ia-ks-ky-…
MaxGhenis Oct 16, 2026
d151671
Clarify Iowa claimed-filer exemption descriptions (#10045)
MaxGhenis Oct 16, 2026
594045e
Test Maryland exemption independence from federal repeal (#10045)
MaxGhenis Oct 16, 2026
5e4bd89
Merge main into claimable filer state fixes
MaxGhenis Oct 18, 2026
e72137e
Merge remote-tracking branch 'gh/main' into claimable-filer-ia-ks-ky-…
MaxGhenis Oct 18, 2026
f9f707b
Use taxable wages for Maryland poverty credit earned income
MaxGhenis Oct 19, 2026
2608e69
Respect actual claims in Kansas consolidated exemption reforms
MaxGhenis Oct 19, 2026
e0eaf47
Complete Kansas joint-filer credit coverage and Iowa citation
MaxGhenis Oct 19, 2026
bfab65a
Merge main into claimable-filer-ia-ks-ky-la-md
claude Nov 3, 2026
61d3134
Test Iowa's claimed-dependent bar on the claimant's income
claude Nov 3, 2026
c14ca63
Count Kansas exemptions and food sales credit on federal law
claude Nov 3, 2026
d0c2885
Exclude Maryland poverty credit filers only on an actual Maryland claim
claude Nov 3, 2026
abdd8e7
Describe the statute-first readings in the changelog fragment
claude Nov 3, 2026
6d5a3cb
Align Kansas witnesses with the federal exemption count
claude Nov 4, 2026
41276e3
Merge main into claimable-filer-ia-ks-ky-la-md
claude Nov 4, 2026
c9e4bf3
Give a Maryland filer with no allowed exemption no poverty level
claude Nov 4, 2026
0a28338
Pin Maryland's poverty level for joint returns with a claimable spouse
claude Nov 5, 2026
13d4d12
Merge main into claimable-filer-ia-ks-ky-la-md
claude Nov 9, 2026
adea558
Give every Maryland filer their own exemption under Tax-General 10-21…
claude Nov 9, 2026
a6b85c2
Pin the Maryland CTC gate before 2025 and the filing exception's rest…
claude Nov 9, 2026
9c98e11
Move Maryland dependent care subtraction cases into the existing test…
claude Nov 9, 2026
983bad7
Record the source conflicts this PR decides
claude Nov 9, 2026
994d9fa
State what Maryland's pro forma EITC waives under section 10-704(c)(3…
claude Nov 9, 2026
b767c36
Record the hub's rulings on two dependent-filer candidates
MaxGhenis Nov 9, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/claimable-filer-ia-ks-ky-la-md.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Apply Iowa, Kansas, Louisiana and Maryland rules for a filer who can be claimed as a dependent: no dependent exemptions or credits on a return with a claimable filer outside the claimant filing exception (Iowa dependent credit, Kansas dependent exemption and food sales tax credit child, Louisiana dependent exemption, Maryland dependent and aged-dependent exemptions and child tax credit). Iowa withholds the low-income exemption, the single-filer tax reduction and the alternate tax from a claimed dependent only when the claiming person's net income exceeds the Iowa Code 422.5 limit, using new claimant income and filing status inputs. Kansas counts each filer whom no one else may claim, plus allowable dependents, for its 2021-2023 exemptions, and each such filer plus dependent children under 18 for its food sales tax credit. Kansas also gives a 2021-2023 head of household one filer exemption rather than two, since K.S.A. 79-32,121 limited the extra allowance to tax year 1998. Maryland keeps every filer's own exemption under Tax-General 10-211(a)(1), including a filer who can be claimed. Its poverty line credit excludes only a filer actually claimed as an exemption on another Maryland return, through a new input that defaults to the federal claim, and keeps ordinary earned income; a Maryland filer who can be claimed gets no childless EITC. Also limit Maryland's aged dependent exemption by federal AGI under Tax-General 10-211(c), as for the personal exemption.
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
description: Kansas provides a food sales tax credit of this amount per qualifying exemption.
description: Kansas provides a food sales tax credit of this amount per exemption claimed on the federal return.
values:
2015-01-01: 125
2025-01-01: 0
Expand All @@ -16,5 +16,7 @@ metadata:
href: https://www.ksrevenue.gov/pdf/ip24.pdf#page=5
- title: HB 2106 (2022) - Food sales tax phase-out
href: https://kslegislature.gov/li_2022/b2021_22/measures/hb2106/
- title: 2022 HB 2106, section 4, amending K.S.A. 79-32,271
href: https://kslegislature.gov/li_2022/b2021_22/measures/documents/hb2106_enrolled.pdf#page=2
- title: Kansas 2022 Statute Article 32 79-32,271(d)
href: https://kslegislature.gov/b2023_24/laws/079_000_0000_chapter/079_032_0000_article/079_032_0271_section/079_032_0271_k/
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
description: Kansas provides this exemption for each person in a filing unit, pre 2024.
description: Kansas provides this exemption for each federal exemption to which a filer is entitled, before 2024.
values:
2021-01-01: 2_250

Expand All @@ -22,3 +22,7 @@ metadata:
# Legal code represents post 2024 structure
- title: Kan. Stat. § 79-32,121 (a)
href: https://ksrevisor.gov/statutes/chapters/ch79/079_032_0121.html
- title: K.S.A. 79-32,121(a), 2023 statutory edition
href: https://kslegislature.gov/li_2024s/b2023_24/statute/079_000_0000_chapter/079_032_0000_article/079_032_0121_section/079_032_0121_k/
- title: 2022 HB 2239, section 40, reenacting K.S.A. 79-32,121(a)
href: https://www.kslegislature.gov/li_2022/b2021_22/measures/documents/hb2239_enrolled.pdf#page=38
Original file line number Diff line number Diff line change
Expand Up @@ -367,11 +367,9 @@ def formula(tax_unit, period, parameters):
head_of_household * p.by_filing_status.hoh_additional_amount
)
return base_amount + veterans_exemption_amount + hoh_additional
filing_status = tax_unit("filing_status", period)
statuses = filing_status.possible_values
joint = filing_status == statuses.JOINT
hoh = filing_status == statuses.HEAD_OF_HOUSEHOLD
adults = where(joint | hoh, 2, 1)
# K.S.A. 79-32,121(a) with IRC 151(d)(2): each filer whom no one
# else may claim keeps an exemption, as in ks_count_exemptions.
adults = tax_unit("head_spouse_count_not_dependent_elsewhere", period)
return adults * p.consolidated.amount + veterans_exemption_amount

class ma_income_tax_exemption_threshold(Variable):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,12 @@ def formula(tax_unit, period, parameters):
# dependent is age-eligible, matching the baseline count.
person = tax_unit.members
age = person("age", period)
is_dependent = person("is_tax_unit_dependent", period)
# A return on which the filer (or, if joint, either spouse) can be
# claimed as a dependent has no dependents (IRC 152(b)(1)), unless
# the would-be claimant meets the filing exception (Publication 501).
is_dependent = person("is_tax_unit_dependent", period) & ~person.tax_unit(
"head_or_spouse_is_dependent_elsewhere_without_filing_exception", period
)
if c.age_limit.in_effect:
eligible = is_dependent & (age < c.age_limit.threshold)
else:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ def formula(tax_unit, period, parameters):

person = tax_unit.members
age = person("age", period)
# A return on which the filer (or, if joint, either spouse) can be
# claimed as a dependent has no dependents (IRC 152(b)(1)), unless
# the would-be claimant meets the filing exception (Publication 501).
no_dependents = tax_unit(
"head_or_spouse_is_dependent_elsewhere_without_filing_exception", period
)
is_dependent = person("is_tax_unit_dependent", period)

# Apply age limit if in effect.
Expand All @@ -24,7 +30,7 @@ def formula(tax_unit, period, parameters):
else:
eligible_dependents = is_dependent

return tax_unit.sum(eligible_dependents)
return where(no_dependents, 0, tax_unit.sum(eligible_dependents))

class ks_dependent_exemption(Variable):
value_type = float
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,12 @@ def formula(tax_unit, period, parameters):
p = parameters(period).gov.contrib.states.md.dependent_exemption
person = tax_unit.members
age = person("age", period)
is_dependent = person("is_tax_unit_dependent", period)
# A return on which the filer (or, if joint, either spouse) can be
# claimed as a dependent has no dependents (IRC 152(b)(1)), unless
# the would-be claimant meets the filing exception (Publication 501).
is_dependent = person("is_tax_unit_dependent", period) & ~person.tax_unit(
"head_or_spouse_is_dependent_elsewhere_without_filing_exception", period
)
if p.age_limit.in_effect:
eligible = is_dependent & (age < p.age_limit.threshold)
else:
Expand Down Expand Up @@ -97,7 +102,12 @@ class md_older_dependents_count(Variable):

def formula(tax_unit, period, parameters):
person = tax_unit.members
is_dependent = person("is_tax_unit_dependent", period)
# A return on which the filer (or, if joint, either spouse) can be
# claimed as a dependent has no dependents (IRC 152(b)(1)), unless
# the would-be claimant meets the filing exception (Publication 501).
is_dependent = person("is_tax_unit_dependent", period) & ~person.tax_unit(
"head_or_spouse_is_dependent_elsewhere_without_filing_exception", period
)
total_dependents = tax_unit.sum(is_dependent)
eligible = tax_unit("md_eligible_dependents_count", period)
return max_(0, total_dependents - eligible)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,248 @@
"""Iowa, Kansas, Kentucky, Louisiana and Maryland rules for a filer who can be
claimed as a dependent.

A return on which the filer (or, if joint, either spouse) can be claimed has no
dependents unless the would-be claimant meets the filing exception (IRC
152(b)(1), Publication 501), so these states' dependent exemptions and credits,
Kansas's food sales tax credit child route, Maryland's child tax credit and the
childless pro forma EITC follow that test. Kansas's 2021-2023 exemptions and
its food sales tax credit count each filer whom no one else may claim (IRC
151(d)(2)). Iowa's low-income exemption, tax reduction and alternate tax turn on
an actual claim and the claiming person's income, whose inputs default to no
disqualification; Maryland's poverty line credit turns on an actual claim on
another Maryland return, a separate input that defaults to the federal claim;
every Maryland filer keeps their own exemption (Tax-General 10-211(a)(1)).

For couples drawn by Hypothesis (claimable, claimed and filing-exception
patterns) and a seeded population in these states:

1. Swap invariance: each output is the same under either head/spouse
labelling.
2. Monotonicity: marking another filer as claimed never raises the amounts in
MONOTONE. This holds for the drawn inputs, which have no self-employment
losses: on separate Maryland returns a claimable spouse's loss no longer
offsets the other spouse's earnings, which is intended.
3. Identities: with a filer claimable outside the filing exception there is no
Louisiana dependent exemption, Maryland aged dependent exemption or child
tax credit, and no pro forma federal EITC without a child. Every filer
keeps a Maryland exemption, so Maryland's count covers both spouses and
the poverty line credit's poverty level is positive.
4. Differential: wherever the federal EITC's eligibility holds, Maryland's
pro forma federal EITC without the minimum age equals the federal credit
before take-up and filing.
5. Unchanged when nobody can be claimed: Maryland's exemption count is the tax
unit's size, the poverty line credit's poverty level is `tax_unit_fpg`, and
Louisiana's dependent exemption is $1,000 per dependent.
6. Filing exception scope: the exception never lowers the 152(b)(1) amounts in
EXCEPTION_RESTORES and does not change the claimability and actual-claim
rules in EXCEPTION_NEUTRAL.
"""

import numpy as np
import pytest
from hypothesis import HealthCheck, given, settings
from hypothesis import strategies as st

from policyengine_us import Simulation
from policyengine_us.tests.core.test_claimable_filer_federal_invariants import (
check_monotone,
check_swap,
)
from policyengine_us.tests.core.test_dependent_elsewhere_head_spouse_swap_invariance import (
CLAIM_PATTERNS,
TOLERANCE,
_calc,
_crafted_cases,
_exception_variants,
_seeded_couples,
_situation,
adults,
dependents,
)

STATES = ["IA", "KS", "KY", "LA", "MD"]
# 2022: Iowa's net-income path, Kansas's exemption count and Louisiana's
# exemptions; 2024: Iowa's consolidated path and Kansas's filing-status
# allowance; 2026: Louisiana's flat tax and Maryland's child tax credit
# phase-out.
YEARS = [2022, 2024, 2026]
MONOTONE = [
"ia_exemption_credit",
"ks_exemptions",
"ks_fstc",
"la_dependents_exemption",
"md_personal_exemptions_count",
"md_total_personal_exemptions",
"md_aged_dependent_exemption",
"federal_eitc_without_age_minimum",
"md_ctc",
]
OUTPUTS = MONOTONE + [
"ia_is_tax_exempt",
"ia_income_tax",
"ks_income_tax",
"ky_income_tax",
"la_income_tax",
"is_eligible_md_poverty_line_credit",
"md_poverty_line_credit",
"md_local_poverty_line_credit",
"md_income_tax",
"state_income_tax",
]
# 152(b)(1) amounts that the claimant filing exception restores.
EXCEPTION_RESTORES = [
"ia_exemption_credit",
"ks_exemptions",
"ks_fstc",
"la_dependents_exemption",
"md_personal_exemptions_count",
"md_aged_dependent_exemption",
"federal_eitc_without_age_minimum",
"md_ctc",
]
# Rules on claimability or an actual claim, which the exception leaves alone.
EXCEPTION_NEUTRAL = [
"ia_is_tax_exempt",
]


# Both spouses claimable outside the filing exception, with no dependents:
# the unit that had no Maryland exemption while the model denied a claimable
# filer's own. Every Hypothesis batch includes it, so identity 3 always sees
# it.
EVERY_FILER_CLAIMABLE = {
"state": "MD",
"adults": [
{"age": 20, "employment_income": 4_000.0},
{"age": 21, "employment_income": 4_000.0},
],
"dependents": [],
"claimed": (False, False),
"claimable": (True, True),
"exception": (False, False),
}


@st.composite
def couples(draw):
claimable = draw(st.sampled_from(CLAIM_PATTERNS))
claimed = tuple(can_claim and draw(st.booleans()) for can_claim in claimable)
return {
"state": draw(st.sampled_from(STATES)),
"adults": [draw(adults()), draw(adults())],
"dependents": draw(st.lists(dependents(), max_size=2)),
"claimed": claimed,
"claimable": claimable,
"exception": (draw(st.booleans()), draw(st.booleans())),
}


def _identities(sim, year):
claimable = _calc(sim, "head_or_spouse_is_dependent_elsewhere", year) > 0
no_dependents = (
_calc(
sim, "head_or_spouse_is_dependent_elsewhere_without_filing_exception", year
)
> 0
)
childless = _calc(sim, "eitc_child_count", year) == 0
for name in ["la_dependents_exemption", "md_aged_dependent_exemption", "md_ctc"]:
assert not _calc(sim, name, year)[no_dependents].any(), name
pro_forma = _calc(sim, "federal_eitc_without_age_minimum", year)
assert not pro_forma[no_dependents & childless].any()
# Each tax unit has its own household, in the same order.
state = np.asarray(sim.calculate("state_code_str", year))
md, la = state == "MD", state == "LA"
# Tax-General 10-211(a)(1): every filer keeps an exemption.
md_count = _calc(sim, "md_personal_exemptions_count", year)
assert (md_count[md] >= _calc(sim, "head_spouse_count", year)[md]).all()
assert (_calc(sim, "md_poverty_line_credit_income_level", year)[md] > 0).all()
# 5. Unchanged when nobody can be claimed.
nobody = ~claimable
np.testing.assert_array_equal(
_calc(sim, "md_personal_exemptions_count", year)[md & nobody],
_calc(sim, "tax_unit_size", year)[md & nobody],
)
np.testing.assert_allclose(
_calc(sim, "md_poverty_line_credit_income_level", year)[md & nobody],
_calc(sim, "tax_unit_fpg", year)[md & nobody],
atol=TOLERANCE,
)
np.testing.assert_allclose(
_calc(sim, "la_dependents_exemption", year)[la & nobody],
1_000 * _calc(sim, "tax_unit_dependents", year)[la & nobody],
atol=TOLERANCE,
)
# 4. Differential: the federal credit before take-up and filing.
federal_eligible = md & (_calc(sim, "eitc_eligible", year) > 0)
federal = np.minimum(
_calc(sim, "eitc_phased_in", year),
np.maximum(
0, _calc(sim, "eitc_maximum", year) - _calc(sim, "eitc_reduction", year)
),
)
np.testing.assert_allclose(
pro_forma[federal_eligible], federal[federal_eligible], atol=TOLERANCE
)


@settings(
max_examples=3,
deadline=None,
derandomize=True,
suppress_health_check=[HealthCheck.too_slow, HealthCheck.data_too_large],
)
@given(st.sampled_from(YEARS), st.lists(couples(), min_size=6, max_size=12))
def test_ia_ks_ky_la_md_claimable_filer_rules_are_label_free(year, units):
check_swap(units + [EVERY_FILER_CLAIMABLE], year, OUTPUTS, _identities)


@pytest.mark.parametrize("year", YEARS)
def test_ia_ks_ky_la_md_claimable_filer_rules_seeded_population(year):
units = _seeded_couples(STATES, per_state=3) + _crafted_cases(STATES)
# Guarantee an actual claim together with the claimant filing exception;
# the Kansas contrib YAML separately checks the consolidated-regime count.
units.append(
{
"state": "KS",
"adults": [
{"age": 20, "employment_income": 500},
{"age": 30, "employment_income": 500},
],
"dependents": [{"age": 5}],
"claimed": (True, False),
"exception": (True, False),
}
)
check_swap(units, year, OUTPUTS, _identities)
check_monotone(units, year, MONOTONE, _identities)


@pytest.mark.parametrize("year", [2022, 2025])
def test_ia_ks_ky_la_md_filing_exception_scope(year):
# Each unit runs four times with nobody actually claimed: claimable
# without the exception (both labellings), then with it.
units = [
unit
for unit in _seeded_couples(STATES, per_state=3) + _crafted_cases(STATES)
if any(unit["claimed"])
]
sim = Simulation(situation=_situation(units, year, _exception_variants))
for name in EXCEPTION_RESTORES + EXCEPTION_NEUTRAL:
copies = _calc(sim, name, year).reshape(-1, 4)
np.testing.assert_allclose(
copies[:, 0::2], copies[:, 1::2], atol=TOLERANCE, rtol=0
)
without, with_exception = copies[:, :2], copies[:, 2:]
if name in EXCEPTION_NEUTRAL:
np.testing.assert_allclose(
with_exception,
without,
atol=TOLERANCE,
rtol=0,
err_msg=f"The filing exception changes {name}",
)
else:
assert not (with_exception < without - TOLERANCE).any(), (
f"The filing exception lowers {name}"
)
Loading
Loading