Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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/fix-state-head-spouse-swap.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Make state income taxes independent of which spouse is labelled head: dependents' income on Arkansas, Delaware, Iowa, Mississippi and Montana separate-column returns goes to the spouse with the greater income (an exact tie splits it); Mississippi splits a tax-unit health savings account or self-employed deduction with no per-person amounts equally between the spouses; Delaware chooses the dependent credit split and, on equal incomes, the EITC and dependent care credit columns together to minimise tax; Montana's joint itemized deductions count both spouses' mortgage and investment interest; and an incapacitated or disabled head now qualifies as the other spouse's qualifying individual for Minnesota's dependent care credit, Montana's dependent care deduction and Oregon's working family household and dependent care credit, a surviving-spouse claimant for Missouri's property tax credit, and a totally disabled spouse for Oklahoma's property tax credit.
37 changes: 37 additions & 0 deletions policyengine_us/model_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,43 @@ def allocate_joint_amount_to_minimize_combined_tax(
return best_head_allocation


def move_dependent_amounts_to_filer(person, period, amount):
"""Puts the tax unit dependents' `amount` on a filer's own column.

Some states' formulas count dependents' income on their filers' return,
although a dependent mostly reports it on their own return, which the
model does not compute. Where spouses file separately on one return, this
is a modelling convention for which column gets those amounts, not a state
rule: the spouse whose own `amount` is greater takes the dependents'
total, and an exact tie splits it equally, so the result does not depend
on which spouse is labelled head. It borrows the federal rule that a
child's income reported by parents who file separately goes on the return
of the parent with the greater taxable income (26 U.S.C. 1(g)(5)(B); IRS
Form 8814 instructions). Without a spouse the head takes it. Dependents
keep nothing of their own.

Assumes each tax unit has one head and at most one spouse, as the role
variables assign; with two spouses the total would not be conserved.
"""
tax_unit = person.tax_unit
is_dependent = person("is_tax_unit_dependent", period)
is_head = person("is_tax_unit_head", period)
is_spouse = person("is_tax_unit_spouse", period)
dependents_amount = tax_unit.sum(is_dependent * amount)
head_amount = tax_unit.sum(is_head * amount)
spouse_amount = tax_unit.sum(is_spouse * amount)
own = where(is_head, head_amount, spouse_amount)
other = where(is_head, spouse_amount, head_amount)
has_spouse = tax_unit.any(is_spouse)
share = where(
has_spouse,
where(own > other, 1, where(own == other, 0.5, 0)),
1,
)
filer_share = (is_head | is_spouse) * share
return where(is_dependent, 0, amount) + filer_share * dependents_amount


STATES = [
"AL",
"AK",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@
2. Bounds: no share is negative or above the tax unit's deduction, and a
dependent's is zero whatever their own amount.
3. Attribution: when the head's and spouse's own amounts sum to the
deduction, each gets their own amount; when they have none, the head gets
all of it.
deduction, each gets their own amount; when they have none, the head and
spouse split it equally, whichever is labelled head.
4. The deduction lowers the members' total Mississippi AGI by at least zero
and at most the deduction itself. (On main it could lower it by more.)
"""
Expand Down Expand Up @@ -215,6 +215,9 @@ def _run(units, year, *, zero_deduction):
np.asarray(sim.calculate(name, year), dtype=float) for name in others
)
person["is_head"] = np.asarray(sim.calculate("is_tax_unit_head", year), dtype=bool)
person["is_spouse"] = np.asarray(
sim.calculate("is_tax_unit_spouse", year), dtype=bool
)
person["is_dependent"] = np.asarray(
sim.calculate("is_tax_unit_dependent", year), dtype=bool
)
Expand Down Expand Up @@ -252,7 +255,7 @@ def _check(units, year):
assert (shares[person["is_dependent"]] == 0).all()

# 3. Attribution: own amounts when they sum to the deduction; otherwise,
# without any, the head's.
# without any, an equal share for the head and the spouse.
filer = ~person["is_dependent"]
filers_own = _unit_sum(
person, person["health_savings_account_ald_person"] * filer, n
Expand All @@ -263,9 +266,13 @@ def _check(units, year):
person["health_savings_account_ald_person"][matches],
atol=TOLERANCE,
)
no_own_head = (filers_own == 0)[unit] & person["is_head"]
head_or_spouse = person["is_head"] | person["is_spouse"]
n_spouses = _unit_sum(person, head_or_spouse.astype(float), n)
no_own = (filers_own == 0)[unit] & head_or_spouse
np.testing.assert_allclose(
shares[no_own_head], deduction[unit][no_own_head], atol=TOLERANCE
shares[no_own],
(deduction / np.maximum(n_spouses, 1))[unit][no_own],
atol=TOLERANCE,
)

# 4. The deduction lowers total Mississippi AGI by no more than itself.
Expand Down
Loading
Loading