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/7763.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added NJ StayNJ and ANCHOR budget housing reforms as separately toggleable contributed reforms.
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
description: >-
New Jersey reduces the ANCHOR senior homeowner lower-income benefit to this amount
after the $250 bonus expires following FY2026.
values:
2027-01-01: 1_500

metadata:
unit: currency-USD
period: year
label: NJ ANCHOR senior homeowner lower-income amount (post-bonus)
reference:
- title: P.L. 2023, c.75, Section 15 - ANCHOR Senior Bonus (sunset FY2026)
href: https://pub.njleg.state.nj.us/Bills/2022/PL23/75_.PDF#page=11
- title: ANCHOR Program - How Benefits Are Calculated
href: https://www.nj.gov/treasury/taxation/anchor/calculated.shtml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
description: >-
New Jersey reduces the ANCHOR senior homeowner upper-income benefit to this amount
after the $250 bonus expires following FY2026.
values:
2027-01-01: 1_000

metadata:
unit: currency-USD
period: year
label: NJ ANCHOR senior homeowner upper-income amount (post-bonus)
reference:
- title: P.L. 2023, c.75, Section 15 - ANCHOR Senior Bonus (sunset FY2026)
href: https://pub.njleg.state.nj.us/Bills/2022/PL23/75_.PDF#page=11
- title: ANCHOR Program - How Benefits Are Calculated
href: https://www.nj.gov/treasury/taxation/anchor/calculated.shtml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
description: >-
New Jersey ANCHOR senior homeowner bonus expiry reform applies if this is true.
values:
0000-01-01: false

metadata:
unit: bool
period: year
label: NJ ANCHOR senior bonus expiry in effect
reference:
- title: P.L. 2023, c.75, Section 15 - ANCHOR Senior Bonus (sunset FY2026)
href: https://pub.njleg.state.nj.us/Bills/2022/PL23/75_.PDF#page=11
- title: NJ FY2027 Budget in Brief - ANCHOR Senior Renter Bonus Extension
href: https://www.nj.gov/treasury/omb/publications/27bib/BIB.pdf#page=18
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
description: >-
New Jersey StayNJ budget reform applies if this is true.
values:
0000-01-01: false

metadata:
unit: bool
period: year
label: NJ StayNJ budget reform in effect
reference:
- title: NJ FY2027 Budget in Brief - Property Tax Relief
href: https://www.nj.gov/treasury/omb/publications/27bib/BIB.pdf#page=17
- title: Governor Sherrill Presents Fiscal Year 2027 Budget
href: https://www.nj.gov/governor/news/2026/20260310b.shtml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
description: >-
New Jersey reduces the StayNJ income threshold to this amount under the
Governor's FY2027 budget proposal.
values:
2027-01-01: 250_000

metadata:
unit: currency-USD
period: year
label: NJ StayNJ budget reform income limit
reference:
- title: NJ FY2027 Budget in Brief - Property Tax Relief
href: https://www.nj.gov/treasury/omb/publications/27bib/BIB.pdf#page=17
- title: P.L. 2023, c.75 - Stay NJ Act
href: https://pub.njleg.state.nj.us/Bills/2022/PL23/75_.PDF#page=1
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
description: >-
New Jersey caps the StayNJ maximum benefit at this amount under the
Governor's FY2027 budget proposal.
values:
2027-01-01: 4_000

metadata:
unit: currency-USD
period: year
label: NJ StayNJ budget reform max benefit
reference:
- title: NJ FY2027 Budget in Brief - Property Tax Relief
href: https://www.nj.gov/treasury/omb/publications/27bib/BIB.pdf#page=17
- title: P.L. 2023, c.75 - Stay NJ Act
href: https://pub.njleg.state.nj.us/Bills/2022/PL23/75_.PDF#page=1
10 changes: 10 additions & 0 deletions policyengine_us/reforms/reforms.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,12 @@
from .congress.watca import (
create_watca_reform,
)
from .states.nj.stay_nj import (
create_nj_stay_nj_reform,
)
from .states.nj.anchor import (
create_nj_anchor_reform,
)


from .states.ga.sb520 import (
Expand Down Expand Up @@ -346,6 +352,8 @@ def create_structural_reforms_from_parameters(parameters, period):
ct_hb5009 = create_ct_hb5009_reform(parameters, period)
ga_sb520 = create_ga_sb520_reform(parameters, period)
watca = create_watca_reform(parameters, period)
nj_stay_nj = create_nj_stay_nj_reform(parameters, period)
nj_anchor = create_nj_anchor_reform(parameters, period)

reforms = [
afa_reform,
Expand Down Expand Up @@ -428,6 +436,8 @@ def create_structural_reforms_from_parameters(parameters, period):
ct_tax_rebate_2026,
ga_sb520,
watca,
nj_stay_nj,
nj_anchor,
]
reforms = tuple(filter(lambda x: x is not None, reforms))

Expand Down
2 changes: 2 additions & 0 deletions policyengine_us/reforms/states/nj/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from .stay_nj import create_nj_stay_nj_reform, nj_stay_nj_budget_reform
from .anchor import create_nj_anchor_reform, nj_anchor_budget_reform
4 changes: 4 additions & 0 deletions policyengine_us/reforms/states/nj/anchor/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from .nj_anchor_reform import (
create_nj_anchor_reform,
nj_anchor_budget_reform,
)
107 changes: 107 additions & 0 deletions policyengine_us/reforms/states/nj/anchor/nj_anchor_reform.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
from policyengine_us.model_api import *
from policyengine_core.periods import period as period_


def create_nj_anchor() -> Reform:
class nj_anchor(Variable):
value_type = float
entity = TaxUnit
label = "New Jersey ANCHOR benefit"
unit = USD
definition_period = YEAR
reference = (
"https://www.nj.gov/treasury/taxation/anchor/",
"https://www.nj.gov/treasury/taxation/anchor/calculated.shtml",
)
defined_for = "nj_anchor_eligible"

def formula(tax_unit, period, parameters):
p_baseline = parameters(period).gov.states.nj.tax.income.credits.anchor
p_reform = parameters(period).gov.contrib.states.nj.anchor

reform_active = p_reform.in_effect

gross_income = add(tax_unit, period, ["nj_gross_income"])
greater_age = tax_unit("greater_age_head_spouse", period)
is_senior = greater_age >= p_baseline.age_threshold

pays_property_taxes = add(tax_unit, period, ["real_estate_taxes"]) > 0
pays_rent = tax_unit("rents", period)
is_homeowner = pays_property_taxes & ~pays_rent
is_renter = pays_rent & ~pays_property_taxes

lower_income = gross_income <= p_baseline.homeowner.income_limit.lower

# Senior homeowner amounts: reform overrides when active
baseline_senior_lower = p_baseline.homeowner.senior.amount.lower_income
baseline_senior_upper = p_baseline.homeowner.senior.amount.upper_income
reform_senior_lower = p_reform.homeowner.senior.amount.lower_income
reform_senior_upper = p_reform.homeowner.senior.amount.upper_income

senior_lower = where(
reform_active,
reform_senior_lower,
baseline_senior_lower,
)
senior_upper = where(
reform_active,
reform_senior_upper,
baseline_senior_upper,
)

homeowner_senior_amount = where(lower_income, senior_lower, senior_upper)

# Non-senior homeowner amounts: unchanged from baseline
homeowner_non_senior_amount = where(
lower_income,
p_baseline.homeowner.non_senior.amount.lower_income,
p_baseline.homeowner.non_senior.amount.upper_income,
)
homeowner_amount = where(
is_senior,
homeowner_senior_amount,
homeowner_non_senior_amount,
)

# Renter amounts: unchanged from baseline
renter_amount = where(
is_senior,
p_baseline.renter.senior.amount,
p_baseline.renter.non_senior.amount,
)

return where(
is_homeowner,
homeowner_amount,
where(is_renter, renter_amount, 0),
)

class reform(Reform):
def apply(self):
self.update_variable(nj_anchor)

return reform


def create_nj_anchor_reform(parameters, period, bypass: bool = False):
if bypass:
return create_nj_anchor()

p = parameters.gov.contrib.states.nj.anchor

reform_active = False
current_period = period_(period)

for _ 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_nj_anchor()
else:
return None


nj_anchor_budget_reform = create_nj_anchor_reform(None, None, bypass=True)
4 changes: 4 additions & 0 deletions policyengine_us/reforms/states/nj/stay_nj/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from .nj_stay_nj_reform import (
create_nj_stay_nj_reform,
nj_stay_nj_budget_reform,
)
102 changes: 102 additions & 0 deletions policyengine_us/reforms/states/nj/stay_nj/nj_stay_nj_reform.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
from policyengine_us.model_api import *
from policyengine_core.periods import period as period_


def create_nj_stay_nj() -> Reform:
class nj_staynj_eligible(Variable):
value_type = bool
entity = TaxUnit
label = "New Jersey Stay NJ Property Tax Credit program eligibility"
definition_period = YEAR
reference = (
"https://pub.njleg.state.nj.us/Bills/2022/PL23/75_.HTM",
"https://www.nj.gov/treasury/taxation/staynj/index.shtml",
)
defined_for = StateCode.NJ

def formula(tax_unit, period, parameters):
p_baseline = parameters(period).gov.states.nj.tax.income.credits.staynj
p_reform = parameters(period).gov.contrib.states.nj.stay_nj

reform_active = p_reform.in_effect

greater_age = tax_unit("greater_age_head_spouse", period)
age_eligible = greater_age >= p_baseline.age_threshold

gross_income = add(tax_unit, period, ["nj_gross_income"])
baseline_income_eligible = gross_income < p_baseline.income_limit
reform_income_eligible = gross_income < p_reform.income_limit
income_eligible = where(
reform_active,
reform_income_eligible,
baseline_income_eligible,
)

pays_property_taxes = add(tax_unit, period, ["real_estate_taxes"]) > 0
pays_rent = tax_unit("rents", period)
is_homeowner = pays_property_taxes & ~pays_rent

return age_eligible & income_eligible & is_homeowner

class nj_staynj(Variable):
value_type = float
entity = TaxUnit
label = "New Jersey Stay NJ Property Tax Credit"
unit = USD
definition_period = YEAR
reference = (
"https://pub.njleg.state.nj.us/Bills/2022/PL23/75_.HTM",
"https://www.nj.gov/treasury/taxation/staynj/index.shtml",
)
defined_for = "nj_staynj_eligible"

def formula(tax_unit, period, parameters):
p_baseline = parameters(period).gov.states.nj.tax.income.credits.staynj
p_reform = parameters(period).gov.contrib.states.nj.stay_nj

reform_active = p_reform.in_effect

property_taxes = add(tax_unit, period, ["real_estate_taxes"])

max_benefit = where(
reform_active,
p_reform.max_benefit,
p_baseline.max_benefit,
)
target_benefit = min_(property_taxes * p_baseline.rate, max_benefit)

anchor_benefit = tax_unit("nj_anchor", period)
senior_freeze = tax_unit("nj_senior_freeze", period)

return max_(target_benefit - anchor_benefit - senior_freeze, 0)

class reform(Reform):
def apply(self):
self.update_variable(nj_staynj_eligible)
self.update_variable(nj_staynj)

return reform


def create_nj_stay_nj_reform(parameters, period, bypass: bool = False):
if bypass:
return create_nj_stay_nj()

p = parameters.gov.contrib.states.nj.stay_nj

reform_active = False
current_period = period_(period)

for _ 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_nj_stay_nj()
else:
return None


nj_stay_nj_budget_reform = create_nj_stay_nj_reform(None, None, bypass=True)
Loading
Loading