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-dc-ga-id.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Apply District of Columbia, Georgia and Idaho rules for a filer who can be claimed as a dependent: DC's property tax credit claimant, childless EITC, child care and child tax credit children; Georgia's dependent exemption, low income credit and 2023 surplus refund; Idaho's food tax credit, household and dependent care deduction and 2022 rebate.
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
description: The District of Columbia allows a person who was a dependent under an income tax law to claim the property tax credit only at or above this age.
values:
2021-01-01: 65
metadata:
unit: year
label: DC property tax credit minimum age for a dependent claimant
period: year
reference:
- title: Code of the District of Columbia § 47–1806.06 (k)
href: https://code.dccouncil.gov/us/dc/council/code/sections/47-1806.06
10 changes: 9 additions & 1 deletion policyengine_us/reforms/dc_kccatc.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,15 @@ def formula(tax_unit, period, parameters):
income_eligible = taxinc <= p.kccatc.income_limit[filing_status]
# determine count of KCCATC age eligible children
person = tax_unit.members
is_dependent = person("is_tax_unit_dependent", period)
# As in the baseline formula: no dependents on a return whose
# filer (or, if joint, either spouse) can be claimed.
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
)
age = person("age", period)
kccatc_age_eligible = is_dependent & (age <= p.kccatc.max_age)
kccatc_eligible_count = tax_unit.sum(kccatc_age_eligible)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,15 @@ def formula(tax_unit, period, parameters):
# Income based eligibility criteria does not apply when the credit
# is phased out.
eligible = tax_unit("dc_ptc_eligible", period)
# The reform keeps D.C. Code 47-1806.06(k): no credit through a
# claimant who was a dependent, unless 65 or older.
has_claimant = tax_unit("dc_ptc_has_eligible_claimant", period)
if p.phase_out.applies:
income_limit = tax_unit("dc_ptc_income_limit", period)
income = tax_unit("adjusted_gross_income", period)
income_excess = max_(0, income - income_limit)
return max_(0, amount - p.phase_out.rate * income_excess)
return amount * eligible
return max_(0, amount - p.phase_out.rate * income_excess) * has_claimant
return amount * eligible * has_claimant

class dc_ptc_eligible(Variable):
value_type = bool
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,13 @@ def formula(tax_unit, period, parameters):

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)), as in
# the baseline formula.
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

# Apply age limit if in effect.
if p.age_limit.in_effect:
Expand Down
107 changes: 107 additions & 0 deletions policyengine_us/tests/core/test_claimable_filer_dc_ga_id_invariants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
"""District of Columbia, Georgia and Idaho rules for a filer who can be claimed
as a dependent.

A return on which the filer, or on a joint return either spouse, can be claimed
as a dependent has no dependents (IRC 152(b)(1)). DC's property tax credit
needs a claimant who was not a dependent or is 65 or older; Georgia's low
income credit and 2023 surplus refund need one filer who qualifies; Idaho's
food tax credit leaves out each filer who can be claimed.

For couples drawn by Hypothesis and a seeded population in these states, 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 the outputs in
MONOTONE (amounts not capped by a tax liability that the claim can raise).
3. Identities: a return with a claimable filer has no DC keep child care
affordable credit or child tax credit and its Idaho food tax credit is at
most one filer's amount; a return on which every filer can be claimed has
no Georgia low income credit.

Hypothesis batches check 1 and 3; the seeded population checks 1, 2 and 3.
"""

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 = ["DC", "GA", "ID"]
YEARS = [2021, 2023, 2025, 2026]
MONOTONE = [
"dc_ptc",
"dc_eitc",
"dc_kccatc",
"dc_ctc",
"ga_exemptions",
"ga_low_income_credit_potential",
"ga_ctc_potential",
"id_grocery_credit",
"id_household_and_dependent_care_expense_deduction",
]
OUTPUTS = MONOTONE + ["ga_surplus_tax_rebate", "id_2022_rebate"]


@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)),
}


def _identities(sim, year):
claimed_filer = _calc(sim, "head_or_spouse_is_dependent_elsewhere", year) > 0
for name in ("dc_kccatc", "dc_ctc"):
assert not _calc(sim, name, year)[claimed_filer].any(), (
f"{name} in {year} is positive on a return with a claimable filer"
)
# Georgia's low income credit needs one filer who cannot be claimed.
every_filer = _calc(sim, "every_filer_is_dependent_elsewhere", year) > 0
assert not _calc(sim, "ga_low_income_credit_potential", year)[every_filer].any()
# At most the filer who cannot be claimed keeps a food tax credit.
one_filer = _one_filer_food_amount(sim, year)
grocery = _calc(sim, "id_grocery_credit", year)
assert (grocery[claimed_filer] <= one_filer + 0.01).all()


def _one_filer_food_amount(sim, year):
p = sim.tax_benefit_system.parameters(f"{year}-01-01").gov.states.id.tax.income
amount = p.credits.grocery.base.amount
if p.credits.grocery.aged.in_effect:
amount += p.credits.grocery.aged.amount
return amount


@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_dc_ga_id_claimable_filer_rules_are_label_free(year, units):
check_swap(units, year, OUTPUTS, _identities)


@pytest.mark.parametrize("year", YEARS)
def test_dc_ga_id_claimable_filer_rules_seeded_population(year):
units = _seeded_couples(STATES, per_state=3) + _crafted_cases(STATES)
check_swap(units, year, OUTPUTS, _identities)
check_monotone(units, year, MONOTONE, _identities)
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,140 @@
state_code: DC
output:
dc_ctc_eligible_child: false


- name: 2026 single filer who can be claimed as a dependent, no DC child tax credit child
period: 2026
input:
people:
filer:
age: 30
employment_income: 10_000
is_tax_unit_head: true
is_tax_unit_spouse: false
claimed_as_dependent_on_another_return: true
child:
age: 3
tax_units:
tax_unit:
members: [filer, child]
households:
household:
members: [filer, child]
state_code: DC
output:
# 47-1806.17(a): a child for whom the taxpayer is allowed a
# deduction under IRC 151; IRC 152(b)(1).
dc_ctc_eligible_child: [false, false]

- name: 2026 joint return with one spouse who can be claimed as a dependent, no DC child tax credit child (the claimable spouse is the head)
period: 2026
input:
people:
person_a:
age: 30
employment_income: 5_000
is_tax_unit_head: true
is_tax_unit_spouse: false
claimed_as_dependent_on_another_return: true
person_b:
age: 30
employment_income: 5_000
is_tax_unit_head: false
is_tax_unit_spouse: true
child:
age: 3
tax_units:
tax_unit:
members: [person_a, person_b, child]
filing_status: JOINT
households:
household:
members: [person_a, person_b, child]
state_code: DC
output:
dc_ctc_eligible_child: [false, false, false]

- name: 2026 joint return with one spouse who can be claimed as a dependent, no DC child tax credit child (the claimable spouse is the spouse)
period: 2026
input:
people:
person_b:
age: 30
employment_income: 5_000
is_tax_unit_head: true
is_tax_unit_spouse: false
person_a:
age: 30
employment_income: 5_000
is_tax_unit_head: false
is_tax_unit_spouse: true
claimed_as_dependent_on_another_return: true
child:
age: 3
tax_units:
tax_unit:
members: [person_b, person_a, child]
filing_status: JOINT
households:
household:
members: [person_b, person_a, child]
state_code: DC
output:
dc_ctc_eligible_child: [false, false, false]

- name: 2026 joint return with both spouses claimable as dependents, no DC child tax credit child
period: 2026
input:
people:
person_a:
age: 30
employment_income: 5_000
is_tax_unit_head: true
is_tax_unit_spouse: false
claimed_as_dependent_on_another_return: true
person_b:
age: 30
employment_income: 5_000
is_tax_unit_head: false
is_tax_unit_spouse: true
claimed_as_dependent_on_another_return: true
child:
age: 3
tax_units:
tax_unit:
members: [person_a, person_b, child]
filing_status: JOINT
households:
household:
members: [person_a, person_b, child]
state_code: DC
output:
dc_ctc_eligible_child: [false, false, false]

- name: 2026 joint return with neither spouse claimable, no DC child tax credit child
period: 2026
input:
people:
person_a:
age: 30
employment_income: 5_000
is_tax_unit_head: true
is_tax_unit_spouse: false
person_b:
age: 30
employment_income: 5_000
is_tax_unit_head: false
is_tax_unit_spouse: true
child:
age: 3
tax_units:
tax_unit:
members: [person_a, person_b, child]
filing_status: JOINT
households:
household:
members: [person_a, person_b, child]
state_code: DC
output:
dc_ctc_eligible_child: [false, false, true]
Loading