Skip to content

Update New Jersey Stay NJ property tax relief baseline#8734

Open
daphnehanse11 wants to merge 5 commits into
PolicyEngine:mainfrom
daphnehanse11:codex/nj-pas1-property-tax-relief-income
Open

Update New Jersey Stay NJ property tax relief baseline#8734
daphnehanse11 wants to merge 5 commits into
PolicyEngine:mainfrom
daphnehanse11:codex/nj-pas1-property-tax-relief-income

Conversation

@daphnehanse11

@daphnehanse11 daphnehanse11 commented Jun 23, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Add a dedicated New Jersey property tax relief application income variable based on PAS-1 instructions for Stay NJ eligibility.
  • Cap homeowner ANCHOR benefits by property taxes paid and apply a capped Senior Freeze offset in Stay NJ/property tax relief calculations.
  • Add combined New Jersey property tax relief benefits to household state benefits beginning in 2026.
  • Clean up New Jersey property tax deduction and credit references.

Closes #8733.

Tests

  • uv run python -m policyengine_core.scripts.policyengine_command test policyengine_us/tests/policy/baseline/gov/states/nj/tax/income/credits/anchor/nj_anchor.yaml -c policyengine_us
  • uv run python -m policyengine_core.scripts.policyengine_command test policyengine_us/tests/policy/baseline/gov/states/nj/tax/income/credits/staynj/nj_staynj.yaml policyengine_us/tests/policy/baseline/gov/states/nj/tax/income/credits/staynj/nj_capped_senior_freeze.yaml policyengine_us/tests/policy/baseline/gov/states/nj/tax/income/credits/property_tax_relief/nj_property_tax_relief.yaml policyengine_us/tests/policy/baseline/gov/states/nj/tax/income/credits/property_tax_relief/nj_property_tax_relief_income.yaml policyengine_us/tests/policy/baseline/gov/states/nj/tax/income/credits/staynj/nj_staynj_eligible.yaml -c policyengine_us
  • uv run python -m policyengine_core.scripts.policyengine_command test policyengine_us/tests/policy/baseline/gov/states/nj/tax/income/credits/property_tax_credit/nj_property_tax_credit.yaml policyengine_us/tests/policy/baseline/gov/states/nj/tax/income/credits/property_tax_credit/nj_property_tax_credit_eligible.yaml policyengine_us/tests/policy/baseline/gov/states/nj/tax/income/deductions/property_tax/nj_potential_property_tax_deduction.yaml -c policyengine_us
  • uv run pytest policyengine_us/tests/test_parameter_files.py
  • git diff --check

@codecov

codecov Bot commented Jun 23, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 81.59%. Comparing base (cf06458) to head (ddba885).
⚠️ Report is 14 commits behind head on main.

Additional details and impacted files
@@             Coverage Diff              @@
##              main    #8734       +/-   ##
============================================
- Coverage   100.00%   81.59%   -18.41%     
============================================
  Files            5        9        +4     
  Lines           73      163       +90     
============================================
+ Hits            73      133       +60     
- Misses           0       30       +30     
Flag Coverage Δ
unittests 81.59% <100.00%> (-18.41%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@daphnehanse11 daphnehanse11 changed the title Add New Jersey PAS-1 property tax relief income Update New Jersey Stay NJ property tax relief baseline Jun 23, 2026
@daphnehanse11 daphnehanse11 marked this pull request as ready for review June 24, 2026 18:30
@daphnehanse11 daphnehanse11 requested a review from DTrim99 June 24, 2026 18:31
@DTrim99

DTrim99 commented Jun 24, 2026

Copy link
Copy Markdown
Collaborator

Program Review — PR #8734 (NJ Stay NJ / ANCHOR / Senior Freeze property tax relief)

New Jersey unifies Stay NJ, ANCHOR, and Senior Freeze under a new nj_property_tax_relief total, adds a PAS-1 application-income definition (nj_property_tax_relief_income), caps ANCHOR by property taxes paid, introduces a capped Senior Freeze offset feeding Stay NJ, and cleans up reference tuples. This review consolidates code-pattern, test-coverage, PDF/income, code-path, and context analyses.

Source Documents


Critical (Must Fix)

C1 — Ungated income formula reads a 2025-only list parameter → TypeError crash for any pre-2025 simulation.
nj_property_tax_relief_income.py defines an all-years formula that resolves additional_sources = add(tax_unit, period, p.additional_sources), but additional_sources.yaml has its first value dated 2025-01-01. Before that date Parameter._get_at_instant returns None (policyengine-core parameters/parameter.py:223-227), and add(tax_unit, period, None) raises TypeError: 'NoneType' is not iterable. The chain nj_staynj_eligible (defined_for=StateCode.NJ, all-years formula) → nj_property_tax_relief_incomep.additional_sources means any NJ household or microsimulation evaluated for a pre-2025 year (e.g. a 2024 web-app run) that reaches nj_staynj_eligible will crash. CI is green only because every test and contrib reform runs ≥ 2026, so the pre-2025 path is never exercised — the bug is masked, not absent. (This single finding was reported independently as code C1, regulatory S2, and context; merged here.)

  • Fix (any one): (a) gate the formula to formula_2026 to align with nj_property_tax_relief.formula_2026 and the 2026 household_state_benefits integration (preferred — a 2025-only income def with no 2025 consumer is dead surface area); or (b) add a base empty-list entry to additional_sources.yaml:
    values:
      2021-01-01: []
      2025-01-01:
        - tax_exempt_interest_income
        - tax_exempt_ira_distributions
        - social_security
  • Regression test that would have caught it: a period: 2024 case asserting nj_property_tax_relief_income (or nj_staynj_eligible) computes without error for an NJ homeowner. No sub-2025 coverage exists today.

C2 — Broken HTML anchor #PAS1IncomeCalculation; correct id is collapsePAS.
The reference anchor https://www.nj.gov/treasury/taxation/relief.shtml#PAS1IncomeCalculation does not exist on the NJ Treasury page; the actual collapsible section id is collapsePAS. Appears in additional_sources.yaml and in nj_property_tax_relief_income.py. Update both to ...relief.shtml#collapsePAS (or the canonical PAS-1 PDF #page=9 link).


Should Address

S1 — Missing session-law / statute citations on new variables. nj_property_tax_relief.py and nj_capped_senior_freeze.py omit the Stay NJ session law (P.L. 2023 c.75 / P.L. 2024 c.88) and the PTR/Senior Freeze statute (N.J.S.A. 54:4-8.67 et seq.). Add these to the variables' reference tuples.

S2 — Retroactive ANCHOR cap is a baseline change for prior years (2022–2025). Capping ANCHOR by property taxes paid changes baseline outputs for 2022–2025, not just going forward. Note for reviewers: the "Household API Partners" CI job passed, which empirically clears the partner-test risk for this baseline change — but the prior-year behavior change should be acknowledged in the PR description.

S3 — Test coverage gaps (the PAS-1 income logic is the weakest area):

  • PAS-1 income — no per-source isolation. nj_property_tax_relief_income.yaml Case 1 lumps all four additions (tax-exempt interest, tax-exempt IRA, Social Security, pre-FRA disability) into one assertion; an implementation that drops one source and over-counts another would net out and pass. Add one case per source.
  • SS-only case not isolated (explicitly requested; SS is the most consequential addition since NJ normally excludes it from gross income).
  • FRA inclusion side tested only bundled in Case 1 (age 60); add an isolated under-FRA "disability IS added" case.
  • Senior-Freeze full-absorption (residual 0) is conflated with the property-tax cap in Case 2 (property taxes set to 2,000 so the cap, not the offset, zeroes Stay NJ). Add a case with large property taxes (cap non-binding) where ANCHOR + a large uncapped Senior Freeze drive the residual to 0.
  • ANCHOR cap non-binding direction not asserted by a dedicated case (only the binding direction, Edge case 17, is tested).
  • Stay NJ income limit just-below-eligible using the new income var — only the above-limit (ineligible) side (Edge case 11) exists; add a just-below case where SS inclusion keeps the household eligible, pinning the strict-< 500,000 boundary.
  • Age-65 boundary combined with the new income variable not added (low risk; age logic unchanged).

S4 — Param description verb is non-standard. additional_sources.yaml line 1 uses "Additional income sources included in…"; the parameter-patterns skill restricts verbs to limits/provides/sets/excludes/deducts/uses. Suggest: "New Jersey uses these additional income sources when calculating property tax relief application income under the property tax relief program."


Suggestions

  • Roth IRA / line c naming. The PR uses tax_exempt_ira_distributions for PAS-1 line c (qualified Roth distributions). Its documented scope is qualifying-Roth-only and aligns with the form, but the variable name is broader than the form line and it omits the "untaxed Roth-to-Roth rollover" piece line c also calls for. Confirm it never silently includes non-Roth tax-exempt IRA amounts.
  • Self-documenting combined cap. A single explicit min_(anchor + senior_freeze + staynj, property_taxes) would document the combined-at-property-taxes cap more clearly than the current staged offset arithmetic.
  • Disability-source comment. Add a comment noting total_disability_payments excludes VA/military disability, since the pre-FRA disability term is load-bearing for eligibility.
  • nj_property_tax_relief could use adds = [...] instead of a formula for a pure sum, but formula_2026 gating justifies the formula; acceptable as-is.

PDF / Income Audit Summary

nj_property_tax_relief_income maps 1:1 to PAS-1 lines a–e on #page=9 with no double-counting:

PAS-1 line Form definition PR source Verdict
a NJ-1040 line 27 (NJ gross income) nj_gross_income Match
b Tax-exempt interest (line 16b) tax_exempt_interest_income Match
c Qualified Roth IRA distribution / untaxed Roth-to-Roth rollover tax_exempt_ira_distributions Aligns (name broader; rollover piece omitted — low)
d Total & permanent disability pension before conversion age (not on NJ-1040) total_disability_payments gated age < ss_full_retirement_age Match (pre-FRA restriction correct per #page=9)
e Social Security incl. Medicare Part B (SSA-1099 Box 5) social_security Match (gross; see Investigated & cleared)
f Sum a–e formula total Match

No double-counting: nj_gross_income carries the taxable interest/IRA counterparts; additional_sources adds the tax-exempt ones plus social_security (excluded from NJ gross income). total_disability_payments is absent from gross income. No extra sources are included beyond what the form calls for. The Income Calculation Worksheet path (#page=13) for line a is an edge case and not a defect.


Validation Summary

Area Status Notes
Hard-coded values Pass ANCHOR cap is min_(amount, property_taxes); no magic numbers
add() / min_ / max_ / where Pass Used correctly throughout
Entity levels Pass TaxUnit relief vars aggregate to Household; person reads summed correctly
Period access Pass All YEAR formulas; no /12 misuse
Double-counting (PAS-1 income) Pass No overlap between gross income and additional sources
Offset ordering ANCHOR→SF→Stay NJ Pass Correct waterfall; combined cap at property taxes
Reference tuples Pass (bug fix) Comma fixes prevent silent string concatenation into one URL
Duplicate variables Pass No reinvented variables (grep-confirmed)
Changelog Pass changelog.d/nj-staynj-property-tax-relief.changed.md (patch)
Era gating Fail (C1) All-years income formula vs 2025-only param
Reference anchor Fail (C2) #PAS1IncomeCalculation#collapsePAS
Citations Should (S1) Stay NJ / PTR statute missing on new vars
Test isolation Should (S3) PAS-1 per-source, FRA inclusion, boundaries
CI Non-blocking Only codecov/project (coverage) fails; all functional suites + Household API Partners pass

Investigated & cleared (not findings)

  • Social Security gross vs net of Medicare Part B — REJECTED false positive. social_security is a pure adds sum of social_security_{dependents,disability,retirement,survivors} (bare input variables, no formula, no subtracts). medicare_part_b_premiums_reported is a separate household health-expense input and is never netted out. The variable already equals the gross SSA-1099 Box 5 amount, matching PAS-1 line e. No Part B add-back needed.
  • #page=9 PAS-1 anchor — CORRECT. The pdf-collector confirmed lines 17a–18f render on PDF file page 9 (cover offset +2). The "page 11" some sources mention is the worksheets on PDF page 13. Do not treat #page=9 as wrong.
  • Retroactive ANCHOR cap partner-test risk — cleared empirically. The "Household API Partners" job passed in CI; no partner contract test files (tests/policy/baseline/partners/**) were changed.

Review Severity: REQUEST_CHANGES

Two must-fix items (one latent crash, one broken anchor) block merge; the income definition and offset logic are otherwise substantively correct.

Next Steps

Run /fix-pr 8734 to apply the Critical fixes (gate the income formula and correct the HTML anchor), then address the Should-level citation and test-coverage items.


🤖 Generated with Claude Code

@DTrim99

DTrim99 commented Jun 25, 2026

Copy link
Copy Markdown
Collaborator

Re-review — PR #8734 (NJ Stay NJ / ANCHOR / Senior Freeze) — updated after fix commit ddba885

Both blocking criticals from the prior review are resolved.

🔴 Criticals — both RESOLVED

Prior critical Status
Pre-2025 TypeError (ungated income formula reading a 2025-only list param) ✅ Fixed — nj_property_tax_relief_income is now formula_2026, so it's never evaluated before its param exists; pre-2026 inactivity is now tested
Broken HTML anchor #PAS1IncomeCalculation ✅ Fixed — now #collapsePAS in both additional_sources.yaml and nj_property_tax_relief_income.py

🟡 Should — mostly addressed

  • Citations (partial): nj_property_tax_relief.py / nj_capped_senior_freeze.py now cite P.L. 2023 c.75 (Stay NJ) and the PTR page. The literal permanent-statute string N.J.S.A. 54:4-8.67 is still absent — minor nit.
  • Test coverage (substantially added): per-source income isolation (tax-exempt interest, tax-exempt IRA, Social Security, pre-FRA disability), pre-2026 inactivity, age-65 just-below-limit eligibility, and the ANCHOR-cap non-binding direction.

🟢 Suggestions (optional)

  • additional_sources.yaml base date is 2025-01-01 while the consumer is gated to 2026 — harmless (never read before 2026), but a 2026-01-01 base or a comment would make intent explicit.
  • One remaining test gap: a Senior-Freeze full-absorption case isolated from the property-tax cap.
  • Roth IRA line-c naming is broader than the form line; an explicit combined cap min_(anchor + sf + staynj, property_taxes) would be more self-documenting; a comment noting total_disability_payments excludes VA/military disability.

Confirmed still correct

Offset ordering (ANCHOR → Senior Freeze → Stay NJ residual), combined property-tax cap, ANCHOR homeowner cap, and the PAS-1 income definition (unchanged this update; maps 1:1 to lines a–e, SS is gross Box 5, no double-counting).

Verification

  • NJ credit tests: 170 passed, 0 failed locally; CI states-non-ny shards green. Only codecov/project (coverage) is red — non-blocking.

Updated severity: APPROVE

Both blocking criticals are fixed and tests pass. Remaining items are a minor citation nit and optional clarity/test suggestions — none blocking.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Encode New Jersey PAS-1 income for property tax relief

2 participants