diff --git a/changelog.d/7763.added.md b/changelog.d/7763.added.md new file mode 100644 index 00000000000..f1ade512119 --- /dev/null +++ b/changelog.d/7763.added.md @@ -0,0 +1 @@ +Added NJ StayNJ and ANCHOR budget housing reforms as separately toggleable contributed reforms. diff --git a/policyengine_us/parameters/gov/contrib/states/nj/anchor/homeowner/senior/amount/lower_income.yaml b/policyengine_us/parameters/gov/contrib/states/nj/anchor/homeowner/senior/amount/lower_income.yaml new file mode 100644 index 00000000000..ec145e1e1a5 --- /dev/null +++ b/policyengine_us/parameters/gov/contrib/states/nj/anchor/homeowner/senior/amount/lower_income.yaml @@ -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 diff --git a/policyengine_us/parameters/gov/contrib/states/nj/anchor/homeowner/senior/amount/upper_income.yaml b/policyengine_us/parameters/gov/contrib/states/nj/anchor/homeowner/senior/amount/upper_income.yaml new file mode 100644 index 00000000000..064355e4531 --- /dev/null +++ b/policyengine_us/parameters/gov/contrib/states/nj/anchor/homeowner/senior/amount/upper_income.yaml @@ -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 diff --git a/policyengine_us/parameters/gov/contrib/states/nj/anchor/in_effect.yaml b/policyengine_us/parameters/gov/contrib/states/nj/anchor/in_effect.yaml new file mode 100644 index 00000000000..cf768138fdb --- /dev/null +++ b/policyengine_us/parameters/gov/contrib/states/nj/anchor/in_effect.yaml @@ -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 diff --git a/policyengine_us/parameters/gov/contrib/states/nj/stay_nj/in_effect.yaml b/policyengine_us/parameters/gov/contrib/states/nj/stay_nj/in_effect.yaml new file mode 100644 index 00000000000..4e9933f33b6 --- /dev/null +++ b/policyengine_us/parameters/gov/contrib/states/nj/stay_nj/in_effect.yaml @@ -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 diff --git a/policyengine_us/parameters/gov/contrib/states/nj/stay_nj/income_limit.yaml b/policyengine_us/parameters/gov/contrib/states/nj/stay_nj/income_limit.yaml new file mode 100644 index 00000000000..ce623d66204 --- /dev/null +++ b/policyengine_us/parameters/gov/contrib/states/nj/stay_nj/income_limit.yaml @@ -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 diff --git a/policyengine_us/parameters/gov/contrib/states/nj/stay_nj/max_benefit.yaml b/policyengine_us/parameters/gov/contrib/states/nj/stay_nj/max_benefit.yaml new file mode 100644 index 00000000000..a42fe65d566 --- /dev/null +++ b/policyengine_us/parameters/gov/contrib/states/nj/stay_nj/max_benefit.yaml @@ -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 diff --git a/policyengine_us/reforms/reforms.py b/policyengine_us/reforms/reforms.py index 3070e9395e9..ac8e92d78ee 100644 --- a/policyengine_us/reforms/reforms.py +++ b/policyengine_us/reforms/reforms.py @@ -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 ( @@ -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, @@ -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)) diff --git a/policyengine_us/reforms/states/nj/__init__.py b/policyengine_us/reforms/states/nj/__init__.py new file mode 100644 index 00000000000..5a45d51f079 --- /dev/null +++ b/policyengine_us/reforms/states/nj/__init__.py @@ -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 diff --git a/policyengine_us/reforms/states/nj/anchor/__init__.py b/policyengine_us/reforms/states/nj/anchor/__init__.py new file mode 100644 index 00000000000..55d3f807527 --- /dev/null +++ b/policyengine_us/reforms/states/nj/anchor/__init__.py @@ -0,0 +1,4 @@ +from .nj_anchor_reform import ( + create_nj_anchor_reform, + nj_anchor_budget_reform, +) diff --git a/policyengine_us/reforms/states/nj/anchor/nj_anchor_reform.py b/policyengine_us/reforms/states/nj/anchor/nj_anchor_reform.py new file mode 100644 index 00000000000..b902ad78c32 --- /dev/null +++ b/policyengine_us/reforms/states/nj/anchor/nj_anchor_reform.py @@ -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) diff --git a/policyengine_us/reforms/states/nj/stay_nj/__init__.py b/policyengine_us/reforms/states/nj/stay_nj/__init__.py new file mode 100644 index 00000000000..dcfc82f1c51 --- /dev/null +++ b/policyengine_us/reforms/states/nj/stay_nj/__init__.py @@ -0,0 +1,4 @@ +from .nj_stay_nj_reform import ( + create_nj_stay_nj_reform, + nj_stay_nj_budget_reform, +) diff --git a/policyengine_us/reforms/states/nj/stay_nj/nj_stay_nj_reform.py b/policyengine_us/reforms/states/nj/stay_nj/nj_stay_nj_reform.py new file mode 100644 index 00000000000..4b52deabde0 --- /dev/null +++ b/policyengine_us/reforms/states/nj/stay_nj/nj_stay_nj_reform.py @@ -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) diff --git a/policyengine_us/tests/policy/contrib/states/nj/anchor/nj_anchor_budget_reform.yaml b/policyengine_us/tests/policy/contrib/states/nj/anchor/nj_anchor_budget_reform.yaml new file mode 100644 index 00000000000..95397cc131a --- /dev/null +++ b/policyengine_us/tests/policy/contrib/states/nj/anchor/nj_anchor_budget_reform.yaml @@ -0,0 +1,171 @@ +# ANCHOR Senior Homeowner Bonus Expiry Reform Tests +# Reform removes $250 senior homeowner bonus that was added for FY2024-FY2026 +# Senior homeowner amounts revert: $1,750 -> $1,500 (lower), $1,250 -> $1,000 (upper) +# All other amounts (non-senior homeowner, renter) are unchanged + +- name: Case 1, senior homeowner lower income gets $1,500 not $1,750. + period: 2027 + absolute_error_margin: 1 + reforms: policyengine_us.reforms.states.nj.anchor.nj_anchor_reform.nj_anchor_budget_reform + input: + gov.contrib.states.nj.anchor.in_effect: true + people: + person1: + age: 70 + employment_income: 100_000 + real_estate_taxes: 5_000 + is_tax_unit_head: true + tax_units: + tax_unit: + members: [person1] + spm_units: + spm_unit: + members: [person1] + spm_unit_tenure_type: OWNER_WITH_MORTGAGE + households: + household: + members: [person1] + state_code: NJ + output: + # Senior homeowner, income $100K <= $150K (lower tier) + # Reform amount: $1,500 (baseline with bonus would be $1,750) + nj_anchor: 1_500 + +- name: Case 2, senior homeowner upper income gets $1,000 not $1,250. + period: 2027 + absolute_error_margin: 1 + reforms: policyengine_us.reforms.states.nj.anchor.nj_anchor_reform.nj_anchor_budget_reform + input: + gov.contrib.states.nj.anchor.in_effect: true + people: + person1: + age: 70 + employment_income: 200_000 + real_estate_taxes: 5_000 + is_tax_unit_head: true + tax_units: + tax_unit: + members: [person1] + spm_units: + spm_unit: + members: [person1] + spm_unit_tenure_type: OWNER_WITHOUT_MORTGAGE + households: + household: + members: [person1] + state_code: NJ + output: + # Senior homeowner, income $200K > $150K but <= $250K (upper tier) + # Reform amount: $1,000 (baseline with bonus would be $1,250) + nj_anchor: 1_000 + +- name: Case 3, senior renter unchanged at $700. + period: 2027 + absolute_error_margin: 1 + reforms: policyengine_us.reforms.states.nj.anchor.nj_anchor_reform.nj_anchor_budget_reform + input: + gov.contrib.states.nj.anchor.in_effect: true + people: + person1: + age: 70 + employment_income: 100_000 + rent: 18_000 + is_tax_unit_head: true + tax_units: + tax_unit: + members: [person1] + spm_units: + spm_unit: + members: [person1] + spm_unit_tenure_type: RENTER + households: + household: + members: [person1] + state_code: NJ + output: + # Senior renter, income $100K <= $150K + # Reform does NOT affect renter amounts - still $700 + nj_anchor: 700 + +- name: Case 4, non-senior homeowner lower income unchanged at $1,500. + period: 2027 + absolute_error_margin: 1 + reforms: policyengine_us.reforms.states.nj.anchor.nj_anchor_reform.nj_anchor_budget_reform + input: + gov.contrib.states.nj.anchor.in_effect: true + people: + person1: + age: 45 + employment_income: 100_000 + real_estate_taxes: 5_000 + is_tax_unit_head: true + tax_units: + tax_unit: + members: [person1] + spm_units: + spm_unit: + members: [person1] + spm_unit_tenure_type: OWNER_WITH_MORTGAGE + households: + household: + members: [person1] + state_code: NJ + output: + # Non-senior homeowner, income $100K <= $150K (lower tier) + # Reform does NOT affect non-senior amounts - still $1,500 + nj_anchor: 1_500 + +- name: Case 5, non-senior renter unchanged at $450. + period: 2027 + absolute_error_margin: 1 + reforms: policyengine_us.reforms.states.nj.anchor.nj_anchor_reform.nj_anchor_budget_reform + input: + gov.contrib.states.nj.anchor.in_effect: true + people: + person1: + age: 45 + employment_income: 100_000 + rent: 18_000 + is_tax_unit_head: true + tax_units: + tax_unit: + members: [person1] + spm_units: + spm_unit: + members: [person1] + spm_unit_tenure_type: RENTER + households: + household: + members: [person1] + state_code: NJ + output: + # Non-senior renter, income $100K <= $150K + # Reform does NOT affect renter amounts - still $450 + nj_anchor: 450 + +- name: Case 6, senior homeowner over income limit is ineligible. + period: 2027 + absolute_error_margin: 1 + reforms: policyengine_us.reforms.states.nj.anchor.nj_anchor_reform.nj_anchor_budget_reform + input: + gov.contrib.states.nj.anchor.in_effect: true + people: + person1: + age: 70 + employment_income: 260_000 + real_estate_taxes: 5_000 + is_tax_unit_head: true + tax_units: + tax_unit: + members: [person1] + spm_units: + spm_unit: + members: [person1] + spm_unit_tenure_type: OWNER_WITH_MORTGAGE + households: + household: + members: [person1] + state_code: NJ + output: + # Income $260K > $250K homeowner upper limit - ineligible + nj_anchor: 0 diff --git a/policyengine_us/tests/policy/contrib/states/nj/stay_nj/nj_stay_nj_budget_reform.yaml b/policyengine_us/tests/policy/contrib/states/nj/stay_nj/nj_stay_nj_budget_reform.yaml new file mode 100644 index 00000000000..aca9f5ec64c --- /dev/null +++ b/policyengine_us/tests/policy/contrib/states/nj/stay_nj/nj_stay_nj_budget_reform.yaml @@ -0,0 +1,178 @@ +# StayNJ Budget Reform Tests +# Reform reduces income limit from $500K to $250K and max benefit from $6,500 to $4,000 +# Formula: StayNJ = max(min(property_taxes * 50%, max_benefit) - ANCHOR - Senior_Freeze, 0) +# Eligibility: age 65+, homeowner, income < $250K (reform limit) + +- name: Case 1, eligible senior homeowner under new $250K income limit. + period: 2027 + absolute_error_margin: 1 + reforms: policyengine_us.reforms.states.nj.stay_nj.nj_stay_nj_reform.nj_stay_nj_budget_reform + input: + gov.contrib.states.nj.stay_nj.in_effect: true + people: + person1: + age: 70 + employment_income: 200_000 + real_estate_taxes: 10_000 + is_tax_unit_head: true + tax_units: + tax_unit: + members: [person1] + spm_units: + spm_unit: + members: [person1] + spm_unit_tenure_type: OWNER_WITH_MORTGAGE + households: + household: + members: [person1] + state_code: NJ + output: + # Income $200K < $250K reform limit - eligible + # min($10,000 * 50%, $4,000) = min($5,000, $4,000) = $4,000 + # ANCHOR = $1,250 (senior homeowner, income $150K-$250K, post-bonus: $1,000) + # But ANCHOR reform is NOT active here, so baseline ANCHOR = $1,250 + # max($4,000 - $1,250 - $0, 0) = $2,750 + nj_staynj: 2_750 + +- name: Case 2, ineligible under new $250K limit but would be eligible under baseline $500K. + period: 2027 + absolute_error_margin: 1 + reforms: policyengine_us.reforms.states.nj.stay_nj.nj_stay_nj_reform.nj_stay_nj_budget_reform + input: + gov.contrib.states.nj.stay_nj.in_effect: true + people: + person1: + age: 70 + employment_income: 300_000 + real_estate_taxes: 15_000 + is_tax_unit_head: true + tax_units: + tax_unit: + members: [person1] + spm_units: + spm_unit: + members: [person1] + spm_unit_tenure_type: OWNER_WITH_MORTGAGE + households: + household: + members: [person1] + state_code: NJ + output: + # Income $300K >= $250K reform limit - ineligible + # (Would be eligible under baseline $500K limit) + nj_staynj_eligible: false + nj_staynj: 0 + +- name: Case 3, max benefit capped at $4,000 with high property taxes. + period: 2027 + absolute_error_margin: 1 + reforms: policyengine_us.reforms.states.nj.stay_nj.nj_stay_nj_reform.nj_stay_nj_budget_reform + input: + gov.contrib.states.nj.stay_nj.in_effect: true + people: + person1: + age: 68 + employment_income: 100_000 + real_estate_taxes: 20_000 + is_tax_unit_head: true + tax_units: + tax_unit: + members: [person1] + spm_units: + spm_unit: + members: [person1] + spm_unit_tenure_type: OWNER_WITH_MORTGAGE + households: + household: + members: [person1] + state_code: NJ + output: + # min($20,000 * 50%, $4,000) = min($10,000, $4,000) = $4,000 (capped at reform max) + # Baseline max would be $6,500 - reform caps at $4,000 + # ANCHOR = $1,750 (senior homeowner, income <= $150K) + # max($4,000 - $1,750 - $0, 0) = $2,250 + nj_staynj: 2_250 + +- name: Case 4, senior renter ineligible for StayNJ. + period: 2027 + absolute_error_margin: 1 + reforms: policyengine_us.reforms.states.nj.stay_nj.nj_stay_nj_reform.nj_stay_nj_budget_reform + input: + gov.contrib.states.nj.stay_nj.in_effect: true + people: + person1: + age: 70 + employment_income: 100_000 + rent: 18_000 + is_tax_unit_head: true + tax_units: + tax_unit: + members: [person1] + spm_units: + spm_unit: + members: [person1] + spm_unit_tenure_type: RENTER + households: + household: + members: [person1] + state_code: NJ + output: + # Renter - not homeowner - ineligible for StayNJ + nj_staynj_eligible: false + nj_staynj: 0 + +- name: Case 5, under-65 homeowner ineligible for StayNJ. + period: 2027 + absolute_error_margin: 1 + reforms: policyengine_us.reforms.states.nj.stay_nj.nj_stay_nj_reform.nj_stay_nj_budget_reform + input: + gov.contrib.states.nj.stay_nj.in_effect: true + people: + person1: + age: 60 + employment_income: 100_000 + real_estate_taxes: 10_000 + is_tax_unit_head: true + tax_units: + tax_unit: + members: [person1] + spm_units: + spm_unit: + members: [person1] + spm_unit_tenure_type: OWNER_WITH_MORTGAGE + households: + household: + members: [person1] + state_code: NJ + output: + # Age 60 < 65 - ineligible for StayNJ + nj_staynj_eligible: false + nj_staynj: 0 + +- name: Case 6, income exactly at $250K reform threshold is ineligible. + period: 2027 + absolute_error_margin: 1 + reforms: policyengine_us.reforms.states.nj.stay_nj.nj_stay_nj_reform.nj_stay_nj_budget_reform + input: + gov.contrib.states.nj.stay_nj.in_effect: true + people: + person1: + age: 70 + employment_income: 250_000 + real_estate_taxes: 10_000 + is_tax_unit_head: true + tax_units: + tax_unit: + members: [person1] + spm_units: + spm_unit: + members: [person1] + spm_unit_tenure_type: OWNER_WITH_MORTGAGE + households: + household: + members: [person1] + state_code: NJ + output: + # Income $250K NOT < $250K (strict less than) - ineligible under reform + nj_staynj_eligible: false + nj_staynj: 0