Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expanded CTC reform including a reformed phase-in structure #5373

Draft
wants to merge 7 commits into
base: master
Choose a base branch
from
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
4 changes: 4 additions & 0 deletions changelog_entry.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
- bump: minor
changes:
added:
- Expanded CTC reform including a reformed phase-in structure.
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
description: An expanded Child Tax Credit amount with an alternative phase-in structure is provided, if this is true.
values:
0000-01-01: false
metadata:
unit: bool
period: year
label: Expanded CTC in effect
reference:
- title: EPPC Tax Teams Comment on Working Families
href: https://eppc.org/wp-content/uploads/2024/10/Tax-Teams-Comment-on-Working-Families.pdf
3 changes: 3 additions & 0 deletions policyengine_us/reforms/ctc/eppc/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from .expanded_ctc import (
create_expanded_ctc_reform,
)
190 changes: 190 additions & 0 deletions policyengine_us/reforms/ctc/eppc/expanded_ctc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
from policyengine_us.model_api import *


def create_expanded_ctc() -> Reform:
class ctc_phase_in(Variable):
value_type = float
entity = TaxUnit
label = "CTC phase-in"
unit = USD
definition_period = YEAR
reference = "https://eppc.org/wp-content/uploads/2024/10/Tax-Teams-Comment-on-Working-Families.pdf"

def formula(tax_unit, period, parameters):
tax = tax_unit("income_tax_pre_ctc", period)
total_benefits = tax_unit.household("household_benefits", period)
max_benefit_amount = tax_unit("maximum_benefits", period)
benefit_reduction = max_benefit_amount - total_benefits
tax_with_benefit_reduction = tax + benefit_reduction
p = parameters(period).gov.irs.credits.ctc.refundable.phase_in
return max_(p.rate * tax_with_benefit_reduction, 0)

class income_tax_non_refundable_credits_pre_ctc(Variable):
value_type = float
entity = TaxUnit
definition_period = YEAR
label = "federal non-refundable income tax credits"
unit = USD

def formula(tax_unit, period, parameters):
non_ref_credits = parameters(period).gov.irs.credits.non_refundable
credits = [
credit
for credit in non_ref_credits
if credit not in ["non_refundable_ctc"]
]
return add(tax_unit, period, credits)

class income_tax_refundable_credits_pre_ctc(Variable):
value_type = float
entity = TaxUnit
definition_period = YEAR
label = "federal refundable income tax credits"
unit = USD

def formula(tax_unit, period, parameters):
ref_credits = parameters(period).gov.irs.credits.refundable
credits = [
credit
for credit in ref_credits
if credit not in ["refundable_ctc"]
]
return add(tax_unit, period, credits)

class income_tax_capped_non_refundable_credits(Variable):
value_type = float
entity = TaxUnit
label = "non-refundable tax credits"
unit = USD
documentation = "Capped value of non-refundable tax credits"
definition_period = YEAR
adds = ["income_tax_non_refundable_credits_pre_ctc"]
subtracts = ["income_tax_unavailable_non_refundable_credits"]

class income_tax_unavailable_non_refundable_credits(Variable):
value_type = float
entity = TaxUnit
label = "unavailable non-refundable tax credits"
unit = USD
documentation = "Total value of non-refundable tax credits that were not available to the filer due to having too low income tax."
definition_period = YEAR

def formula(tax_unit, period, parameters):
return -min_(
tax_unit("income_tax_before_credits", period),
tax_unit("income_tax_non_refundable_credits_pre_ctc", period),
) + tax_unit("income_tax_non_refundable_credits_pre_ctc", period)

class income_tax_pre_ctc(Variable):
value_type = float
entity = TaxUnit
definition_period = YEAR
unit = USD
label = "Federal income tax"
documentation = "Total federal individual income tax liability."

def formula(person, period, parameters):
if parameters(
period
).gov.contrib.ubi_center.flat_tax.abolish_federal_income_tax:
return 0
else:
added_components = add(
person, period, ["income_tax_before_refundable_credits"]
)
subtracted_components = add(
person, period, ["income_tax_refundable_credits_pre_ctc"]
)
return added_components - subtracted_components

class income_tax(Variable):
value_type = float
entity = TaxUnit
definition_period = YEAR
unit = USD
label = "Federal income tax"
documentation = "Total federal individual income tax liability."

def formula(tax_unit, period, parameters):
pre_ctc_tax = tax_unit("income_tax_pre_ctc", period)
non_ref_ctc = tax_unit("non_refundable_ctc", period)
ref_ctc = tax_unit("refundable_ctc", period)
return max_(pre_ctc_tax - non_ref_ctc, 0) - ref_ctc

class maximum_benefits(Variable):
value_type = float
entity = TaxUnit
definition_period = YEAR
label = "Maximum benfit amount which the housheold is entitled to"
unit = USD
# Currently only includes SNAP, free school meals, and TANF

def formula(tax_unit, period, parameters):
snap_max_allotment = tax_unit.spm_unit(
"snap_max_allotment", period
)
# get maximum free school meal allotment
state_group = tax_unit.spm_unit.household(
"state_group_str", period
)
tier = "FREE"
p_amount = parameters(period).gov.usda.school_meals.amount
nslp_per_child = p_amount.nslp[state_group][tier]
sbp_per_child = p_amount.sbp[state_group][tier]
school_meals_daily_subsidy = nslp_per_child + sbp_per_child
daily_paid_subsidy = tax_unit.spm_unit(
"school_meal_paid_daily_subsidy", period
)
net_daily_subsidy_per_child = (
school_meals_daily_subsidy - daily_paid_subsidy
)
p_school_meals = parameters(period).gov.usda.school_meals
children = add(tax_unit, period, ["is_in_k12_school"])
school_meal_max_value = (
net_daily_subsidy_per_child
* children
* p_school_meals.school_days
)
# Get TANF grant standards (maximum amounts)
max_federal_tanf = tax_unit.spm_unit("tanf_max_amount", period)
max_ny_tanf = tax_unit.spm_unit("ny_tanf_grant_standard", period)
tanf_dem_eligible = tax_unit.spm_unit(
"is_demographic_tanf_eligible", period
)
max_dc_tanf = tax_unit.spm_unit("dc_tanf_grant_standard", period)
max_co_tanf = tax_unit.spm_unit("co_tanf_grant_standard", period)
max_tanf = (
max_co_tanf + max_dc_tanf + max_federal_tanf + max_ny_tanf
) * tanf_dem_eligible
return snap_max_allotment + school_meal_max_value + max_tanf

class reform(Reform):
def apply(self):
self.neutralize_variable("eitc")
self.neutralize_variable("head_of_household_eligible")
self.neutralize_variable("cdcc")
self.update_variable(ctc_phase_in)
self.update_variable(income_tax)
self.update_variable(income_tax_pre_ctc)
self.update_variable(income_tax_unavailable_non_refundable_credits)
self.update_variable(income_tax_refundable_credits_pre_ctc)
self.update_variable(income_tax_non_refundable_credits_pre_ctc)
self.update_variable(income_tax_capped_non_refundable_credits)
self.update_variable(maximum_benefits)

return reform


def create_expanded_ctc_reform(parameters, period, bypass: bool = False):
if bypass:
return create_expanded_ctc()

p = parameters(period).gov.contrib.ctc.eppc.expanded_ctc

if p.in_effect:
return create_expanded_ctc()
else:
return None


expanded_ctc = create_expanded_ctc_reform(None, None, bypass=True)
5 changes: 5 additions & 0 deletions policyengine_us/reforms/reforms.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@
from .second_earner import (
create_second_earner_tax_reform,
)
from .ctc.eppc import (
create_expanded_ctc_reform,
)


from policyengine_core.reforms import Reform
Expand Down Expand Up @@ -148,6 +151,7 @@ def create_structural_reforms_from_parameters(parameters, period):
second_earner_tax_reform = create_second_earner_tax_reform(
parameters, period
)
expanded_ctc = create_expanded_ctc_reform(parameters, period)

reforms = [
afa_reform,
Expand Down Expand Up @@ -179,6 +183,7 @@ def create_structural_reforms_from_parameters(parameters, period):
repeal_state_dependent_exemptions,
ctc_older_child_supplement,
second_earner_tax_reform,
expanded_ctc,
]
reforms = tuple(filter(lambda x: x is not None, reforms))

Expand Down
141 changes: 141 additions & 0 deletions policyengine_us/tests/policy/contrib/ctc/eppc/expanded_ctc.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
- name: Base test
period: 2024
reforms: policyengine_us.reforms.ctc.eppc.expanded_ctc.expanded_ctc
input:
gov.contrib.ctc.eppc.expanded_ctc.in_effect: true
income_tax_pre_ctc: 800
household_benefits: 700
maximum_benefits: 900
output:
ctc_phase_in: 150

- name: Negative pre-ctc income tax
period: 2024
reforms: policyengine_us.reforms.ctc.eppc.expanded_ctc.expanded_ctc
input:
gov.contrib.ctc.eppc.expanded_ctc.in_effect: true
income_tax_pre_ctc: -1_000
maximum_benefits: 0
output:
ctc_phase_in: 0

- name: Single parent of one child, $20k income
period: 2024
reforms: policyengine_us.reforms.ctc.eppc.expanded_ctc.expanded_ctc
input:
gov.contrib.ctc.eppc.expanded_ctc.in_effect: true
people:
person1:
age: 40
employment_income: 20_000
person2:
age: 10
tax_units:
tax_unit:
members: [person1, person2]
output:
non_refundable_ctc: 540
refundable_ctc: 1_460
income_tax_pre_ctc: 540
income_tax: -1_460

- name: Single parent of one child, $20k income - no reform, no eitc
period: 2024
input:
gov.contrib.ctc.eppc.expanded_ctc.in_effect: false
people:
person1:
age: 40
employment_income: 20_000
person2:
age: 10
tax_units:
tax_unit:
members: [person1, person2]
eitc: 0
output:
non_refundable_ctc: 300
refundable_ctc: 1_700
income_tax: -1_700

- name: Joint household with one child, $10k income
period: 2024
absolute_error_margin: 1
reforms: policyengine_us.reforms.ctc.eppc.expanded_ctc.expanded_ctc
input:
gov.contrib.ctc.eppc.expanded_ctc.in_effect: true
people:
person1:
age: 40
employment_income: 10_000
person2:
age: 40
person3:
age: 10
tax_units:
tax_unit:
members: [person1, person2, person3]
households:
household:
members: [person1, person2, person3]
state_code: TX
output:
non_refundable_ctc: 1_748
refundable_ctc: 252
ctc_phase_in: 252
household_benefits: 8_562
maximum_benefits: 10_242
income_tax_pre_ctc: 0
income_tax: -252

- name: Joint household with one child, $10k income - no reform, no eitc
period: 2024
absolute_error_margin: 1
input:
gov.contrib.ctc.eppc.expanded_ctc.in_effect: false
people:
person1:
age: 40
employment_income: 10_000
person2:
age: 40
person3:
age: 10
tax_units:
tax_unit:
members: [person1, person2, person3]
eitc: 0
households:
household:
members: [person1, person2, person3]
state_code: TX
output:
non_refundable_ctc: 875
refundable_ctc: 1_125
ctc_phase_in: 1_125
household_benefits: 8_562
income_tax: -1_125

- name: Joint household with one child, $60k income
period: 2024
absolute_error_margin: 4
reforms: policyengine_us.reforms.ctc.eppc.expanded_ctc.expanded_ctc
input:
gov.contrib.ctc.eppc.expanded_ctc.in_effect: true
people:
person1:
age: 40
employment_income: 60_000
person2:
age: 40
person3:
age: 10
tax_units:
tax_unit:
members: [person1, person2, person3]
output:
non_refundable_ctc: 2_000
refundable_ctc: 0
ctc_phase_in: 3_686
income_tax_pre_ctc: 3_232
income_tax: 1_232
Loading