From d1b2cf0c95cf7ea9bb9ae3d17cc9ff8f909d4e79 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Tue, 20 Oct 2026 18:41:58 -0400 Subject: [PATCH] Decide NM Premium Assistance's income edges on the exact FPL ratio New Mexico's base program excludes "consumers with household income over 400% of the FPL", Addendum 1 covers Middle Income Households with income "above 400% of the FPL", and the PY2026 manual grosses up the benchmark "up to 200% FPL" (the next range starts at "200.01"). All three compared the truncated aca_magi_fraction, which maps 400.00%-400.99% to 4.00 and 200.00%-200.99% to 2.00. So incomes just above 400% were in neither component (the federal gate ends the base program at 400%, and MIH needed a fraction above 4.00), and the PY2026 gross-up reached 200.99%. Test the exact ratio of nonnegative ACA MAGI to the prior-year guideline instead. Case 24 expected $0 for a $62,700 filer (400.64%) because of the gap; it now expects the $2,670.50 MIH amount. About twenty cases supplied a fraction that disagreed with their own MAGI. Where the case's arithmetic assumed another guideline (often $15,000), its prior-year tax_unit_fpg is pinned to that guideline; otherwise the income and expected amount are recomputed. Case 29 now supplies MIH eligibility to reach the negative- income clamp. Co-Authored-By: Claude Opus 5.5 --- ...remium-assistance-exact-fpl-ratio.fixed.md | 1 + .../nm/hca/premium_assistance/fpl_limit.yaml | 2 + .../hca/premium_assistance/integration.yaml | 30 +- .../nm_premium_assistance.yaml | 272 ++++++++++++++---- .../nm_premium_assistance_eligible.yaml | 118 +++++++- .../nm_premium_assistance_mih_eligible.yaml | 110 ++++++- .../nm_premium_assistance.py | 13 +- .../nm_premium_assistance_eligible.py | 9 +- .../nm_premium_assistance_mih_eligible.py | 9 +- 9 files changed, 474 insertions(+), 90 deletions(-) create mode 100644 changelog.d/nm-premium-assistance-exact-fpl-ratio.fixed.md diff --git a/changelog.d/nm-premium-assistance-exact-fpl-ratio.fixed.md b/changelog.d/nm-premium-assistance-exact-fpl-ratio.fixed.md new file mode 100644 index 00000000000..0289be1077c --- /dev/null +++ b/changelog.d/nm-premium-assistance-exact-fpl-ratio.fixed.md @@ -0,0 +1 @@ +Decide New Mexico Premium Assistance's income edges on the exact ratio of ACA MAGI to the prior-year poverty guideline instead of the truncated `aca_magi_fraction`. Incomes from 400.003% to 400.994% of the poverty line were in neither component (the base program's federal gate ends at 400%, and the Middle Income Household gate needed a truncated fraction above 4.00); they now qualify as Middle Income Households (Addendum 1: "above 400% of the FPL"). The PY2026 10% benchmark gross-up no longer applies from 200.003% to 200.994% ("up to 200% FPL"), and the base program's 400% ceiling holds on its own under a reform that extends the federal credit. diff --git a/policyengine_us/parameters/gov/states/nm/hca/premium_assistance/fpl_limit.yaml b/policyengine_us/parameters/gov/states/nm/hca/premium_assistance/fpl_limit.yaml index cb363a4f71d..d216a777809 100644 --- a/policyengine_us/parameters/gov/states/nm/hca/premium_assistance/fpl_limit.yaml +++ b/policyengine_us/parameters/gov/states/nm/hca/premium_assistance/fpl_limit.yaml @@ -3,6 +3,8 @@ values: # Dual use: this ceiling is the base-program cutoff via <= (units at or below # 400% FPL qualify for base assistance) and simultaneously the Middle Income # Household floor via > (units above 400% FPL route to the MIH component). + # Both compare the exact ratio of ACA MAGI to the prior-year guideline, not the + # truncated aca_magi_fraction, so every income is in exactly one component. # PY2027 uses the same 400% ceiling per the FINAL PY2027 MAP Manual (HCA, # issued 2026-06-16), Table 1 (p.4) and the flat 8.5% cap at >=400% FPL (p.4). 2026-01-01: 4 diff --git a/policyengine_us/tests/policy/baseline/gov/states/nm/hca/premium_assistance/integration.yaml b/policyengine_us/tests/policy/baseline/gov/states/nm/hca/premium_assistance/integration.yaml index a6153aa6270..bd5d4f1cfa1 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/nm/hca/premium_assistance/integration.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/nm/hca/premium_assistance/integration.yaml @@ -35,6 +35,9 @@ members: [person1] tax_unit_is_filer: true aca_magi: 22_500 + # The case's arithmetic assumes a $15,000 prior-year guideline. + tax_unit_fpg: + 2025: 15_000 aca_magi_fraction: 1.50 slcsp: 6_000 aca_ptc: 3_500 @@ -128,6 +131,9 @@ members: [person1, person2, person3, person4] tax_unit_is_filer: true aca_magi: 93_900 + # The case's arithmetic assumes a $31,300 prior-year guideline. + tax_unit_fpg: + 2025: 31_300 aca_magi_fraction: 3.00 slcsp: 18_000 aca_ptc: 8_000 @@ -240,7 +246,7 @@ tax_unit: members: [person1] tax_unit_is_filer: true - aca_magi: 39_125 + aca_magi: 37_650 # 250% of the 2024 one-person guideline ($15,060) aca_magi_fraction: 2.50 slcsp: 6_000 aca_ptc: 2_500 @@ -269,6 +275,9 @@ members: [person1] tax_unit_is_filer: true aca_magi: 39_000 + # The case's arithmetic assumes a $15,600 prior-year guideline. + tax_unit_fpg: + 2025: 15_600 aca_magi_fraction: 2.50 slcsp: 0 aca_ptc: 0 @@ -298,6 +307,9 @@ members: [person1] tax_unit_is_filer: true aca_magi: 39_125 + # Pinned to the 2025 guideline so the inputs match the PY2026 Scenario 3. + tax_unit_fpg: + 2026: 15_650 aca_magi_fraction: 2.50 slcsp: 6_000 aca_ptc: 2_500 @@ -314,10 +326,11 @@ - name: Scenario 11, MIH couple with one premium-payer (base gate vs MIH gate). # No member is is_aca_ptc_eligible -> base band fails. person1 pays_aca_premium # so tax_unit.any(pays_aca_premium) is true and filing is joint (not separate) - # -> MIH eligible. magi_frac 4.20 > 4.0. + # -> MIH eligible. 420% of the 2025 two-person guideline ($21,150) is 88_830; + # ratio 4.20 > 4.0. # target% schedule clamps to the top band -> 0.085 (unused in MIH; MIH uses the - # flat 0.085 mih.rate). amount = max(0, 12_000 - 0 - 0.085*66_000) - # = 12_000 - 5_610 = 6_390. + # flat 0.085 mih.rate). amount = max(0, 12_000 - 0 - 0.085*88_830) + # = 12_000 - 7_550.55 = 4_449.45. period: 2026 absolute_error_margin: 0.01 input: @@ -335,7 +348,7 @@ members: [person1, person2] filing_status: JOINT tax_unit_is_filer: true - aca_magi: 66_000 + aca_magi: 88_830 aca_magi_fraction: 4.20 slcsp: 12_000 aca_ptc: 0 @@ -347,7 +360,7 @@ nm_premium_assistance_eligible: false nm_premium_assistance_mih_eligible: true nm_premium_assistance_target_contribution_percentage: 0.085 - nm_premium_assistance: 6_390 + nm_premium_assistance: 4_449.45 # ---- Derived-from-raw-income scenario ---- # Unlike every scenario above, this one does NOT inject aca_magi, @@ -438,7 +451,8 @@ nm_premium_assistance: 2_600 - name: Scenario 14, exactly 200% FPL in 2027 -> gross-up NOT applied (strict <). - # magi_frac 2.00 is NOT < 2.0 in PY2027, so the gross-up is off and the plain + # 200% of the 2026 one-person guideline ($15,960), the prior year for 2027, is + # 31_920. The ratio 2.00 is NOT < 2.0 in PY2027, so the gross-up is off and the plain # benchmark applies. amount = 6_000 - 4_000 = 2_000. Same inputs as Scenario 13 # except the year; the 600 drop is exactly the withdrawn 10% gross-up. period: 2027 @@ -452,7 +466,7 @@ tax_unit: members: [person1] tax_unit_is_filer: true - aca_magi: 31_300 + aca_magi: 31_920 aca_magi_fraction: 2.00 slcsp: 6_000 aca_ptc: 4_000 diff --git a/policyengine_us/tests/policy/baseline/gov/states/nm/hca/premium_assistance/nm_premium_assistance.yaml b/policyengine_us/tests/policy/baseline/gov/states/nm/hca/premium_assistance/nm_premium_assistance.yaml index 9b3e0c164a7..b9c1a71f76b 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/nm/hca/premium_assistance/nm_premium_assistance.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/nm/hca/premium_assistance/nm_premium_assistance.yaml @@ -75,8 +75,9 @@ # ---- Base band: gross-up OFF (>200% FPL) ---- - name: Case 3, just above 200% FPL (2.01), no gross-up, target ~0.04%. - # benchmark = 6_000 (magi_frac 2.01 > 2.0). target_pct 0.0004. - # amount = max(0, 6_000 - 3_000 - 0.0004*30_000) = 6_000 - 3_000 - 12 = 2_988. + # 201% of the 2025 one-person guideline ($15,650) is 31_456.50. + # benchmark = 6_000 (ratio 2.01 > 2.0). target_pct 0.0004. + # amount = max(0, 6_000 - 3_000 - 0.0004*31_456.50) = 6_000 - 3_000 - 12.58 = 2_987.42. period: 2026 absolute_error_margin: 0.01 input: @@ -88,7 +89,7 @@ tax_unit: members: [person1] tax_unit_is_filer: true - aca_magi: 30_000 + aca_magi: 31_456.50 aca_magi_fraction: 2.01 slcsp: 6_000 aca_ptc: 3_000 @@ -97,7 +98,7 @@ members: [person1] state_code: NM output: - nm_premium_assistance: 2_988 + nm_premium_assistance: 2_987.42 - name: Case 4, 300% FPL, target 5%, no gross-up. # benchmark = 9_000. target_pct 0.05. term = 0.05*45_000 = 2_250. @@ -114,6 +115,9 @@ members: [person1] tax_unit_is_filer: true aca_magi: 45_000 + # The case's arithmetic assumes a $15,000 prior-year guideline. + tax_unit_fpg: + 2025: 15_000 aca_magi_fraction: 3.00 slcsp: 9_000 aca_ptc: 4_000 @@ -139,6 +143,9 @@ members: [person1] tax_unit_is_filer: true aca_magi: 60_000 + # The case's arithmetic assumes a $15,000 prior-year guideline. + tax_unit_fpg: + 2025: 15_000 aca_magi_fraction: 4.00 slcsp: 10_000 aca_ptc: 4_000 @@ -165,6 +172,9 @@ tax_unit: members: [person1] aca_magi: 39_000 + # The case's arithmetic assumes a $15,600 prior-year guideline. + tax_unit_fpg: + 2025: 15_600 aca_magi_fraction: 2.50 slcsp: 6_000 aca_ptc: 6_000 @@ -223,6 +233,9 @@ members: [person1] filing_status: SINGLE aca_magi: 120_000 + # The case's arithmetic assumes a $24,000 prior-year guideline. + tax_unit_fpg: + 2025: 24_000 aca_magi_fraction: 5.00 slcsp: 8_000 aca_ptc: 0 @@ -235,7 +248,8 @@ - name: Case 9, MIH filer using married-filing-separately is ineligible -> $0. # Federal PTC criteria (except income) exclude separate filers; MIH inherits - # that exclusion, so despite pays_aca_premium the amount is 0. + # that exclusion, so despite pays_aca_premium the amount is 0. 420% of the 2025 + # two-person guideline ($21,150) is 88_830. period: 2026 absolute_error_margin: 0.01 input: @@ -252,7 +266,7 @@ tax_unit: members: [person1, person2] filing_status: SEPARATE - aca_magi: 70_000 + aca_magi: 88_830 aca_magi_fraction: 4.20 slcsp: 12_000 aca_ptc: 0 @@ -277,6 +291,9 @@ tax_unit: members: [person1] aca_magi: 39_000 + # The case's arithmetic assumes a $15,600 prior-year guideline. + tax_unit_fpg: + 2025: 15_600 aca_magi_fraction: 2.50 slcsp: 6_000 aca_ptc: 3_000 @@ -298,7 +315,7 @@ tax_units: tax_unit: members: [person1] - aca_magi: 39_000 + aca_magi: 37_650 # 250% of the 2024 one-person guideline ($15,060) aca_magi_fraction: 2.50 slcsp: 6_000 aca_ptc: 3_000 @@ -350,7 +367,7 @@ tax_units: tax_unit: members: [person1] - aca_magi: 22_000 + aca_magi: 23_475 # 150% of the 2025 one-person guideline ($15,650) aca_magi_fraction: 1.50 slcsp: 0 aca_ptc: 0 @@ -375,7 +392,7 @@ tax_unit: members: [person1] filing_status: SINGLE - aca_magi: 70_000 + aca_magi: 70_425 # 450% of the 2025 one-person guideline ($15,650) aca_magi_fraction: 4.50 slcsp: 0 aca_ptc: 0 @@ -405,6 +422,9 @@ filing_status: SINGLE tax_unit_is_filer: true aca_magi: 60_000 + # The case's arithmetic assumes a $15,000 prior-year guideline. + tax_unit_fpg: + 2025: 15_000 aca_magi_fraction: 4.00 slcsp: 10_000 aca_ptc: 0 @@ -418,8 +438,9 @@ - name: Case 16, base->MIH handoff just above 400% FPL (4.01) routes to MIH. # magi_frac 4.01 > 4.0 -> base ineligible, MIH eligible. is_aca_ptc_eligible # is false above 400% FPL, so MIH uses pays_aca_premium & non-separate filing. - # MIH amount = max(0, 10_000 - 0 - 0.085*60_150) = max(0, 10_000 - 5_112.75) - # = 4_887.25. Same 8.5% rate as the base 300-400% top, so the handoff is + # 401% of the 2025 one-person guideline ($15,650) is 62_756.50. + # MIH amount = max(0, 10_000 - 0 - 0.085*62_756.50) = max(0, 10_000 - 5_334.30) + # = 4_665.70. Same 8.5% rate as the base 300-400% top, so the handoff is # nearly continuous here; the routing branch is what changes. period: 2026 absolute_error_margin: 0.01 @@ -434,7 +455,7 @@ members: [person1] filing_status: SINGLE tax_unit_is_filer: true - aca_magi: 60_150 + aca_magi: 62_756.50 aca_magi_fraction: 4.01 slcsp: 10_000 aca_ptc: 0 @@ -443,13 +464,13 @@ members: [person1] state_code: NM output: - nm_premium_assistance: 4_887.25 + nm_premium_assistance: 4_665.70 - name: Case 17, MIH unit where only one member pays the ACA premium is eligible. # No member is is_aca_ptc_eligible (base gate fails), but person1 pays_aca_premium # so tax_unit.any(pays_aca_premium) is true -> MIH gate passes. Non-separate - # filing. magi_frac 4.20 > 4.0. - # MIH amount = max(0, 12_000 - 0 - 0.085*66_000) = max(0, 12_000 - 5_610) = 6_390. + # filing. 420% of the 2025 two-person guideline ($21,150) is 88_830; ratio 4.20 > 4.0. + # MIH amount = max(0, 12_000 - 0 - 0.085*88_830) = max(0, 12_000 - 7_550.55) = 4_449.45. # Demonstrates the base gate (needs is_aca_ptc_eligible) vs MIH gate # (needs pays_aca_premium) difference within one household. period: 2026 @@ -469,7 +490,7 @@ members: [person1, person2] filing_status: JOINT tax_unit_is_filer: true - aca_magi: 66_000 + aca_magi: 88_830 aca_magi_fraction: 4.20 slcsp: 12_000 aca_ptc: 0 @@ -478,14 +499,17 @@ members: [person1, person2] state_code: NM output: - nm_premium_assistance: 6_390 + nm_premium_assistance: 4_449.45 - name: Case 18, negative MAGI in the base band is clamped to zero (guarded). - # magi_frac 3.00 -> target% 0.05, no gross-up, benchmark = 9_000. + # aca_magi_fraction is supplied at 3.00 only so the target percentage is + # nonzero (0.05) and the clamp matters. Eligibility and the gross-up read the + # exact ratio, max(-10_000, 0) / FPG = 0, which is up to 200% FPL, so the PY2026 + # gross-up applies: benchmark = 9_000 * 1.1 = 9_900. # income is clamped to max(-10_000, 0) = 0, so the target term is 0.05*0 = 0. - # amount = max(0, 9_000 - 4_000 - 0) = 5_000. Without the clamp the - # -target%*income term would ADD 500, inflating the top-up to 5_500 above the - # benchmark - aca_ptc residual (= 5_000); the income>=0 guard prevents that. + # amount = max(0, 9_900 - 4_000 - 0) = 5_900. Without the clamp the + # -target%*income term would ADD 500, inflating the top-up to 6_400 above the + # benchmark - aca_ptc residual (= 5_900); the income>=0 guard prevents that. period: 2026 absolute_error_margin: 0.01 input: @@ -506,7 +530,7 @@ members: [person1] state_code: NM output: - nm_premium_assistance: 5_000 + nm_premium_assistance: 5_900 - name: Case 19, PY2027 carry-forward year pays the same base-band top-up. # Same schedule and parameters carry forward through PY2027. 300% FPL, target 5%, @@ -523,6 +547,9 @@ members: [person1] tax_unit_is_filer: true aca_magi: 45_000 + # The case's arithmetic assumes a $15,000 prior-year guideline. + tax_unit_fpg: + 2026: 15_000 aca_magi_fraction: 3.00 slcsp: 9_000 aca_ptc: 4_000 @@ -554,6 +581,9 @@ members: [person1] tax_unit_is_filer: true aca_magi: 45_000 + # The case's arithmetic assumes a $15,000 prior-year guideline. + tax_unit_fpg: + 2027: 15_000 aca_magi_fraction: 3.00 slcsp: 9_000 aca_ptc: 4_000 @@ -582,7 +612,9 @@ members: [person1] filing_status: SINGLE tax_unit_is_filer: true - aca_magi: 62_000 + aca_magi: 63_382.50 # 405% of the pinned $15,650 guideline + tax_unit_fpg: + 2027: 15_650 aca_magi_fraction: 4.05 slcsp: 10_000 aca_ptc: 0 @@ -599,11 +631,12 @@ # In the PY2026 baseline federal APTC is $0 above 400% FPL, so the - aca_ptc # term in the MIH branch is never exercised by any other case. Here aca_ptc is # injected non-zero (a reform-style input restoring APTC above 400% FPL) to - # drive the subtraction. magi_frac 4.50 > 4.0 -> MIH; no gross-up on MIH. - # amount = max(0, 10_000 - 2_000 - 0.085*70_000) - # = max(0, 10_000 - 2_000 - 5_950) = 2_050. + # drive the subtraction. 450% of the 2025 one-person guideline ($15,650) is + # 70_425; ratio 4.50 > 4.0 -> MIH; no gross-up on MIH. + # amount = max(0, 10_000 - 2_000 - 0.085*70_425) + # = max(0, 10_000 - 2_000 - 5_986.125) = 2_013.875. # Documents the reform-scenario behavior: the state subsidy nets any restored - # APTC (2_050 here vs 4_050 if the - aca_ptc term were dropped). + # APTC (2_013.875 here vs 4_013.875 if the - aca_ptc term were dropped). period: 2026 absolute_error_margin: 0.01 input: @@ -617,7 +650,7 @@ members: [person1] filing_status: SINGLE tax_unit_is_filer: true - aca_magi: 70_000 + aca_magi: 70_425 aca_magi_fraction: 4.50 slcsp: 10_000 aca_ptc: 2_000 @@ -626,7 +659,7 @@ members: [person1] state_code: NM output: - nm_premium_assistance: 2_050 + nm_premium_assistance: 2_013.875 # ---- Mid-bracket target interpolation (base band) ---- @@ -634,8 +667,9 @@ # magi_frac 2.75 lands mid-way in the 250-300% band (initial 2%, final 5%): # position = (2.75 - 2.5) / (3.0 - 2.5) = 0.5, so target% = 0.02 + 0.5*0.03 # = 0.035. No gross-up (2.75 > 2.0). benchmark = 8_000. - # amount = max(0, 8_000 - 1_000 - 0.035*43_000) - # = max(0, 8_000 - 1_000 - 1_505) = 5_495. + # 275% of the 2025 one-person guideline ($15,650) is 43_037.50. + # amount = max(0, 8_000 - 1_000 - 0.035*43_037.50) + # = max(0, 8_000 - 1_000 - 1_506.3125) = 5_493.6875. # Exercises the within-band searchsorted interpolation at a non-endpoint FPL, # complementing the band-endpoint cases (200/250/300/400% FPL) above. period: 2026 @@ -649,7 +683,7 @@ tax_unit: members: [person1] tax_unit_is_filer: true - aca_magi: 43_000 + aca_magi: 43_037.50 aca_magi_fraction: 2.75 slcsp: 8_000 aca_ptc: 1_000 @@ -658,25 +692,27 @@ members: [person1] state_code: NM output: - nm_premium_assistance: 5_495 + nm_premium_assistance: 5_493.6875 # ---- Derived 400%-FPL boundary pair (A10) ---- # These two cases do NOT inject aca_magi/aca_magi_fraction/is_aca_ptc_eligible; # they drive the whole federal ACA chain from raw employment_income for a single # NM filer in 2026 and inject only slcsp. The pair straddles the 400% FPL cliff: -# 62_700 -> ~4.00 (base band, MIH-ineligible) and 62_800 -> ~4.01 (base- -# ineligible, MIH-eligible). Above 400% FPL the federal APTC and the base target -# schedule both bind at the cap, so all three benefits round to 0 at 62_700. -# EXPECTED TO CHANGE once upstream #9264 lands (the aca_magi_fraction whole-% -# truncation fix): the exact fraction each wage maps to may shift by a cent of -# FPL, moving one case across the 4.00 boundary. - -- name: Case 24, derived single NM filer 2026 at ~400% FPL (base band, benefits 0). - # employment_income 62_700 derives aca_magi_fraction ~4.00; base-eligible but - # the 8.5% top-of-schedule target plus the derived federal APTC leave no state - # top-up. MIH-ineligible (not > 4.0). Paired with Case 25 across the cliff. - # May move when #9264 lands (aca_magi_fraction whole-% truncation fix). +# 62_700 -> 4.00 truncated and 62_800 -> 4.01 truncated; both are base- +# ineligible, MIH-eligible on the exact ratio: 62_700 is 400.64% and 62_800 is +# 401.28% of $15,650, both "above 400% of the FPL" (Addendum 1). Truncation maps +# 62_700 to 4.00, which once left it in neither component. + +- name: Case 24, derived single NM filer 2026 at 400.64% FPL is a Middle Income Household. + # employment_income 62_700 is 400.64% of the 2025 one-person guideline + # ($15,650); aca_magi_fraction truncates to 4.00. The federal credit stops above + # 400%, so the base program (which needs is_aca_ptc_eligible) does not apply. + # Addendum 1 covers income "above 400% of the FPL", decided on the exact ratio, + # so the household is MIH-eligible (main left it in neither component): + # MIH amount = max(0, 8_000 - 0 - 0.085*62_700) = 8_000 - 5_329.50 = 2_670.50. + # Paired with Case 25 across the cliff. period: 2026 + absolute_error_margin: 0.01 input: people: person1: @@ -693,13 +729,14 @@ members: [person1] state_code: NM output: - nm_premium_assistance: 0 + nm_premium_assistance_eligible: false + nm_premium_assistance_mih_eligible: true + nm_premium_assistance: 2_670.50 - name: Case 25, derived single NM filer 2026 at ~401% FPL (MIH-eligible). # employment_income 62_800 derives aca_magi_fraction ~4.01 -> base-ineligible, # MIH-eligible (> 4.0). The MIH 8.5% cap gives a small positive top-up against # the 8_000 benchmark. Paired with Case 24 across the 400% FPL cliff. - # May move when #9264 lands (aca_magi_fraction whole-% truncation fix). period: 2026 input: people: @@ -809,8 +846,9 @@ - name: Case 29, negative MAGI in the MIH band is clamped to zero (guarded mirror). # Base-band Case 18 covers the negative-income clamp for the base branch; this - # mirrors it for the MIH branch. magi_frac 4.50 > 4.0 -> MIH. income clamps to - # max(-20_000, 0) = 0, so the 0.085*income term is 0. + # mirrors it for the MIH branch. Negative MAGI is 0% of the guideline on the + # exact ratio, so MIH eligibility is supplied to reach the MIH amount's clamp. + # income clamps to max(-20_000, 0) = 0, so the 0.085*income term is 0. # amount = max(0, 10_000 - 0 - 0.085*0) = 10_000. Without the clamp the # -0.085*(-20_000) term would ADD 1_700 above the slcsp, inflating the cap. period: 2026 @@ -826,6 +864,7 @@ members: [person1] filing_status: SINGLE tax_unit_is_filer: true + nm_premium_assistance_mih_eligible: true aca_magi: -20_000 aca_magi_fraction: 4.50 slcsp: 10_000 @@ -887,3 +926,138 @@ # Derived SLCSP from the Bernalillo County rating area yields a positive base # top-up. Value is data-driven from the premium table. nm_premium_assistance: 2_645.60 + +- name: Case 32, $31,300 (exactly 200% of the 2025 guideline) keeps the PY2026 gross-up. + # PY2026: "up to 200% FPL" (the next range starts at "200.01"); PY2027: "under + # 200% (< 200%)". Eligibility and a 0% target are supplied, so the amount shows + # only the gross-up: 6_000 * 1.1 - 4_000 = 2_600. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + tax_units: + tax_unit: + members: [person1] + nm_premium_assistance_eligible: true + nm_premium_assistance_mih_eligible: false + nm_premium_assistance_target_contribution_percentage: 0 + aca_magi: 31_300 + slcsp: 6_000 + aca_ptc: 4_000 + households: + household: + members: [person1] + state_code: NM + output: + aca_magi_fraction: 2.00 + nm_premium_assistance: 2_600 + +- name: Case 33, $31,300.47 (200.003%) gets no PY2026 gross-up though truncated to 2.00. + # PY2026: "up to 200% FPL" (the next range starts at "200.01"); PY2027: "under + # 200% (< 200%)". Eligibility and a 0% target are supplied, so the amount shows + # only the gross-up: 6_000 - 4_000 = 2_000. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + tax_units: + tax_unit: + members: [person1] + nm_premium_assistance_eligible: true + nm_premium_assistance_mih_eligible: false + nm_premium_assistance_target_contribution_percentage: 0 + aca_magi: 31_300.47 + slcsp: 6_000 + aca_ptc: 4_000 + households: + household: + members: [person1] + state_code: NM + output: + aca_magi_fraction: 2.00 + nm_premium_assistance: 2_000 + +- name: Case 34, $31,455.56 (200.994%) gets no PY2026 gross-up though truncated to 2.00. + # PY2026: "up to 200% FPL" (the next range starts at "200.01"); PY2027: "under + # 200% (< 200%)". Eligibility and a 0% target are supplied, so the amount shows + # only the gross-up: 6_000 - 4_000 = 2_000. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + tax_units: + tax_unit: + members: [person1] + nm_premium_assistance_eligible: true + nm_premium_assistance_mih_eligible: false + nm_premium_assistance_target_contribution_percentage: 0 + aca_magi: 31_455.56 + slcsp: 6_000 + aca_ptc: 4_000 + households: + household: + members: [person1] + state_code: NM + output: + aca_magi_fraction: 2.00 + nm_premium_assistance: 2_000 + +- name: Case 35, $31,919 (199.994% of the 2026 guideline) keeps the PY2027 gross-up. + # PY2026: "up to 200% FPL" (the next range starts at "200.01"); PY2027: "under + # 200% (< 200%)". Eligibility and a 0% target are supplied, so the amount shows + # only the gross-up: 6_000 * 1.1 - 4_000 = 2_600. + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + tax_units: + tax_unit: + members: [person1] + nm_premium_assistance_eligible: true + nm_premium_assistance_mih_eligible: false + nm_premium_assistance_target_contribution_percentage: 0 + aca_magi: 31_919 + slcsp: 6_000 + aca_ptc: 4_000 + households: + household: + members: [person1] + state_code: NM + output: + aca_magi_fraction: 1.99 + nm_premium_assistance: 2_600 + +- name: Case 36, $31,920 (exactly 200% of the 2026 guideline) gets no PY2027 gross-up. + # PY2026: "up to 200% FPL" (the next range starts at "200.01"); PY2027: "under + # 200% (< 200%)". Eligibility and a 0% target are supplied, so the amount shows + # only the gross-up: 6_000 - 4_000 = 2_000. + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + tax_units: + tax_unit: + members: [person1] + nm_premium_assistance_eligible: true + nm_premium_assistance_mih_eligible: false + nm_premium_assistance_target_contribution_percentage: 0 + aca_magi: 31_920 + slcsp: 6_000 + aca_ptc: 4_000 + households: + household: + members: [person1] + state_code: NM + output: + aca_magi_fraction: 2.00 + nm_premium_assistance: 2_000 diff --git a/policyengine_us/tests/policy/baseline/gov/states/nm/hca/premium_assistance/nm_premium_assistance_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/nm/hca/premium_assistance/nm_premium_assistance_eligible.yaml index 4916b85758a..e417698c9c7 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/nm/hca/premium_assistance/nm_premium_assistance_eligible.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/nm/hca/premium_assistance/nm_premium_assistance_eligible.yaml @@ -21,7 +21,9 @@ tax_unit: members: [person1] tax_unit_is_filer: true - aca_magi_fraction: 2.50 + aca_magi: 25_000 + tax_unit_fpg: + 2025: 10_000 households: household: members: [person1] @@ -41,7 +43,9 @@ tax_units: tax_unit: members: [person1] - aca_magi_fraction: 4.05 + aca_magi: 40_500 + tax_unit_fpg: + 2025: 10_000 households: household: members: [person1] @@ -60,7 +64,9 @@ tax_unit: members: [person1] tax_unit_is_filer: true - aca_magi_fraction: 4.00 + aca_magi: 40_000 + tax_unit_fpg: + 2025: 10_000 households: household: members: [person1] @@ -78,7 +84,9 @@ tax_units: tax_unit: members: [person1] - aca_magi_fraction: 2.50 + aca_magi: 25_000 + tax_unit_fpg: + 2025: 10_000 households: household: members: [person1] @@ -96,7 +104,9 @@ tax_units: tax_unit: members: [person1] - aca_magi_fraction: 2.50 + aca_magi: 25_000 + tax_unit_fpg: + 2025: 10_000 households: household: members: [person1] @@ -114,7 +124,9 @@ tax_units: tax_unit: members: [person1] - aca_magi_fraction: 2.50 + aca_magi: 25_000 + tax_unit_fpg: + 2024: 10_000 households: household: members: [person1] @@ -132,7 +144,9 @@ tax_units: tax_unit: members: [person1] - aca_magi_fraction: 2.50 + aca_magi: 25_000 + tax_unit_fpg: + 2027: 10_000 households: household: members: [person1] @@ -154,7 +168,9 @@ tax_unit: members: [person1, person2] tax_unit_is_filer: true - aca_magi_fraction: 3.00 + aca_magi: 30_000 + tax_unit_fpg: + 2025: 10_000 households: household: members: [person1, person2] @@ -176,7 +192,9 @@ tax_unit: members: [person1] tax_unit_is_filer: true - aca_magi_fraction: 0 + aca_magi: 0 + tax_unit_fpg: + 2025: 10_000 households: household: members: [person1] @@ -195,7 +213,9 @@ tax_unit: members: [person1] tax_unit_is_filer: true - aca_magi_fraction: 3.99 + aca_magi: 39_900 + tax_unit_fpg: + 2025: 10_000 households: household: members: [person1] @@ -216,7 +236,9 @@ tax_unit: members: [person1] tax_unit_is_filer: true - aca_magi_fraction: 2.50 + aca_magi: 25_000 + tax_unit_fpg: + 2026: 10_000 households: household: members: [person1] @@ -236,7 +258,9 @@ tax_units: tax_unit: members: [person1] - aca_magi_fraction: 4.01 + aca_magi: 40_100 + tax_unit_fpg: + 2025: 10_000 households: household: members: [person1] @@ -262,7 +286,9 @@ tax_unit_is_filer: false # Nor does it take up the federal credit, so it files no return to claim it. takes_up_aca_if_eligible: false - aca_magi_fraction: 2.50 + aca_magi: 25_000 + tax_unit_fpg: + 2025: 10_000 households: household: members: [person1] @@ -344,3 +370,69 @@ state_code: NM output: nm_premium_assistance_eligible: true + +- name: Case 14, $62,600 (exactly 400% of the 2025 guideline) is base-eligible. + # The PY2026 manual: "consumers with household income over 400% of the FPL do + # not qualify". is_aca_ptc_eligible is supplied to isolate the state ceiling. + period: 2026 + input: + people: + person1: + age: 40 + is_aca_ptc_eligible: true + tax_units: + tax_unit: + members: [person1] + files_return_for_aca_ptc: true + aca_magi: 62_600 + households: + household: + members: [person1] + state_code: NM + output: + aca_magi_fraction: 4.00 + nm_premium_assistance_eligible: true + +- name: Case 15, $62,600.47 (400.003%) is base-ineligible though truncated to 4.00. + # The PY2026 manual: "consumers with household income over 400% of the FPL do + # not qualify". is_aca_ptc_eligible is supplied to isolate the state ceiling. + period: 2026 + input: + people: + person1: + age: 40 + is_aca_ptc_eligible: true + tax_units: + tax_unit: + members: [person1] + files_return_for_aca_ptc: true + aca_magi: 62_600.47 + households: + household: + members: [person1] + state_code: NM + output: + aca_magi_fraction: 4.00 + nm_premium_assistance_eligible: false + +- name: Case 16, $62,755.56 (400.994%) is base-ineligible though truncated to 4.00. + # The PY2026 manual: "consumers with household income over 400% of the FPL do + # not qualify". is_aca_ptc_eligible is supplied to isolate the state ceiling. + period: 2026 + input: + people: + person1: + age: 40 + is_aca_ptc_eligible: true + tax_units: + tax_unit: + members: [person1] + files_return_for_aca_ptc: true + aca_magi: 62_755.56 + households: + household: + members: [person1] + state_code: NM + output: + aca_magi_fraction: 4.00 + nm_premium_assistance_eligible: false diff --git a/policyengine_us/tests/policy/baseline/gov/states/nm/hca/premium_assistance/nm_premium_assistance_mih_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/nm/hca/premium_assistance/nm_premium_assistance_mih_eligible.yaml index 8f51d63d7df..7aafbfe4fff 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/nm/hca/premium_assistance/nm_premium_assistance_mih_eligible.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/nm/hca/premium_assistance/nm_premium_assistance_mih_eligible.yaml @@ -22,7 +22,9 @@ members: [person1] filing_status: SINGLE tax_unit_is_filer: true - aca_magi_fraction: 4.20 + aca_magi: 42_000 + tax_unit_fpg: + 2025: 10_000 households: household: members: [person1] @@ -44,7 +46,9 @@ members: [person1] filing_status: SINGLE tax_unit_is_filer: true - aca_magi_fraction: 4.20 + aca_magi: 42_000 + tax_unit_fpg: + 2025: 10_000 households: household: members: [person1] @@ -69,7 +73,9 @@ members: [person1, person2] filing_status: JOINT tax_unit_is_filer: true - aca_magi_fraction: 4.50 + aca_magi: 45_000 + tax_unit_fpg: + 2025: 10_000 households: household: members: [person1, person2] @@ -94,7 +100,9 @@ members: [person1, person2] filing_status: SEPARATE tax_unit_is_filer: true - aca_magi_fraction: 4.20 + aca_magi: 42_000 + tax_unit_fpg: + 2025: 10_000 households: household: members: [person1, person2] @@ -116,7 +124,9 @@ members: [person1] filing_status: SINGLE tax_unit_is_filer: true - aca_magi_fraction: 4.00 + aca_magi: 40_000 + tax_unit_fpg: + 2025: 10_000 households: household: members: [person1] @@ -138,7 +148,9 @@ members: [person1] filing_status: SINGLE tax_unit_is_filer: true - aca_magi_fraction: 4.01 + aca_magi: 40_100 + tax_unit_fpg: + 2025: 10_000 households: household: members: [person1] @@ -159,7 +171,9 @@ members: [person1] filing_status: SINGLE tax_unit_is_filer: true - aca_magi_fraction: 4.20 + aca_magi: 42_000 + tax_unit_fpg: + 2025: 10_000 households: household: members: [person1] @@ -181,7 +195,9 @@ members: [person1] filing_status: SINGLE tax_unit_is_filer: true - aca_magi_fraction: 4.20 + aca_magi: 42_000 + tax_unit_fpg: + 2024: 10_000 households: household: members: [person1] @@ -205,7 +221,9 @@ members: [person1] filing_status: SINGLE tax_unit_is_filer: true - aca_magi_fraction: 4.20 + aca_magi: 42_000 + tax_unit_fpg: + 2027: 10_000 households: household: members: [person1] @@ -230,7 +248,9 @@ members: [person1] filing_status: SINGLE tax_unit_is_filer: false - aca_magi_fraction: 4.20 + aca_magi: 42_000 + tax_unit_fpg: + 2025: 10_000 households: household: members: [person1] @@ -255,10 +275,78 @@ filing_status: SINGLE tax_unit_is_filer: false takes_up_aca_if_eligible: true - aca_magi_fraction: 4.20 + aca_magi: 42_000 + tax_unit_fpg: + 2025: 10_000 households: household: members: [person1] state_code: NM output: nm_premium_assistance_mih_eligible: false + +- name: Case 11, $62,600 (exactly 400% of the 2025 guideline) is not above 400%. + # Addendum 1, Section I.A(3): "Have a household income above 400% of the FPL." + period: 2026 + input: + people: + person1: + age: 40 + pays_aca_premium: true + tax_units: + tax_unit: + members: [person1] + filing_status: SINGLE + tax_unit_is_filer: true + aca_magi: 62_600 + households: + household: + members: [person1] + state_code: NM + output: + aca_magi_fraction: 4.00 + nm_premium_assistance_mih_eligible: false + +- name: Case 12, $62,600.47 (400.003%) is above 400% though truncated to 4.00. + # Addendum 1, Section I.A(3): "Have a household income above 400% of the FPL." + period: 2026 + input: + people: + person1: + age: 40 + pays_aca_premium: true + tax_units: + tax_unit: + members: [person1] + filing_status: SINGLE + tax_unit_is_filer: true + aca_magi: 62_600.47 + households: + household: + members: [person1] + state_code: NM + output: + aca_magi_fraction: 4.00 + nm_premium_assistance_mih_eligible: true + +- name: Case 13, $62,755.56 (400.994%) is above 400% though truncated to 4.00. + # Addendum 1, Section I.A(3): "Have a household income above 400% of the FPL." + period: 2026 + input: + people: + person1: + age: 40 + pays_aca_premium: true + tax_units: + tax_unit: + members: [person1] + filing_status: SINGLE + tax_unit_is_filer: true + aca_magi: 62_755.56 + households: + household: + members: [person1] + state_code: NM + output: + aca_magi_fraction: 4.00 + nm_premium_assistance_mih_eligible: true diff --git a/policyengine_us/variables/gov/states/nm/hca/premium_assistance/nm_premium_assistance.py b/policyengine_us/variables/gov/states/nm/hca/premium_assistance/nm_premium_assistance.py index df33b423c65..d6f888af6fb 100644 --- a/policyengine_us/variables/gov/states/nm/hca/premium_assistance/nm_premium_assistance.py +++ b/policyengine_us/variables/gov/states/nm/hca/premium_assistance/nm_premium_assistance.py @@ -40,17 +40,20 @@ def formula(tax_unit, period, parameters): # called from this YEAR formula. slcsp = add(tax_unit, period, ["slcsp"]) aca_ptc = tax_unit("aca_ptc", period) - magi_frac = tax_unit("aca_magi_fraction", period) + # The exact ratio of income to the prior-year guideline, not the + # truncated aca_magi_fraction, which maps 200.00%-200.99% to 2.00. + exact_magi_frac = income / tax_unit("tax_unit_fpg", period.last_year) # Base NMPA: gross up the benchmark by 10% up to the gross-up FPL limit, # subtract the federal APTC and the target contribution, floor at zero. - # PY2026 treats the 200% FPL limit as inclusive; PY2027 treats it as - # strict (below 200% FPL), toggled by benchmark_gross_up.boundary_inclusive. + # PY2026 treats the 200% FPL limit as inclusive ("up to 200% FPL"; the + # next range starts at "200.01"); PY2027 treats it as strict ("under + # 200% (< 200%)"), toggled by benchmark_gross_up.boundary_inclusive. limit = p.benchmark_gross_up.fpl_limit gross_up_applies = where( p.benchmark_gross_up.boundary_inclusive, - magi_frac <= limit, - magi_frac < limit, + exact_magi_frac <= limit, + exact_magi_frac < limit, ) gross_up = where(gross_up_applies, 1 + p.benchmark_gross_up.rate, 1) benchmark = slcsp * gross_up diff --git a/policyengine_us/variables/gov/states/nm/hca/premium_assistance/nm_premium_assistance_eligible.py b/policyengine_us/variables/gov/states/nm/hca/premium_assistance/nm_premium_assistance_eligible.py index 0ca623a510c..e572facb5be 100644 --- a/policyengine_us/variables/gov/states/nm/hca/premium_assistance/nm_premium_assistance_eligible.py +++ b/policyengine_us/variables/gov/states/nm/hca/premium_assistance/nm_premium_assistance_eligible.py @@ -27,6 +27,11 @@ def formula(tax_unit, period, parameters): # (IRC 36B(a); aca_ptc does), so gate it explicitly here, as aca_ptc # does. is_filer = tax_unit("files_return_for_aca_ptc", period) - magi_frac = tax_unit("aca_magi_fraction", period) - income_eligible = magi_frac <= p.fpl_limit + # The PY2026 manual excludes "consumers with household income over + # 400% of the FPL". Test the exact ratio, not the truncated + # aca_magi_fraction: truncation maps 400.00%-400.99% to 4.00, which + # would admit incomes above 400%. + magi = max_(tax_unit("aca_magi", period), 0) + exact_magi_frac = magi / tax_unit("tax_unit_fpg", period.last_year) + income_eligible = exact_magi_frac <= p.fpl_limit return in_effect & aptc_eligible & is_filer & income_eligible diff --git a/policyengine_us/variables/gov/states/nm/hca/premium_assistance/nm_premium_assistance_mih_eligible.py b/policyengine_us/variables/gov/states/nm/hca/premium_assistance/nm_premium_assistance_mih_eligible.py index 718534d46ea..527bc11903b 100644 --- a/policyengine_us/variables/gov/states/nm/hca/premium_assistance/nm_premium_assistance_mih_eligible.py +++ b/policyengine_us/variables/gov/states/nm/hca/premium_assistance/nm_premium_assistance_mih_eligible.py @@ -39,6 +39,11 @@ def formula(tax_unit, period, parameters): # stand for filing (files_return_for_aca_ptc); only filing for another # reason counts. is_filer = tax_unit("tax_unit_is_filer", period) - magi_frac = tax_unit("aca_magi_fraction", period) - income_eligible = magi_frac > p.fpl_limit + # Addendum 1, Section I.A(3): "Have a household income above 400% of + # the FPL." Test the exact ratio, not the truncated aca_magi_fraction: + # truncation maps 400.00%-400.99% to 4.00, which would leave incomes + # just above 400% out of both this component and the base program. + magi = max_(tax_unit("aca_magi", period), 0) + exact_magi_frac = magi / tax_unit("tax_unit_fpg", period.last_year) + income_eligible = exact_magi_frac > p.fpl_limit return in_effect & pays_premium & not_separate & is_filer & income_eligible