Skip to content
Draft
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/nj-njhps-exact-fpl-ratio.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Decide New Jersey Health Plan Savings' 600% income ceiling on the exact ratio of ACA MAGI to the prior-year poverty guideline instead of the truncated `aca_magi_fraction`, so incomes from 600.003% to 600.994% of the poverty line are no longer eligible, and put a household exactly at 150%, 200%, 250% or 400% of the poverty line (or up to one point above it) in the higher per-member band, following DOBI's table of plan year 2026 amounts ("At least 150% FPL but less than 200% FPL", ..., "At least 550% FPL but less than or equal to 600% FPL", "Above 600% FPL").
36 changes: 22 additions & 14 deletions policyengine_us/parameters/gov/states/nj/dobi/njhps/pmpm.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ metadata:
href: https://pub.njleg.gov/bills/2020/AL20/61_.HTM
- title: Get Covered NJ - New Jersey Health Plan Savings
href: https://www.nj.gov/getcoverednj/financialhelp/premiums/
- title: NJ DOBI 2027 Rate Filing Requirements, section 7 (plan year 2026 subsidy amounts by FPL range)
href: https://www.nj.gov/dobi/division_insurance/ihcseh/ratefilings/2027RateFilingRequirements.pdf#page=4

brackets:
# Household income measured as ACA MAGI as a share of the federal poverty line.
Expand All @@ -33,17 +35,23 @@ brackets:
# FY2026, continuing "up to 600% FPL"). Neither source alone both states the
# values and confirms PY2026 currency, so both are carried. The Get Covered NJ
# URL is a secondary (anchorless) corroborating reference, not a value source.
# Bands are lower-exclusive / upper-inclusive, so thresholds use the "x.0001"
# convention to match the published "from X% through Y%" banding. The 138% floor
# is enforced by the eligibility gate; this base band gives $0 below 138% FPL so
# the single_amount lookup covers the full domain.
# Bands are lower-inclusive / upper-exclusive, as DOBI's 2027 Rate Filing
# Requirements (section 7) tabulate "the subsidy amounts from plan year 2026":
# "Less than 150% FPL" $20, "At least 150% FPL but less than 200% FPL" $40, ...,
# "At least 550% FPL but less than or equal to 600% FPL" $50, "Above 600% FPL"
# $0. Each threshold is therefore the band's lower edge, and a household exactly
# at an edge takes the higher band. The scale reads the truncated
# aca_magi_fraction, which is exact for lower-inclusive whole-percentage edges.
# The 138% floor and the 600% ceiling are enforced by the eligibility gate; this
# base band gives $0 below 138% FPL so the single_amount lookup covers the full
# domain.
# Below 138% FPL (routed to NJ FamilyCare/Medicaid) -> $0 per member per month.
- threshold:
2026-01-01: 0
amount:
0000-01-01: 0
2026-01-01: 0
# 138% through 150% FPL -> $20 per member per month. The schedule is new to the
# 138% to less than 150% FPL -> $20 per member per month. The schedule is new to the
# model for PY2026, so every band is $0 before 2026-01-01. The threshold is set
# to 1.3799 (just below the 138% floor) because aca_magi_fraction is a float32
# variable and float32(1.38) rounds to 1.37999999..., which is just BELOW a
Expand All @@ -54,32 +62,32 @@ brackets:
amount:
0000-01-01: 0
2026-01-01: 20
# 150% through 200% FPL -> $40.
# At least 150% but less than 200% FPL -> $40.
- threshold:
2026-01-01: 1.5001
2026-01-01: 1.5
amount:
0000-01-01: 0
2026-01-01: 40
# 200% through 250% FPL -> $50.
# At least 200% but less than 250% FPL -> $50.
- threshold:
2026-01-01: 2.0001
2026-01-01: 2
amount:
0000-01-01: 0
2026-01-01: 50
# 250% through 400% FPL -> $100.
# At least 250% but less than 400% FPL -> $100.
- threshold:
2026-01-01: 2.5001
2026-01-01: 2.5
amount:
0000-01-01: 0
2026-01-01: 100
# 400% through 600% FPL -> $50. In this band the federal APTC is $0 (the 400% FPL
# At least 400% and up to 600% FPL -> $50. In this band the federal APTC is $0 (the 400% FPL
# cliff reverts for PY2026), so NJHPS is the sole premium subsidy.
- threshold:
2026-01-01: 4.0001
2026-01-01: 4
amount:
0000-01-01: 0
2026-01-01: 50
# Above 600% FPL (ineligible) -> $0.
# Above 600% FPL (ineligible; the eligibility gate excludes it) -> $0.
- threshold:
2026-01-01: 6.0001
amount:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Integration tests for New Jersey Health Plan Savings (NJHPS).
#
# These exercise the full chain: aca_magi_fraction (income band) ->
# These exercise the full chain: the exact ratio of ACA MAGI to the prior-year
# guideline (eligibility band) and aca_magi_fraction (PMPM band) ->
# nj_njhps_member_eligible (per-person non-income enrollee gate) ->
# nj_njhps_eligible (in_effect + band + at least one member) ->
# nj_njhps (per-member build-up capped by SLCSP - APTC).
Expand All @@ -12,19 +13,19 @@
#
# Amount formula recap:
# n = count of nj_njhps_member_eligible members
# pmpm(frac) = 20 [1.38-1.5], 40 (1.5-2.0], 50 (2.0-2.5], 100 (2.5-4.0],
# 50 (4.0-6.0], 0 elsewhere [per member per month]
# pmpm(frac) = 20 [1.38-1.5), 40 [1.5-2.0), 50 [2.0-2.5), 100 [2.5-4.0),
# 50 [4.0-6.0], 0 elsewhere [per member per month]
# base_annual = 12 * pmpm(frac) * n
# residual = max(0, slcsp_annual - aca_ptc)
# annual = eligible * min(base_annual, residual)

- name: Scenario 1, single adult with income deriving 200% FPL, $40 band.
- name: Scenario 1, single adult with income deriving 200% FPL, $50 band.
# Income path: employment_income 31_300 -> aca_magi 31_300 (single W-2 filer,
# AGI = gross). aca_magi_fraction uses the PRIOR-year (2025) single FPG 15_650:
# floor(100 * 31_300 / 15_650) / 100 = floor(200.0) / 100 = 2.0.
# frac 2.0 is upper-inclusive in the (1.5, 2.0] band -> pmpm 40. n=1 ->
# base = 12*40 = 480. residual = max(0, 8_000 - 5_000) = 3_000.
# annual = min(480, 3_000) = 480.
# DOBI's plan year 2026 bands are lower-inclusive: "At least 200% FPL but less
# than 250% FPL" -> pmpm 50. n=1 -> base = 12*50 = 600.
# residual = max(0, 8_000 - 5_000) = 3_000. annual = min(600, 3_000) = 600.
period: 2026
absolute_error_margin: 0.01
input:
Expand All @@ -49,7 +50,7 @@
nj_njhps_eligible: true
slcsp: 8_000 # annual sum of the MONTH variable
aca_ptc: 5_000
nj_njhps: 480
nj_njhps: 600

- name: Scenario 2, family of four at 300% FPL, four members, digest anchor.
# frac 3.0 -> pmpm 100. All four pay an ACA premium -> n=4.
Expand All @@ -66,7 +67,9 @@
tax_units:
tax_unit:
members: [person1, person2, person3, person4]
aca_magi_fraction: 3.0
aca_magi: 30_000
tax_unit_fpg:
2025: 10_000
slcsp: 20_000
aca_ptc: 8_000
households:
Expand Down Expand Up @@ -96,7 +99,9 @@
tax_units:
tax_unit:
members: [person1]
aca_magi_fraction: 5.0
aca_magi: 50_000
tax_unit_fpg:
2025: 10_000
slcsp: 7_000
aca_ptc: 0
households:
Expand Down Expand Up @@ -125,7 +130,9 @@
tax_units:
tax_unit:
members: [person1, person2]
aca_magi_fraction: 2.2
aca_magi: 22_000
tax_unit_fpg:
2025: 10_000
slcsp: 6_000
aca_ptc: 5_200
households:
Expand Down Expand Up @@ -153,7 +160,9 @@
tax_units:
tax_unit:
members: [person1]
aca_magi_fraction: 1.2
aca_magi: 12_000
tax_unit_fpg:
2025: 10_000
slcsp: 6_000
aca_ptc: 4_000
households:
Expand Down Expand Up @@ -181,7 +190,9 @@
tax_units:
tax_unit:
members: [person1]
aca_magi_fraction: 2.5
aca_magi: 25_000
tax_unit_fpg:
2025: 10_000
filing_status: SEPARATE
slcsp: 9_000
aca_ptc: 4_000
Expand Down Expand Up @@ -211,7 +222,9 @@
tax_units:
tax_unit:
members: [person1]
aca_magi_fraction: 6.0
aca_magi: 60_000
tax_unit_fpg:
2025: 10_000
slcsp: 10_000
aca_ptc: 0
households:
Expand All @@ -231,10 +244,10 @@
# ===========================================================================

- name: Scenario 8, exactly 400% FPL, APTC-positive residual caps below the base.
# notes: 400.0 leg of the residual-flip pair. frac 4.0 (NOT > 4.0001) -> pmpm 100,
# n=1 -> base = 12*100 = 1_200. At the cliff the tax unit is still APTC-eligible,
# notes: 400.0 leg of the residual-flip pair. "At least 400% FPL" -> pmpm 50,
# n=1 -> base = 12*50 = 600. At the cliff the tax unit is still APTC-eligible,
# so aca_ptc 9_500 -> residual = max(0, 10_000 - 9_500) = 500. annual =
# min(1_200, 500) = 500 (the post-APTC residual binds). Paired with Scenario 9.
# min(600, 500) = 500 (the post-APTC residual binds). Paired with Scenario 9.
period: 2026
absolute_error_margin: 0.01
input:
Expand All @@ -245,7 +258,9 @@
tax_units:
tax_unit:
members: [person1]
aca_magi_fraction: 4.0
aca_magi: 40_000
tax_unit_fpg:
2025: 10_000
slcsp: 10_000
aca_ptc: 9_500
households:
Expand All @@ -262,7 +277,7 @@

- name: Scenario 9, just above 400% FPL, APTC 0 so residual is the full SLCSP.
# notes: 400.01 leg of the residual-flip pair (same slcsp 10_000 as Scenario 8).
# frac 4.01 > 4.0001 -> pmpm 50, n=1 -> base = 12*50 = 600. Above the 400% cliff
# frac 4.01 >= 4.0 -> pmpm 50, n=1 -> base = 12*50 = 600. Above the 400% cliff
# aca_ptc 0 -> residual = max(0, 10_000 - 0) = 10_000. annual = min(600, 10_000) =
# 600. The residual source flips from slcsp - aptc (500) to full slcsp (10_000),
# so the base binds -> 600. NJHPS is the sole subsidy in this band.
Expand All @@ -276,7 +291,9 @@
tax_units:
tax_unit:
members: [person1]
aca_magi_fraction: 4.01
aca_magi: 40_100
tax_unit_fpg:
2025: 10_000
slcsp: 10_000
aca_ptc: 0
households:
Expand Down Expand Up @@ -307,7 +324,9 @@
tax_units:
tax_unit:
members: [person1, person2, person3, person4]
aca_magi_fraction: 3.0
aca_magi: 30_000
tax_unit_fpg:
2025: 10_000
slcsp: 20_000
aca_ptc: 8_000
households:
Expand Down
Loading