Skip to content

Comments

fix: implement NJ same-category loss rule for gross income#7280

Open
MaxGhenis wants to merge 12 commits intomainfrom
fix/nj-same-category-loss-rule
Open

fix: implement NJ same-category loss rule for gross income#7280
MaxGhenis wants to merge 12 commits intomainfrom
fix/nj-same-category-loss-rule

Conversation

@MaxGhenis
Copy link
Contributor

Summary

  • Implements New Jersey's "same category rule" (N.J.S. 54A:5-1) for gross income calculation
  • Under this rule, if any income category has a net loss, that loss is disregarded (treated as $0) and cannot offset income from other categories
  • Applies max_(0, category_total) to capital gains, partnership/S-corp income, self-employment income, farm income, and rental income

Fixes #7017

Test plan

  • Added YAML tests covering the same-category loss rule scenarios
  • Verified existing NJ integration tests still pass
  • Ran make format to ensure code style compliance

🤖 Generated with Claude Code

Fixes #7017

Under NJ's "same category rule" (N.J.S. 54A:5-1), if any of the income
categories has a net loss, that loss must be disregarded (treated as $0)
and cannot offset income from other categories.

This change applies max_(0, category_total) to:
- Capital gains (short-term + long-term combined)
- Partnership/S-corp income
- Self-employment income
- Farm income
- Rental income

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@codecov
Copy link

codecov bot commented Jan 30, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (f073b57) to head (580c0a8).
⚠️ Report is 102 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff            @@
##              main     #7280   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files            4         1    -3     
  Lines           76        16   -60     
  Branches         1         1           
=========================================
- Hits            76        16   -60     
Flag Coverage Δ
unittests 100.00% <100.00%> (ø)

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

☔ View full report in Codecov by Sentry.
📢 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.

MaxGhenis and others added 2 commits February 2, 2026 23:50
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@MaxGhenis MaxGhenis requested a review from DTrim99 February 4, 2026 01:52
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
PavelMakarchuk and others added 5 commits February 18, 2026 20:28
- Move hardcoded income source lists to parameter YAML files:
  - non_negative_sources.yaml (categories a, e, f, g, j, n, o)
  - loss_eligible/category_b.yaml (business profits)
  - loss_eligible/category_c.yaml (capital gains)
  - loss_eligible/category_d.yaml (rental income)
  - loss_eligible/category_k_p.yaml (partnership/S-corp)
- Add unemployment_compensation (NJ-1040 Line 20b, Category o)
- Remove orphaned gross_income_sources.yaml
- Add tests for unemployment comp and farm loss scenarios
- All parameter files reference N.J.S. 54A:5-1 and NJ-1040 form

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove 4 separate category parameter files; keep groupings inline
in the formula with category letter and NJ-1040 line references.
The non_negative_sources.yaml parameter file remains.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Each NJ statute category (b, c, d, k/p) gets its own parameter
file under loss_eligible_categories/ with references to N.J.S.
54A:5-1 and NJ-1040 line numbers. The formula reads all sources
from parameters.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@hua7450
Copy link
Collaborator

hua7450 commented Feb 19, 2026

State Tax Parameter Audit

Source


Issues Found

ISSUE 1 (HIGH): unemployment_compensation incorrectly added to NJ gross income

File: parameters/gov/states/nj/tax/income/gross_income/non_negative_sources.yaml

The PR adds unemployment_compensation to the non-negative sources list with the comment:

- unemployment_compensation  # Line 20b: Unemployment compensation (Category o)

This is wrong on three counts:

  1. NJ does NOT tax unemployment compensation. It is explicitly exempt per N.J.S. 54A:6-13 and listed as nontaxable on the NJ Division of Taxation exempt income page. The NJ-1040 instructions state: "Do not include the following income... Unemployment Compensation received from the state."

  2. Line 20b is NOT unemployment. Line 20b on the NJ-1040 is "Excludable Pension, Annuity, and IRA Distributions/Withdrawals" — the return-of-contributions portion of retirement distributions. There is no NJ-1040 line for unemployment compensation. (NJ-1040 Instructions, p.16 of booklet)

  3. Category (o) is NOT unemployment. Per N.J.S. 54A:5-1(o), category (o) is "income, gain or profit derived from acts or omissions defined as crimes or offenses" — i.e., criminal income.

  4. This is a new addition, not a refactor. The old gross_income_sources.yaml on master did NOT include unemployment_compensation. The PR introduces this error.

Impact: Overstates NJ gross income for anyone receiving unemployment benefits.

Fix: Remove unemployment_compensation from non_negative_sources.yaml.

ISSUE 2 (HIGH): Test incorrectly asserts unemployment is included

File: tests/policy/baseline/gov/states/nj/tax/income/taxable_income/nj_gross_income.yaml

- name: NJ gross income includes unemployment compensation
  period: 2024
  input:
    employment_income: 60_000
    unemployment_compensation: 12_000
    state_code: NJ
  output:
    nj_gross_income: 72_000

Expected output should be 60_000, not 72_000. Unemployment compensation is exempt from NJ income tax.

Fix: Either remove this test or change expected output to 60_000.


Confirmed Correct

Component Verification Status
Same-category loss rule logic Instructions for Lines 18, 19, 21, 22, 23, 24 all say "if net is a loss, make no entry"
category_b (self-employment + farm) Maps to Line 18 / N.J.S. 54A:5-1(b)
category_c (capital gains) Maps to Line 19 / N.J.S. 54A:5-1(c)
category_d (rental) Maps to Line 23 / N.J.S. 54A:5-1(d)
category_k_p (partnership + S-corp) Maps to Lines 21-22 / N.J.S. 54A:5-1(k,p)
employment_income Line 15 / Category (a)
taxable_interest_income Line 16a / Category (e)
dividend_income Line 17 / Category (f)
taxable_pension_income Line 20a / Category (j)
taxable_ira_distributions Line 20a / Category (j)
gambling_winnings Line 24 / Category (g)
alimony_income Line 25 / Category (n)
miscellaneous_income Line 26 (other)
6 of 7 YAML tests Verified against NJ-1040 form and instructions

Pre-existing Issues (not introduced by this PR)

  1. Categories (k) and (p) combined: The statute defines partnership (k) and S-corp (p) as separate categories with separate NJ-1040 lines (21 and 22). Under the same-category rule, a partnership loss should NOT offset S-corp gain. But PolicyEngine uses a single partnership_s_corp_income variable, making separate clamping impossible. This is a data model limitation.

  2. Gambling losses not netted for NJ: NJ-1040 Line 24 is "Net Gambling Winnings" — gambling losses can offset winnings in the same year. The code uses gambling_winnings without subtracting gambling_losses. Since gambling_winnings can't go negative this doesn't overstate income, but misses a legitimate NJ-specific deduction.

  3. Estate/trust income not modeled: Category (h) uses "net gains" language and could have losses, but PolicyEngine doesn't have a dedicated variable for this.

  4. Alternative Business Calculation Adjustment (ABCA) not implemented: Per P.L. 2011 c.60, NJ allows limited cross-category netting (50%) between business categories (b, d, k, p) with 20-year carryforward. Out of scope for this PR.


Summary

The core same-category loss rule implementation is correct and well-structured. The parameter-driven refactoring is an improvement over the hardcoded version. The one actionable fix is removing unemployment_compensation from non_negative_sources.yaml and correcting the associated test — NJ has exempted unemployment from state income tax since 1976.

NJ does not tax unemployment benefits per NJ Division of Unemployment
Insurance. Category (o) in N.J.S. 54A:5-1 is "income from crimes or
offenses", not unemployment compensation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@PavelMakarchuk
Copy link
Collaborator

@hua7450 ready for re-review

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.

NJ gross income incorrectly includes net losses from income categories; missing 'same category rule'

3 participants