Skip to content
Draft
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/claimable-filer-az-ar-ca.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Apply Arizona, Arkansas and California 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, unless the would-be claimant files only for a refund, counts no dependents for Arizona's dependent credit, family credit and 2021 families rebate, Arkansas's dependent credit, disabled-dependent credit (except for a filer's own child, new input `ar_personal_credit_disabled_child_of_filer`) and low-income tables, or California's dependent exemption credit, and gets no childless CalEITC or foster youth credit. A claimable filer loses California's personal, senior and blind credits, and California's standard deduction follows its dependent worksheet, with earned income from the new `dependent_standard_deduction_earned_income` (taxable wages, self-employment and farm income). A filer who is claimed is not counted for Arizona's excise credit and, if they lived in the claimer's home, is not a qualified renter for California's renter credit (new input `lives_with_claiming_taxpayer`). Arkansas's care credit counts only childcare for a qualifying person. The Arizona, Arkansas and California dependent credit reforms, the state dependent exemption repeal and California's AB 2591 reform keep these rules. Arizona's 2021 families rebate counts at most three dependents on a joint return, as on every other return (Laws 2023, ch. 147, sec. 3(E)), and California's 2025 joint renter credit income limit is now $107,988.
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ metadata:
reference:
- title: Arizona Families Tax Rebate
href: https://azdor.gov/individuals/arizona-families-tax-rebate
- title: SB1734 - Arizona Families Tax Rebate
href: https://www.azleg.gov/legtext/56leg/1r/laws/0147.htm
- title: SB1734 - Arizona Families Tax Rebate (Laws 2023, ch. 147), sec. 3(C), (E)
href: https://www.azleg.gov/legtext/56leg/1R/laws/0147.pdf#page=4
SINGLE:
2021-01-01: 3
HEAD_OF_HOUSEHOLD:
Expand All @@ -19,5 +19,8 @@ SEPARATE:
2021-01-01: 3
SURVIVING_SPOUSE:
2021-01-01: 3
# Sec. 3(E): "a maximum of three dependents for a qualifying taxpayer", with one
# rebate for each 2021 return (sec. 3(C)). ADOR's $1,500 joint maximum
# describes two 2021 families who married later, each with its own 2021 return.
JOINT:
2021-01-01: 6
2021-01-01: 3
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,19 @@ SURVIVING_SPOUSE:
2022-01-01: 98_440
2023-01-01: 101_492
2024-01-01: 104_842
2025-01-01: 107_987
2025-01-01: 107_988
JOINT:
2021-01-01: 90_896
2022-01-01: 98_440
2023-01-01: 101_492
2024-01-01: 104_842
2025-01-01: 107_987
2025-01-01: 107_988
HEAD_OF_HOUSEHOLD:
2021-01-01: 90_896
2022-01-01: 98_440
2023-01-01: 101_492
2024-01-01: 104_842
2025-01-01: 107_987
2025-01-01: 107_988

metadata:
period: year
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
from policyengine_us.model_api import *
from policyengine_core.periods import period as period_
from policyengine_us.variables.gov.states.ca.tax.income.exemptions.ca_exemptions import (
ca_personal_aged_blind_exemption_count,
)


def create_repeal_state_dependent_exemptions() -> Reform:
Expand Down Expand Up @@ -239,17 +242,18 @@ def formula(tax_unit, period, parameters):
over_agi_threshold / p.phase_out.increment[filing_status]
)
exemption_reduction = increments * p.phase_out.amount
# Personal Exemptions
personal_exemption_count = p.personal_scale[filing_status]
personal_aged_blind_exemption_count = personal_exemption_count + tax_unit(
"aged_blind_count", period
# Personal, blind and senior exemptions, as in the baseline
# (none for a filer who can be claimed elsewhere)
personal_aged_blind_exemption_count = (
ca_personal_aged_blind_exemption_count(
tax_unit, period, p, filing_status
)
)
personal_aged_blind_exemption = max_(
0,
personal_aged_blind_exemption_count * (p.amount - exemption_reduction),
)
# Dependent exemptions
# total exemptions
# Dependent exemptions repealed
return personal_aged_blind_exemption

class ga_exemptions(Variable):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,14 @@ def formula(person, period, parameters):
# age; this person-level amount is summed via adds in
# ar_personal_credits_potential, so the age gate composes.
p = parameters(period).gov.contrib.states.ar.dependent_credit
is_dependent = person("is_tax_unit_dependent", period)
# As in the baseline, a return on which the filer (or, if joint,
# either spouse) can be claimed has no dependents (IRC 152(b)(1)).
# The would-be claimant's filing exception (not required to file and
# filing only for a refund, Publication 501) restores the dependents.
filer_is_dependent = person.tax_unit(
"head_or_spouse_is_dependent_elsewhere_without_filing_exception", period
)
is_dependent = person("is_tax_unit_dependent", period) & ~filer_is_dependent
if p.age_limit.in_effect:
age = person("age", period)
eligible = is_dependent & (age < p.age_limit.threshold)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,17 @@ def formula(tax_unit, period, parameters):
period
).gov.states.az.tax.income.credits.dependent_credit
p = parameters(period).gov.contrib.states.az.dependent_credit
dependent = person("is_tax_unit_dependent", period)
# As in the baseline, a return on which the filer (or, if joint,
# either spouse) can be claimed has no dependents (A.R.S.
# 43-1001(3), IRC 152(b)(1)).
# The would-be claimant's filing exception (not required to file and
# filing only for a refund, Publication 501) restores the dependents.
filer_is_dependent = tax_unit(
"head_or_spouse_is_dependent_elsewhere_without_filing_exception", period
)
dependent = person("is_tax_unit_dependent", period) & ~tax_unit.project(
filer_is_dependent
)
age = person("age", period)
if p.age_limit.in_effect:
eligible = dependent & (age < p.age_limit.threshold)
Expand Down
12 changes: 11 additions & 1 deletion policyengine_us/reforms/states/ca/ab2591/ab2591.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
from policyengine_us.model_api import *
from policyengine_core.periods import period as period_
from policyengine_us.variables.gov.states.ca.tax.income.deductions.standard.ca_standard_deduction import (
ca_dependent_standard_deduction_limit,
)


def create_ca_ab2591() -> Reform:
Expand Down Expand Up @@ -52,7 +55,14 @@ def formula(tax_unit, period, parameters):
# statutory standard deduction.
elected_deduction = max_(fpl_deduction, baseline_deduction)

return where(p_ab2591.in_effect, elected_deduction, baseline_deduction)
deduction = where(p_ab2591.in_effect, elected_deduction, baseline_deduction)
# Assumes the bill leaves 17073.5(c)(2) in place, so the IRC
# 63(c)(5) limit for a filer who can be claimed as a dependent
# applies to whichever deduction is taken (and to the baseline
# amount in years the election is not in effect).
return ca_dependent_standard_deduction_limit(
tax_unit, period, parameters, deduction
)

class reform(Reform):
def apply(self):
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
from policyengine_us.model_api import *
from policyengine_core.periods import period as period_
from policyengine_us.variables.gov.states.ca.tax.income.exemptions.ca_exemptions import (
ca_personal_aged_blind_exemption_count,
)


def create_ca_dependent_credit_reform() -> Reform:
Expand All @@ -25,10 +28,12 @@ def formula(tax_unit, period, parameters):
)
exemption_reduction = increments * p.phase_out.amount

# Personal exemptions (unchanged)
personal_exemption_count = p.personal_scale[filing_status]
personal_aged_blind_exemption_count = personal_exemption_count + tax_unit(
"aged_blind_count", period
# Personal, blind and senior exemptions (unchanged from baseline,
# including the rules for a filer who can be claimed elsewhere)
personal_aged_blind_exemption_count = (
ca_personal_aged_blind_exemption_count(
tax_unit, period, p, filing_status
)
)
personal_aged_blind_exemption = max_(
0,
Expand All @@ -40,7 +45,16 @@ 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)
# As in the baseline, a return on which the filer (or, if joint,
# either spouse) can be claimed has no dependents (IRC 152(b)(1)).
# The would-be claimant's filing exception (not required to file and
# filing only for a refund, Publication 501) restores the dependents.
filer_is_dependent = tax_unit(
"head_or_spouse_is_dependent_elsewhere_without_filing_exception", period
)
is_dependent = person("is_tax_unit_dependent", period) & ~tax_unit.project(
filer_is_dependent
)
if c.age_limit.in_effect:
eligible = is_dependent & (age < c.age_limit.threshold)
else:
Expand Down
150 changes: 150 additions & 0 deletions policyengine_us/tests/core/test_claimable_filer_az_ar_ca_invariants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
"""Arizona, Arkansas and California rules for a filer who can be claimed as a
dependent.

Arizona's dependent and family credits, excise credit and 2021 families
rebate, Arkansas's dependent, disabled-dependent and low-income-table rules,
and California's standard deduction, exemption credits, CalEITC, foster youth
credit and renter credit apply IRC 151(d)(2) and 152(b)(1) and each state's
own text.

For couples drawn by Hypothesis, a seeded population and crafted cases in the
three states (renters who lived with the person who can claim them, and a
disabled dependent who is a filer's own child, someone else's child, or of
unknown relationship), with either, both or neither spouse claimed:

1. Swap invariance: each output is the same under either head/spouse
labelling.
2. Monotonicity: marking another filer as claimed never raises a credit,
deduction or eligibility below.
3. Identities: a return with a claimable filer gets no Arizona dependent
credit or families rebate and no Arkansas dependent credit, and a return
on which every filer is claimed gets no Arizona excise credit (the cases
set the claim input, so claimability follows it and the filing exception
is off).
"""

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

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,
_calc,
_crafted_cases,
_seeded_couples,
adults,
dependents,
)

STATES = ["AZ", "AR", "CA"]
# 2021 covers the Arizona families rebate; 2026 the new Arizona amounts.
YEARS = [2021, 2023, 2025, 2026]
MONOTONE = [
"az_dependent_tax_credit_potential",
"az_family_tax_credit_eligible",
"az_family_tax_credit_potential",
"az_increased_excise_tax_credit",
"az_families_tax_rebate",
"ar_personal_credits_potential",
"ca_standard_deduction",
"ca_exemptions",
"ca_eitc_eligible",
"ca_eitc",
"ca_foster_youth_tax_credit",
"ca_renter_credit",
]
# Arkansas's tax can rise when the low-income table changes, so it is checked
# only for swap invariance.
OUTPUTS = MONOTONE + [
"ar_income_tax_before_non_refundable_credits_unit",
"state_income_tax",
]


def _identities(sim, year):
claimed_filer = _calc(sim, "head_or_spouse_is_dependent_elsewhere", year) > 0
every_filer = _calc(sim, "every_filer_is_dependent_elsewhere", year) > 0
for name in ["az_dependent_tax_credit_potential", "az_families_tax_rebate"]:
assert not _calc(sim, name, year)[claimed_filer].any(), name
ar_dependent = np.asarray(
sim.calculate("ar_personal_credit_dependent", year, map_to="tax_unit")
)
assert not ar_dependent[claimed_filer].any()
assert not _calc(sim, "az_increased_excise_tax_credit", year)[every_filer].any()


def _claimable_rule_cases():
"""Cases for the inputs the shared strategies never set."""
renter = {
"age": 20,
"employment_income": 2_000.0,
"rent": 6_000.0,
"lives_with_claiming_taxpayer": True,
}
earner = {"age": 30, "person_id": 1, "employment_income": 20_000.0}
cases = []
for state in STATES:
for claimed in CLAIM_PATTERNS:
cases.append(
{
"state": state,
"adults": [
{**renter, "person_id": 1},
{**renter, "person_id": 2, "age": 22},
],
"dependents": [],
"claimed": claimed,
}
)
# Parent id 1 is the first adult; 99 is a parent outside the
# household; no id leaves the relationship unknown.
for parent in (1, 99, None):
dependent = {"age": 8, "is_disabled": True, "person_id": 3}
if parent is not None:
dependent["parent_1_id"] = parent
cases.append(
{
"state": state,
"adults": [earner, {"age": 32, "person_id": 2}],
"dependents": [dependent],
"claimed": claimed,
}
)
return cases


@st.composite
def couples(draw):
return {
"state": draw(st.sampled_from(STATES)),
"adults": [draw(adults()), draw(adults())],
"dependents": draw(st.lists(dependents(), max_size=2)),
"claimed": draw(st.sampled_from(CLAIM_PATTERNS)),
}


@settings(
max_examples=2,
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_az_ar_ca_claimable_filer_rules_are_label_free(year, units):
check_swap(units, year, OUTPUTS, _identities)


@pytest.mark.parametrize("year", YEARS)
def test_az_ar_ca_claimable_filer_rules_seeded_population(year):
units = (
_seeded_couples(STATES, per_state=3)
+ _crafted_cases(STATES)
+ _claimable_rule_cases()
)
check_swap(units, year, OUTPUTS, _identities)
check_monotone(units, year, MONOTONE, _identities)
Loading