diff --git a/changelog_entry.yaml b/changelog_entry.yaml index e69de29bb2d..3bbdcdbd0aa 100644 --- a/changelog_entry.yaml +++ b/changelog_entry.yaml @@ -0,0 +1,4 @@ +- bump: minor + changes: + added: + - NYC school tax credit phase out reform. diff --git a/policyengine_us/parameters/gov/contrib/local/nyc/stc/phase_out/in_effect.yaml b/policyengine_us/parameters/gov/contrib/local/nyc/stc/phase_out/in_effect.yaml new file mode 100644 index 00000000000..4b0e94537f3 --- /dev/null +++ b/policyengine_us/parameters/gov/contrib/local/nyc/stc/phase_out/in_effect.yaml @@ -0,0 +1,9 @@ +description: The NYC school tax credit phases out with state adjusted gross income, if this is true. + +values: + 0000-01-01: false + +metadata: + unit: bool + period: year + label: NYC school tax credit phase out in effect diff --git a/policyengine_us/parameters/gov/contrib/local/nyc/stc/phase_out/rate/joint_and_surviving_spouse.yaml b/policyengine_us/parameters/gov/contrib/local/nyc/stc/phase_out/rate/joint_and_surviving_spouse.yaml new file mode 100644 index 00000000000..6c3a30b50ce --- /dev/null +++ b/policyengine_us/parameters/gov/contrib/local/nyc/stc/phase_out/rate/joint_and_surviving_spouse.yaml @@ -0,0 +1,20 @@ +description: New York reduces the NYC school tax credit for joint filers and surviving spouses at this rate, based on state adjusted gross income. +metadata: + type: marginal_rate + threshold_unit: currency-USD + rate_unit: /1 + period: year + label: NYC school tax credit joint and surviving spouse reduction rate + reference: + - title: New York Senate Bill S2238 §1(4-c)(B) + href: https://www.nysenate.gov/legislation/bills/2025/S2238 + +brackets: + - threshold: + 2025-01-01: 0 + rate: + 2025-01-01: 0 + - threshold: + 2025-01-01: 150_000 + rate: + 2025-01-01: 0.05 diff --git a/policyengine_us/parameters/gov/contrib/local/nyc/stc/phase_out/rate/other.yaml b/policyengine_us/parameters/gov/contrib/local/nyc/stc/phase_out/rate/other.yaml new file mode 100644 index 00000000000..bb7961a2876 --- /dev/null +++ b/policyengine_us/parameters/gov/contrib/local/nyc/stc/phase_out/rate/other.yaml @@ -0,0 +1,20 @@ +description: New York reduces the NYC school tax credit for single, separate, and head of household filers, based on state adjusted gross income. +metadata: + type: marginal_rate + threshold_unit: currency-USD + rate_unit: /1 + period: year + label: NYC school tax credit other reduction rate + reference: + - title: New York Senate Bill S2238 §1(4-c)(A) + href: https://www.nysenate.gov/legislation/bills/2025/S2238 + +brackets: + - threshold: + 2025-01-01: 0 + rate: + 2025-01-01: 0 + - threshold: + 2025-01-01: 75_000 + rate: + 2025-01-01: 0.05 diff --git a/policyengine_us/reforms/local/nyc/stc/phase_out/README.md b/policyengine_us/reforms/local/nyc/stc/phase_out/README.md new file mode 100644 index 00000000000..ddec2de5387 --- /dev/null +++ b/policyengine_us/reforms/local/nyc/stc/phase_out/README.md @@ -0,0 +1,7 @@ +## NYC School Tax Credit Reform with Phase-Out + +# This code defines a policy reform for implementing a New York City School Tax Credit with a phase-out mechanism, as outlined in [Senate Bill S2238](https://www.nysenate.gov/legislation/bills/2025/S2238). + +# The credit is phased out based on state adjusted gross income (AGI) and filing status. + +# The phase-out start threshold is doubled for joint filers and surviving spouses. diff --git a/policyengine_us/reforms/local/nyc/stc/phase_out/__init__.py b/policyengine_us/reforms/local/nyc/stc/phase_out/__init__.py new file mode 100644 index 00000000000..869c2e8e1a3 --- /dev/null +++ b/policyengine_us/reforms/local/nyc/stc/phase_out/__init__.py @@ -0,0 +1,3 @@ +from .nyc_school_tax_credit_with_phase_out import ( + create_nyc_school_tax_credit_with_phase_out_reform, +) diff --git a/policyengine_us/reforms/local/nyc/stc/phase_out/nyc_school_tax_credit_with_phase_out.py b/policyengine_us/reforms/local/nyc/stc/phase_out/nyc_school_tax_credit_with_phase_out.py new file mode 100644 index 00000000000..0ddd5500ed9 --- /dev/null +++ b/policyengine_us/reforms/local/nyc/stc/phase_out/nyc_school_tax_credit_with_phase_out.py @@ -0,0 +1,84 @@ +from policyengine_us.model_api import * +from policyengine_core.periods import period as period_ + + +def create_nyc_school_tax_credit_with_phase_out() -> Reform: + class nyc_school_tax_credit_phase_out(Variable): + value_type = float + unit = USD + entity = TaxUnit + label = "NYC School Tax Credit Phase Out" + definition_period = YEAR + defined_for = "in_nyc" + reference = "https://www.nysenate.gov/legislation/bills/2025/S2238" + + def formula(tax_unit, period, parameters): + agi = tax_unit("ny_agi", period) + filing_status = tax_unit("filing_status", period) + joint = filing_status == filing_status.possible_values.JOINT + surviving_spouse = ( + filing_status == filing_status.possible_values.SURVIVING_SPOUSE + ) + joint_filers = joint | surviving_spouse + p = parameters(period).gov.contrib.local.nyc.stc.phase_out.rate + return where( + joint_filers, + p.joint_and_surviving_spouse.calc(agi), + p.other.calc(agi), + ) + + class nyc_school_tax_credit(Variable): + value_type = float + entity = TaxUnit + label = "NYC School Tax Credit" + unit = USD + definition_period = YEAR + defined_for = "in_nyc" + reference = "https://www.nysenate.gov/legislation/bills/2025/S2238" + + def formula(tax_unit, period, parameters): + base_amount = add( + tax_unit, + period, + [ + "nyc_school_tax_credit_fixed_amount", + "nyc_school_tax_credit_rate_reduction_amount", + ], + ) + phase_out = tax_unit("nyc_school_tax_credit_phase_out", period) + return max_(0, base_amount - phase_out) + + class reform(Reform): + def apply(self): + self.update_variable(nyc_school_tax_credit_phase_out) + self.update_variable(nyc_school_tax_credit) + + return reform + + +def create_nyc_school_tax_credit_with_phase_out_reform( + parameters, period, bypass: bool = False +): + if bypass: + return create_nyc_school_tax_credit_with_phase_out() + + p = parameters.gov.contrib.local.nyc.stc.phase_out + + reform_active = False + current_period = period_(period) + + for i in range(5): + if p(current_period).in_effect: + reform_active = True + break + current_period = current_period.offset(1, "year") + + if reform_active: + return create_nyc_school_tax_credit_with_phase_out() + else: + return None + + +nyc_school_tax_credit_with_phase_out = ( + create_nyc_school_tax_credit_with_phase_out_reform(None, None, bypass=True) +) diff --git a/policyengine_us/reforms/reforms.py b/policyengine_us/reforms/reforms.py index 223bee62f24..774f943ae53 100644 --- a/policyengine_us/reforms/reforms.py +++ b/policyengine_us/reforms/reforms.py @@ -82,6 +82,10 @@ create_limit_salt_deduction_to_property_taxes_reform, ) +from .local.nyc.stc.phase_out import ( + create_nyc_school_tax_credit_with_phase_out_reform, +) + from policyengine_core.reforms import Reform import warnings @@ -181,6 +185,9 @@ def create_structural_reforms_from_parameters(parameters, period): parameters, period ) ) + nyc_school_tax_credit_with_phase_out = ( + create_nyc_school_tax_credit_with_phase_out_reform(parameters, period) + ) reforms = [ afa_reform, @@ -218,6 +225,7 @@ def create_structural_reforms_from_parameters(parameters, period): dc_property_tax_credit, ny_2025_inflation_rebates, limit_salt_deduction_to_property_taxes, + nyc_school_tax_credit_with_phase_out, ] reforms = tuple(filter(lambda x: x is not None, reforms)) diff --git a/policyengine_us/tests/policy/contrib/local/nyc/stc/nyc_school_tax_credit_with_phase_out.yaml b/policyengine_us/tests/policy/contrib/local/nyc/stc/nyc_school_tax_credit_with_phase_out.yaml new file mode 100644 index 00000000000..0614b149961 --- /dev/null +++ b/policyengine_us/tests/policy/contrib/local/nyc/stc/nyc_school_tax_credit_with_phase_out.yaml @@ -0,0 +1,49 @@ +- name: Joint, with AGI below phase out threshold + period: 2025 + reforms: policyengine_us.reforms.local.nyc.stc.phase_out.nyc_school_tax_credit_with_phase_out.nyc_school_tax_credit_with_phase_out + input: + gov.contrib.local.nyc.stc.phase_out.in_effect: true + in_nyc: True + ny_agi: 150_000 + filing_status: JOINT + output: + nyc_school_tax_credit_phase_out: 0 + +- name: Single, with AGI above phase out threshold + period: 2025 + reforms: policyengine_us.reforms.local.nyc.stc.phase_out.nyc_school_tax_credit_with_phase_out.nyc_school_tax_credit_with_phase_out + input: + gov.contrib.local.nyc.stc.phase_out.in_effect: true + in_nyc: True + ny_agi: 150_000 + filing_status: SINGLE + output: + nyc_school_tax_credit_phase_out: 3_750 + +- name: Single, with AGI above phase out threshold, with credit amount, fully reduced + period: 2025 + reforms: policyengine_us.reforms.local.nyc.stc.phase_out.nyc_school_tax_credit_with_phase_out.nyc_school_tax_credit_with_phase_out + input: + gov.contrib.local.nyc.stc.phase_out.in_effect: true + in_nyc: True + ny_agi: 150_000 + filing_status: SINGLE + nyc_school_tax_credit_fixed_amount: 1_000 + nyc_school_tax_credit_rate_reduction_amount: 500 + output: + nyc_school_tax_credit_phase_out: 3_750 + nyc_school_tax_credit: 0 + +- name: Single, with AGI above phase out threshold, with credit amount, partially reduced + period: 2025 + reforms: policyengine_us.reforms.local.nyc.stc.phase_out.nyc_school_tax_credit_with_phase_out.nyc_school_tax_credit_with_phase_out + input: + gov.contrib.local.nyc.stc.phase_out.in_effect: true + in_nyc: True + ny_agi: 160_000 + filing_status: SURVIVING_SPOUSE + nyc_school_tax_credit_fixed_amount: 1_000 + nyc_school_tax_credit_rate_reduction_amount: 600 + output: + nyc_school_tax_credit_phase_out: 500 + nyc_school_tax_credit: 1_100