diff --git a/changelog.d/ca-premium-subsidy-165-exact-ratio.fixed.md b/changelog.d/ca-premium-subsidy-165-exact-ratio.fixed.md new file mode 100644 index 00000000000..aad77321e4d --- /dev/null +++ b/changelog.d/ca-premium-subsidy-165-exact-ratio.fixed.md @@ -0,0 +1 @@ +Decide California Premium Subsidy eligibility on the exact ratio of nonnegative ACA MAGI to prior-year FPL instead of the truncated `aca_magi_fraction`, so PY2026 incomes from 165.003% to 165.994% of FPL are no longer eligible ([2026 Program Design, (d)(7)](https://board.coveredca.com/meetings/2025/July%2028,%202025/CoveredCA_2026_Premium_Subsidy_Program_Design_Final.pdf#page=2): "at or below 165 percent"). Move PY2027 from the superseded Program Design V4 to the [Final 2027 Program Design, (d)(4) and (d)(7)](https://hbex.coveredca.com/data-research/Final-2027-California-Premium-Subsidy-Program-Design.pdf#page=2): 0% up to 150% FPL, 2.8% rising to 5.28% above 150% up to 200% (replacing V4's 2.69%-3.41% and 3.41%-5.1% bands), and eligibility "at or below 200 percent", so households at exactly 200% FPL now qualify. diff --git a/changelog.d/statute-first-ca-premium-subsidy.fixed.md b/changelog.d/statute-first-ca-premium-subsidy.fixed.md new file mode 100644 index 00000000000..edcb4be4da5 --- /dev/null +++ b/changelog.d/statute-first-ca-premium-subsidy.fixed.md @@ -0,0 +1 @@ +Select California Premium Subsidy bands from exact nonnegative ACA MAGI divided by prior-year FPL under the adopted [2026 Program Design, (d)(4)](https://board.coveredca.com/meetings/2025/July%2028,%202025/CoveredCA_2026_Premium_Subsidy_Program_Design_Final.pdf#page=2): exactly 150% changes from a 3.19% contribution to 0%, while income above 150% enters the 3.19% band. Retain the truncated fraction for the rate within that band, following [FTB 3849 Table 1-2 (2020)](https://www.ftb.ca.gov/forms/2020/2020-3849-instructions.pdf#page=6) and [2021](https://www.ftb.ca.gov/forms/2021/2021-3849-instructions.pdf#page=6); [2025 Form 8962 Worksheet 2 line 4](https://www.irs.gov/pub/irs-prior/i8962--2025.pdf#page=9) likewise tests "more than" on the exact amount before truncating. diff --git a/policyengine_us/parameters/gov/states/ca/hbex/premium_subsidy/applicable_percentage/final.yaml b/policyengine_us/parameters/gov/states/ca/hbex/premium_subsidy/applicable_percentage/final.yaml index 76f17788fdf..e7ebb6e31fa 100644 --- a/policyengine_us/parameters/gov/states/ca/hbex/premium_subsidy/applicable_percentage/final.yaml +++ b/policyengine_us/parameters/gov/states/ca/hbex/premium_subsidy/applicable_percentage/final.yaml @@ -4,12 +4,10 @@ values: 2026-01-01: - 0 # 100-150%: flat 0% - 0.0391 # 150-165%: ends 3.91% - # PY2027 Program Design V4 (adopted Board Res. 2026-22). - # Bands: 100-150% FPL, 150-165% FPL, 165-200% FPL. + # Final 2027 Program Design. Bands: 100-150% FPL, 150-200% FPL. 2027-01-01: - 0 # 100-150%: flat 0% - - 0.0341 # 150-165%: ends 3.41% - - 0.051 # 165-200%: ends 5.1% + - 0.0528 # 150-200%: ends 5.28% metadata: unit: /1 @@ -20,7 +18,7 @@ metadata: href: https://board.coveredca.com/meetings/2025/July%2028,%202025/CoveredCA_2026_Premium_Subsidy_Program_Design_Final.pdf#page=2 - title: 2026 California State Premium Subsidy Policy Explainer, Table 1 href: https://hbex.coveredca.com/stakeholders/PDFs/2026-02_StatePremiumSub_PolicyExplainer-Final.pdf#page=2 - - title: 2027 California Premium Subsidy Program Design V4, subdiv. (d)(4) - href: https://board.coveredca.com/meetings/2026/May%2021,%202026/CoveredCA_2027_Premium_Subsidy_Program_Design_V4_05-19-26.pdf#page=2 - - title: Covered California Board Resolution 2026-22 - href: https://board.coveredca.com/meetings/2026/June%2018,%202026/2026-22.pdf + - title: Final 2027 California Premium Subsidy Program Design, subdiv. (d)(4) + href: https://hbex.coveredca.com/data-research/Final-2027-California-Premium-Subsidy-Program-Design.pdf#page=2 + - title: 2027 California State Premium Subsidy Program Insight, Table 1 + href: https://hbex.coveredca.com/stakeholders/PDFs/2026-03_StatePremiumSub2027_Insight_Final.pdf#page=2 diff --git a/policyengine_us/parameters/gov/states/ca/hbex/premium_subsidy/applicable_percentage/initial.yaml b/policyengine_us/parameters/gov/states/ca/hbex/premium_subsidy/applicable_percentage/initial.yaml index 5496e66f5ef..a1fafd8c2cd 100644 --- a/policyengine_us/parameters/gov/states/ca/hbex/premium_subsidy/applicable_percentage/initial.yaml +++ b/policyengine_us/parameters/gov/states/ca/hbex/premium_subsidy/applicable_percentage/initial.yaml @@ -4,12 +4,10 @@ values: 2026-01-01: - 0 # 100-150%: flat 0% - 0.0319 # 150-165%: starts 3.19% - # PY2027 Program Design V4 (adopted Board Res. 2026-22). - # Bands: 100-150% FPL, 150-165% FPL, 165-200% FPL. + # Final 2027 Program Design. Bands: 100-150% FPL, 150-200% FPL. 2027-01-01: - - 0 # 100-150%: flat 0% - - 0.0269 # 150-165%: starts 2.69% - - 0.0341 # 165-200%: starts 3.41% + - 0 # 100-150%: flat 0% + - 0.028 # 150-200%: starts 2.8% metadata: unit: /1 @@ -20,7 +18,7 @@ metadata: href: https://board.coveredca.com/meetings/2025/July%2028,%202025/CoveredCA_2026_Premium_Subsidy_Program_Design_Final.pdf#page=2 - title: 2026 California State Premium Subsidy Policy Explainer, Table 1 href: https://hbex.coveredca.com/stakeholders/PDFs/2026-02_StatePremiumSub_PolicyExplainer-Final.pdf#page=2 - - title: 2027 California Premium Subsidy Program Design V4, subdiv. (d)(4) - href: https://board.coveredca.com/meetings/2026/May%2021,%202026/CoveredCA_2027_Premium_Subsidy_Program_Design_V4_05-19-26.pdf#page=2 - - title: Covered California Board Resolution 2026-22 - href: https://board.coveredca.com/meetings/2026/June%2018,%202026/2026-22.pdf + - title: Final 2027 California Premium Subsidy Program Design, subdiv. (d)(4) + href: https://hbex.coveredca.com/data-research/Final-2027-California-Premium-Subsidy-Program-Design.pdf#page=2 + - title: 2027 California State Premium Subsidy Program Insight, Table 1 + href: https://hbex.coveredca.com/stakeholders/PDFs/2026-03_StatePremiumSub2027_Insight_Final.pdf#page=2 diff --git a/policyengine_us/parameters/gov/states/ca/hbex/premium_subsidy/applicable_percentage/threshold.yaml b/policyengine_us/parameters/gov/states/ca/hbex/premium_subsidy/applicable_percentage/threshold.yaml index 63e30cf9e77..3af7d80bda5 100644 --- a/policyengine_us/parameters/gov/states/ca/hbex/premium_subsidy/applicable_percentage/threshold.yaml +++ b/policyengine_us/parameters/gov/states/ca/hbex/premium_subsidy/applicable_percentage/threshold.yaml @@ -13,12 +13,11 @@ values: - 1 - 1.5 - 1.65 - # PY2027 Program Design V4 (adopted Board Res. 2026-22) widens the top band - # to under 200% FPL, adding a 165-200% bracket. + # The Final 2027 Program Design has two bands, "100% up to 150%" (0%) and + # "Above 150% up to 200%" (2.8% to 5.28%), with no knot at 165%. 2027-01-01: - 1 - 1.5 - - 1.65 - 2.0 metadata: @@ -30,7 +29,5 @@ metadata: href: https://board.coveredca.com/meetings/2025/July%2028,%202025/CoveredCA_2026_Premium_Subsidy_Program_Design_Final.pdf#page=2 - title: 2026 California State Premium Subsidy Policy Explainer, Table 1 href: https://hbex.coveredca.com/stakeholders/PDFs/2026-02_StatePremiumSub_PolicyExplainer-Final.pdf#page=2 - - title: 2027 California Premium Subsidy Program Design V4, subdiv. (d)(4) - href: https://board.coveredca.com/meetings/2026/May%2021,%202026/CoveredCA_2027_Premium_Subsidy_Program_Design_V4_05-19-26.pdf#page=2 - - title: Covered California Board Resolution 2026-22 - href: https://board.coveredca.com/meetings/2026/June%2018,%202026/2026-22.pdf + - title: Final 2027 California Premium Subsidy Program Design, subdiv. (d)(4) and (d)(7) + href: https://hbex.coveredca.com/data-research/Final-2027-California-Premium-Subsidy-Program-Design.pdf#page=2 diff --git a/policyengine_us/parameters/gov/states/ca/hbex/premium_subsidy/fpl_limit.yaml b/policyengine_us/parameters/gov/states/ca/hbex/premium_subsidy/fpl_limit.yaml index d50b1ad09c0..baf2ec7dfec 100644 --- a/policyengine_us/parameters/gov/states/ca/hbex/premium_subsidy/fpl_limit.yaml +++ b/policyengine_us/parameters/gov/states/ca/hbex/premium_subsidy/fpl_limit.yaml @@ -1,9 +1,16 @@ description: California limits income eligibility to this share of the federal poverty line under the California Premium Subsidy program. values: + # Each limit is inclusive and applies to the exact income-to-FPL ratio, not + # the truncated aca_magi_fraction. + # PY2026 Program Design (d)(7): "at or below 165 percent". 2026-01-01: 1.65 - # PY2027 Program Design V4 (adopted Board Res. 2026-22) extends eligibility to - # "under 200%" FPL; with 2-decimal FPL-percentage truncation, <= 1.99 == "<200%". - 2027-01-01: 1.99 + # Final 2027 Program Design (d)(7): "at or below 200 percent". + # The OE27 enroller deck conflicts with (d)(7) at 200%: its summary slide says + # the subsidy "applies only to enrollees up to under 200% FPL", while its own + # worked example at exactly 200% FPL applies the 5.28% rate. Follow the + # adopted Design. + # Candidate: us-ca:premium-subsidy-200-fpl-limit until registered. + 2027-01-01: 2.0 metadata: unit: /1 @@ -14,7 +21,9 @@ metadata: href: https://board.coveredca.com/meetings/2025/July%2028,%202025/CoveredCA_2026_Premium_Subsidy_Program_Design_Final.pdf#page=2 - title: 2026 California State Premium Subsidy Policy Explainer, eligibility href: https://hbex.coveredca.com/stakeholders/PDFs/2026-02_StatePremiumSub_PolicyExplainer-Final.pdf#page=1 - - title: 2027 California Premium Subsidy Program Design V4, subdiv. (d)(7) - href: https://board.coveredca.com/meetings/2026/May%2021,%202026/CoveredCA_2027_Premium_Subsidy_Program_Design_V4_05-19-26.pdf#page=2 - - title: Covered California Board Resolution 2026-22 - href: https://board.coveredca.com/meetings/2026/June%2018,%202026/2026-22.pdf + - title: Final 2027 California Premium Subsidy Program Design, subdiv. (d)(7) + href: https://hbex.coveredca.com/data-research/Final-2027-California-Premium-Subsidy-Program-Design.pdf#page=2 + - title: 2027 California State Premium Subsidy Program Insight, eligibility + href: https://hbex.coveredca.com/stakeholders/PDFs/2026-03_StatePremiumSub2027_Insight_Final.pdf#page=1 + - title: "Covered California OE 2027 Enroller Kick-off, 200% FPL example #page 37-40" + href: https://hbex.coveredca.com/toolkit/webinars-briefings/downloads/OE27_Enroller_Kickoff.pdf#page=37 diff --git a/policyengine_us/parameters/gov/states/ca/hbex/premium_subsidy/in_effect.yaml b/policyengine_us/parameters/gov/states/ca/hbex/premium_subsidy/in_effect.yaml index 8602e85fce9..594aae370db 100644 --- a/policyengine_us/parameters/gov/states/ca/hbex/premium_subsidy/in_effect.yaml +++ b/policyengine_us/parameters/gov/states/ca/hbex/premium_subsidy/in_effect.yaml @@ -1,13 +1,15 @@ description: California provides state premium subsidies when this is true under the California Premium Subsidy program. values: # The provision runs from PY2026, matching the New Mexico and Maryland approach - # (ending 2028-01-01). PY2026 and PY2027 both have board-adopted, verified - # program designs: the PY2027 Program Design V4 (adopted Board Resolution - # 2026-22, June 18 2026; funded by the 2026 Budget Act $300M HCARF) changes the - # schedule, so PY2027 is NOT a carry-forward of PY2026 (see the applicable - # percentage and fpl_limit files). Section 100800(c) has the Exchange adopt the - # design annually upon appropriation and Section 100825 creates no entitlement, - # so the provision ends 2028-01-01 absent a verified PY2028 design. + # (ending 2028-01-01). PY2026 and PY2027 both have adopted, verified program + # designs: the Final 2027 Program Design (adopted under item 4800-101-3381 of + # the Budget Act of 2026; the 2027 Insight reports $300M appropriated from + # the Health Care Affordability Reserve Fund) changes the schedule, so PY2027 + # is NOT a carry-forward of PY2026 (see the applicable percentage and + # fpl_limit files). + # Section 100800(c) has the Exchange adopt the design annually upon + # appropriation and Section 100825 creates no entitlement, so the provision + # ends 2028-01-01 absent a verified PY2028 design. # # The Program Design's enhanced-PTC trigger ("state subsidy applies if the # federal enhanced PTC is not extended by Sept 30 2025") fired, so the residual @@ -25,6 +27,10 @@ metadata: reference: - title: 2026 California Premium Subsidy Program Design, preamble href: https://board.coveredca.com/meetings/2025/July%2028,%202025/CoveredCA_2026_Premium_Subsidy_Program_Design_Final.pdf#page=1 + - title: Final 2027 California Premium Subsidy Program Design, preamble + href: https://hbex.coveredca.com/data-research/Final-2027-California-Premium-Subsidy-Program-Design.pdf#page=1 + - title: 2027 California State Premium Subsidy Program Insight, background + href: https://hbex.coveredca.com/stakeholders/PDFs/2026-03_StatePremiumSub2027_Insight_Final.pdf#page=1 - title: CA Gov. Code Section 100800(c) (Exchange adopts annual program design upon appropriation) href: https://leginfo.legislature.ca.gov/faces/codes_displaySection.xhtml?lawCode=GOV§ionNum=100800. - title: CA Gov. Code Section 100825 diff --git a/policyengine_us/tests/code_health/test_source_conflicts.py b/policyengine_us/tests/code_health/test_source_conflicts.py index a27d5755ced..938518754b4 100644 --- a/policyengine_us/tests/code_health/test_source_conflicts.py +++ b/policyengine_us/tests/code_health/test_source_conflicts.py @@ -125,6 +125,7 @@ "policyengine_us/parameters/gov/ssa/ssi/income/ism/food_counts.yaml": 1, "policyengine_us/parameters/gov/states/ar/ade/oec/sra/rates/no_copay_smi_threshold.yaml": 1, "policyengine_us/parameters/gov/states/ca/cpuc/income_sources.yaml": 1, + "policyengine_us/parameters/gov/states/ca/hbex/premium_subsidy/fpl_limit.yaml": 1, "policyengine_us/parameters/gov/states/co/doi/premium_assistance/amount/additional_member.yaml": 1, "policyengine_us/parameters/gov/states/co/doi/premium_assistance/amount/first_member.yaml": 1, "policyengine_us/parameters/gov/states/fl/commerce/liheap/payment/base_amount.yaml": 1, diff --git a/policyengine_us/tests/policy/baseline/gov/states/ca/hbex/premium_subsidy/ca_premium_subsidy.yaml b/policyengine_us/tests/policy/baseline/gov/states/ca/hbex/premium_subsidy/ca_premium_subsidy.yaml index da255575843..e9fbfbaa428 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ca/hbex/premium_subsidy/ca_premium_subsidy.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ca/hbex/premium_subsidy/ca_premium_subsidy.yaml @@ -13,6 +13,7 @@ # The negative-MAGI clamp case supplies eligibility and a positive rate to # isolate the amount formula; its income fraction is correctly zero. # CA schedule: threshold [1.0, 1.5, 1.65], initial [0, 0.0319], final [0, 0.0391] +# PY2027 (Final Design): threshold [1.0, 1.5, 2.0], initial [0, 0.028], final [0, 0.0528] # ---- 100-150% FPL band (applicable percentage 0%) ---- @@ -99,8 +100,9 @@ ca_premium_subsidy: 3_112.50 - name: Case 4, exactly 165% FPL positive subsidy (pct 3.91%, top of the ramp). - # pct = 0.0391 (final rate). income 26_000. - # amount = max(0, 7_000 - 2_500 - 0.0391*26_000) = 7_000 - 2_500 - 1_016.60 = 3_483.40. + # pct = 0.0391 (final rate). income 25_822.50 = 165% of the 2025 one-person + # FPG $15,650, the highest income "at or below 165 percent". + # amount = max(0, 7_000 - 2_500 - 0.0391*25_822.50) = 7_000 - 2_500 - 1_009.66 = 3_490.34. period: 2026 absolute_error_margin: 0.01 input: @@ -112,8 +114,8 @@ tax_unit: members: [person1] tax_unit_fpg: - 2025: 15_757.575757575758 - aca_magi: 26_000 + 2025: 15_650 + aca_magi: 25_822.50 aca_magi_fraction: 1.65 slcsp: 7_000 aca_ptc: 2_500 @@ -123,7 +125,8 @@ members: [person1] state_code: CA output: - ca_premium_subsidy: 3_483.40 + ca_premium_subsidy_eligible: true + ca_premium_subsidy: 3_490.34 - name: Case 5, 160% FPL with expected contribution exceeding the gap -> $0 floor. # pct = 0.0319 + (2/3)*0.0072 = 0.0367. income 24_000 -> 0.0367*24_000 = 880.80. @@ -259,6 +262,7 @@ aca_magi_fraction: 1.66 slcsp: 6_000 aca_ptc: 2_000 + tax_unit_is_filer: true households: household: members: [person1] @@ -295,9 +299,10 @@ # ---- Floor edge with a positive amount ---- - name: Case 11, exactly at the 100% FPL floor (1.0) with a positive subsidy. - # magi_frac 1.0 -> pct 0 (flat 100-150% band). Eligible: 1.0 >= fpl_floor 1.0, - # <= fpl_limit 1.65, in effect, APTC-eligible. income 15_000. - # amount = max(0, 6_000 - 3_500 - 0*15_000) = 2_500. Confirms the floor edge is + # magi_frac 1.0 -> pct 0 (flat 100-150% band). Eligible: income 15_650 is + # exactly the 2025 one-person FPG, so 1.0 >= fpl_floor 1.0, <= fpl_limit 1.65, + # in effect, APTC-eligible. + # amount = max(0, 6_000 - 3_500 - 0*15_650) = 2_500. Confirms the floor edge is # eligible AND pays the full post-APTC gap (pct is 0 at the bottom of the band). period: 2026 absolute_error_margin: 0.01 @@ -310,8 +315,8 @@ tax_unit: members: [person1] tax_unit_fpg: - 2025: 15_000 - aca_magi: 15_000 + 2025: 15_650 + aca_magi: 15_650 aca_magi_fraction: 1.00 slcsp: 6_000 aca_ptc: 3_500 @@ -321,6 +326,7 @@ members: [person1] state_code: CA output: + ca_premium_subsidy_eligible: true ca_premium_subsidy: 2_500 # ---- Mid-band interaction: APTC and pct both active, residual positive ---- @@ -409,6 +415,7 @@ aca_magi_fraction: 1.66 slcsp: 6_000 aca_ptc: 2_000 + tax_unit_is_filer: true households: household: members: [person1] @@ -439,6 +446,7 @@ aca_magi_fraction: 0.99 slcsp: 6_000 aca_ptc: 2_000 + tax_unit_is_filer: true households: household: members: [person1] @@ -447,15 +455,16 @@ ca_premium_subsidy_applicable_percentage: 0 ca_premium_subsidy: 0 -# ---- PY2027 carry-forward (positive amount) ---- +# ---- PY2027 Final Program Design (positive amount) ---- - name: Case 16, PY2027 pays a positive subsidy at 157.5% FPL (revised schedule). - # in_effect is true through PY2027 and the schedule CHANGED (Board Res. 2026-22): - # the 150-165% band is now initial 0.0269 -> final 0.0341, so - # magi_frac 1.575 -> pct 0.0269 + 0.5*(0.0341-0.0269) = 0.0269 + 0.0036 = 0.0305. - # income 25_000. amount = max(0, 6_000 - 2_000 - 0.0305*25_000) - # = 6_000 - 2_000 - 762.50 = 3_237.50 (up from the 2026 value of 3_112.50 at the - # same FPL because the PY2027 applicable percentage in this band is lower). + # in_effect is true through PY2027 and the Final 2027 Program Design (d)(4) + # changes the schedule: one paying band "Above 150% up to 200%", initial + # 0.028 -> final 0.0528. magi_frac 1.575 -> position (1.575-1.5)/0.5 = 0.15 + # -> pct 0.028 + 0.15*0.0248 = 0.03172. income 25_000. + # amount = max(0, 6_000 - 2_000 - 0.03172*25_000) = 6_000 - 2_000 - 793 = 3_207 + # (up from the 2026 value of 3_112.50 at the same FPL because the PY2027 + # applicable percentage at this point is lower). period: 2027 absolute_error_margin: 0.01 input: @@ -478,7 +487,7 @@ members: [person1] state_code: CA output: - ca_premium_subsidy: 3_237.50 + ca_premium_subsidy: 3_207 # ---- Floor from the SLCSP == APTC direction ---- @@ -551,3 +560,43 @@ ca_premium_subsidy_eligible: true ca_premium_subsidy_applicable_percentage: 0.0355 ca_premium_subsidy: 3_881.75 + +# ---- PY2027 upper limit (Covered California's worked example) ---- + +- name: Case 19, PY2027 exactly 200% FPL pays the 1.5-point gap (OE27 example). + # The OE27 enroller deck (PDF pages 37-38) works a single 40-year-old in + # Sacramento County at $31,920, exactly 200% of the 2026 one-person FPG + # $15,960: benchmark $688 a month, federal required contribution 6.78%, + # state rate 5.28% (6.78% - 1.5%), state subsidy about $40 a month. + # slcsp = 688*12 = 8_256. aca_ptc = 8_256 - 0.0678*31_920 = 8_256 - 2_164.176 + # = 6_091.824. Eligible: 2.0 is "at or below 200 percent" (Design (d)(7)). + # amount = max(0, 8_256 - 6_091.824 - 0.0528*31_920) + # = 2_164.176 - 1_685.376 = 478.80 = 0.015*31_920, or $39.90 a month. + # aca_ptc is derived from the federal 2027 applicable percentage table + # (Rev. Proc. 2026-26, PolicyEngine/policyengine-us#10443), which gives 6.78% + # at exactly 200% FPL. The output pins that federal credit. + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + is_aca_ptc_eligible: true + tax_units: + tax_unit: + members: [person1] + tax_unit_fpg: + 2026: 15_960 + aca_magi: 31_920 + aca_magi_fraction: 2.00 + slcsp: 8_256 + tax_unit_is_filer: true + households: + household: + members: [person1] + state_code: CA + output: + aca_ptc: 6_091.824 + ca_premium_subsidy_eligible: true + ca_premium_subsidy_applicable_percentage: 0.0528 + ca_premium_subsidy: 478.80 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ca/hbex/premium_subsidy/ca_premium_subsidy_applicable_percentage.yaml b/policyengine_us/tests/policy/baseline/gov/states/ca/hbex/premium_subsidy/ca_premium_subsidy_applicable_percentage.yaml index a5b10f361ab..22f9e017b04 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ca/hbex/premium_subsidy/ca_premium_subsidy_applicable_percentage.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ca/hbex/premium_subsidy/ca_premium_subsidy_applicable_percentage.yaml @@ -7,6 +7,8 @@ # Bands (initial -> final over the FPL band): # 100-150% FPL: 0% (flat) # above 150-165% FPL: 3.19% -> 3.91% (interpolated linearly) +# PY2027 (Final 2027 Program Design (d)(4)): threshold [1.0, 1.5, 2.0], +# initial [0, 0.028], final [0, 0.0528]; above 150-200% FPL: 2.8% -> 5.28%. # The exact nonnegative MAGI / prior-year FPG ratio selects the band; the # truncated aca_magi_fraction determines the rate within it. Supplied-fraction # cases normalize prior-year FPG to $10,000 and supply consistent MAGI to @@ -292,13 +294,12 @@ output: ca_premium_subsidy_applicable_percentage: 0 -# ---- Carry-forward ---- +# ---- PY2027 Final Program Design ---- -- name: Case 12, PY2027 schedule at 157.5% FPL -> 3.05% (revised 150-165% band). - # PY2027 Program Design V4 (Board Res. 2026-22) changes the 150-165% band to - # initial 0.0269 -> final 0.0341. bracket 1 [1.5,1.65]: - # position (1.575-1.5)/0.15 = 0.5 -> 0.0269 + 0.5*(0.0341-0.0269) - # = 0.0269 + 0.5*0.0072 = 0.0269 + 0.0036 = 0.0305. +- name: Case 12, PY2027 157.5% FPL -> 3.172%. + # Final 2027 Program Design (d)(4): "Above 150% up to 200%", 2.8% -> 5.28%. + # bracket 1 [1.5,2.0]: position (1.575-1.5)/0.5 = 0.15 + # -> 0.028 + 0.15*(0.0528-0.028) = 0.028 + 0.15*0.0248 = 0.028 + 0.00372 = 0.03172. period: 2027 absolute_error_margin: 0.0001 input: @@ -318,15 +319,12 @@ members: [person1] state_code: CA output: - ca_premium_subsidy_applicable_percentage: 0.0305 + ca_premium_subsidy_applicable_percentage: 0.03172 -# ---- PY2027 new 165-200% FPL tier ---- - -- name: Case 13, PY2027 exactly 165% FPL -> 3.41% from either adjacent tier. - # PY2027 thresholds [1,1.5,1.65,2.0]. searchsorted(..., 1.65, "left") = 2, - # -1 = 1 -> bracket 1 [1.5,1.65] at position 1 -> its final rate 0.0341, - # which equals the 165-200% tier's initial rate, so the rate is continuous - # at 165% (2026 had 0.0391 at this point). +- name: Case 13, PY2027 exactly 165% FPL -> 3.544% (165% is not a PY2027 knot). + # PY2027 thresholds [1,1.5,2.0]. searchsorted(..., 1.65, "left") = 2, -1 = 1 + # -> bracket 1 [1.5,2.0]: position (1.65-1.5)/0.5 = 0.3 + # -> 0.028 + 0.3*0.0248 = 0.028 + 0.00744 = 0.03544. period: 2027 absolute_error_margin: 0.0001 input: @@ -346,11 +344,11 @@ members: [person1] state_code: CA output: - ca_premium_subsidy_applicable_percentage: 0.0341 + ca_premium_subsidy_applicable_percentage: 0.03544 -- name: Case 14, PY2027 182.5% FPL (midpoint of the new 165-200% tier) -> 4.255%. - # bracket 2 [1.65,2.0]: position (1.825-1.65)/0.35 = 0.5. - # 0.0341 + 0.5*(0.051-0.0341) = 0.0341 + 0.5*0.0169 = 0.0341 + 0.00845 = 0.04255. +- name: Case 14, PY2027 182.5% FPL -> 4.412%. + # bracket 1 [1.5,2.0]: position (1.825-1.5)/0.5 = 0.65 + # -> 0.028 + 0.65*0.0248 = 0.028 + 0.01612 = 0.04412. period: 2027 absolute_error_margin: 0.0001 input: @@ -370,12 +368,11 @@ members: [person1] state_code: CA output: - ca_premium_subsidy_applicable_percentage: 0.04255 + ca_premium_subsidy_applicable_percentage: 0.04412 -- name: Case 15, PY2027 199% FPL (top of the eligible band) -> ~5.05%. - # bracket 2 [1.65,2.0]: position (1.99-1.65)/0.35 = 0.34/0.35 = 0.971428... - # 0.0341 + 0.971428*(0.051-0.0341) = 0.0341 + 0.971428*0.0169 = 0.0341 + - # 0.016417 = 0.050517. Derived (non-round position); confirm against the model. +- name: Case 15, PY2027 199% FPL -> 5.2304%. + # bracket 1 [1.5,2.0]: position (1.99-1.5)/0.5 = 0.98 + # -> 0.028 + 0.98*0.0248 = 0.028 + 0.024304 = 0.052304. period: 2027 absolute_error_margin: 0.0001 input: @@ -395,7 +392,31 @@ members: [person1] state_code: CA output: - ca_premium_subsidy_applicable_percentage: 0.050517 # model computes 0.05051714 + ca_premium_subsidy_applicable_percentage: 0.052304 + +- name: Case 15a, PY2027 exactly 200% FPL -> 5.28% (top of the band). + # searchsorted([1,1.5,2.0], 2.0, "left") = 2, -1 = 1 -> bracket 1 [1.5,2.0] + # at position 1 -> the final rate 0.0528. + period: 2027 + absolute_error_margin: 0.0001 + input: + people: + person1: + age: 40 + is_aca_ptc_eligible: true + tax_units: + tax_unit: + members: [person1] + aca_magi: 20_000 + tax_unit_fpg: + 2026: 10_000 + aca_magi_fraction: 2.00 + households: + household: + members: [person1] + state_code: CA + output: + ca_premium_subsidy_applicable_percentage: 0.0528 - name: Case 2b, a supplied fraction of 1.505 interpolates on the fraction given. # searchsorted([1.0,1.5,1.65], 1.505, "left") = 2, -1 -> bracket 1 [1.5,1.65]. @@ -552,3 +573,43 @@ output: aca_magi_fraction: 1.50 ca_premium_subsidy_applicable_percentage: 0 + +- name: Case 16b, PY2027 raw income $23,942, just above 150% enters the 2.8% band. + # $23,942 / 2026 one-person FPG $15,960 = 150.0125%, "Above 150%". The + # truncated fraction 1.50 sits at the start of the band: rate 0.028. + period: 2027 + absolute_error_margin: 0.000001 + input: + people: + person1: + age: 40 + employment_income: + 2027: 23_942 + is_aca_ptc_eligible: true + households: + household: + members: [person1] + state_code: CA + output: + aca_magi_fraction: 1.50 + ca_premium_subsidy_applicable_percentage: 0.028 + +- name: Case 16c, PY2027 raw income $31,920, exactly 200% pays 5.28%. + # $31,920 / $15,960 = 200.000%. The OE27 enroller deck's 200% FPL example + # applies "5.28% (6.78% - 1.5%)" (PDF page 37). + period: 2027 + absolute_error_margin: 0.000001 + input: + people: + person1: + age: 40 + employment_income: + 2027: 31_920 + is_aca_ptc_eligible: true + households: + household: + members: [person1] + state_code: CA + output: + aca_magi_fraction: 2.00 + ca_premium_subsidy_applicable_percentage: 0.0528 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ca/hbex/premium_subsidy/ca_premium_subsidy_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/ca/hbex/premium_subsidy/ca_premium_subsidy_eligible.yaml index 85d82596a5a..34d90029643 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ca/hbex/premium_subsidy/ca_premium_subsidy_eligible.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ca/hbex/premium_subsidy/ca_premium_subsidy_eligible.yaml @@ -5,14 +5,20 @@ # married-filing-separately exclusion, and the federal FPL-band income test), # the tax unit is an applicable return filer, and ACA MAGI is at # or above the 100% FPL floor and at or below the FPL limit (1.65 in PY2026, -# 1.99 in PY2027). is_aca_ptc_eligible and aca_magi_fraction are supplied -# directly so the gate logic is tested in isolation from the premium-data lookup. -# fpl_floor = 1.0, fpl_limit = 1.65 (PY2026) +# 2.00 in PY2027). Program Design (d)(7) applies +# these limits to the exact ratio of nonnegative ACA MAGI to prior-year FPG, +# not to the truncated aca_magi_fraction (FTB 3849 Table 1-2 likewise decides +# band edges on the exact percentage). Supplied-income cases pin prior-year FPG +# to $10,000 and set aca_magi to the target ratio; is_aca_ptc_eligible is +# supplied so the gate logic is tested in isolation from the premium-data +# lookup. Income-derived cases use the actual prior-year one-person FPG +# ($15,650 for PY2026, $15,960 for PY2027). +# fpl_floor = 1.0; fpl_limit = 1.65 (PY2026), 2.0 (PY2027), both inclusive # ---- Income-band boundaries ---- - name: Case 1, just below the 100% FPL floor (0.99) is ineligible. - # aca_magi_fraction 0.99 < fpl_floor 1.0 -> income test fails. + # Exact ratio 9_900 / 10_000 = 0.99 < fpl_floor 1.0 -> income test fails. period: 2026 input: people: @@ -22,7 +28,10 @@ tax_units: tax_unit: members: [person1] - aca_magi_fraction: 0.99 + aca_magi: 9_900 + tax_unit_fpg: + 2025: 10_000 + tax_unit_is_filer: true households: household: members: [person1] @@ -31,7 +40,7 @@ ca_premium_subsidy_eligible: false - name: Case 2, exactly at the 100% FPL floor (1.0) is eligible. - # aca_magi_fraction 1.0 >= fpl_floor 1.0 and <= fpl_limit 1.65. + # Exact ratio 1.0 >= fpl_floor 1.0 and <= fpl_limit 1.65. period: 2026 input: people: @@ -41,7 +50,9 @@ tax_units: tax_unit: members: [person1] - aca_magi_fraction: 1.00 + aca_magi: 10_000 + tax_unit_fpg: + 2025: 10_000 tax_unit_is_filer: true households: household: @@ -60,7 +71,9 @@ tax_units: tax_unit: members: [person1] - aca_magi_fraction: 1.40 + aca_magi: 14_000 + tax_unit_fpg: + 2025: 10_000 tax_unit_is_filer: true households: household: @@ -70,7 +83,7 @@ ca_premium_subsidy_eligible: true - name: Case 4, exactly at the 165% FPL limit (1.65) is eligible. - # aca_magi_fraction 1.65 <= fpl_limit 1.65 (at the limit, not above). + # Exact ratio 1.65 <= fpl_limit 1.65 (PY2026: "at or below 165 percent"). period: 2026 input: people: @@ -80,7 +93,9 @@ tax_units: tax_unit: members: [person1] - aca_magi_fraction: 1.65 + aca_magi: 16_500 + tax_unit_fpg: + 2025: 10_000 tax_unit_is_filer: true households: household: @@ -90,7 +105,7 @@ ca_premium_subsidy_eligible: true - name: Case 5, just above the 165% FPL limit (1.66) is ineligible. - # aca_magi_fraction 1.66 > fpl_limit 1.65 -> above the band (federal APTC only). + # Exact ratio 1.66 > fpl_limit 1.65 -> above the band (federal APTC only). period: 2026 input: people: @@ -100,7 +115,10 @@ tax_units: tax_unit: members: [person1] - aca_magi_fraction: 1.66 + aca_magi: 16_600 + tax_unit_fpg: + 2025: 10_000 + tax_unit_is_filer: true households: household: members: [person1] @@ -108,6 +126,150 @@ output: ca_premium_subsidy_eligible: false +# ---- PY2026 limits from derived income (exact ratio, not truncation) ---- + +- name: Case 1b, PY2026 income of 99.994% of FPL is ineligible. + # $15,649 / 2025 one-person FPG $15,650 = 99.9936%, below "at or above 100 + # percent" (Program Design (d)(7)). + period: 2026 + absolute_error_margin: 0.0001 + input: + people: + person1: + age: 40 + employment_income: 15_649 + is_aca_ptc_eligible: true + tax_units: + tax_unit: + members: [person1] + tax_unit_is_filer: true + households: + household: + members: [person1] + state_code: CA + output: + aca_magi: 15_649 + aca_magi_fraction: 0.99 + ca_premium_subsidy_eligible: false + +- name: Case 2b, PY2026 income of exactly 100% of FPL is eligible. + # $15,650 / $15,650 = 100.000%, "at or above 100 percent". + period: 2026 + absolute_error_margin: 0.0001 + input: + people: + person1: + age: 40 + employment_income: 15_650 + is_aca_ptc_eligible: true + tax_units: + tax_unit: + members: [person1] + tax_unit_is_filer: true + households: + household: + members: [person1] + state_code: CA + output: + aca_magi: 15_650 + aca_magi_fraction: 1.00 + ca_premium_subsidy_eligible: true + +- name: Case 4b, PY2026 income of exactly 165% of FPL is eligible. + # $25,822.50 / 2025 one-person FPG $15,650 = 165.000%, which is "at or below + # 165 percent" (Program Design (d)(7)). + period: 2026 + absolute_error_margin: 0.0001 + input: + people: + person1: + age: 40 + employment_income: 25_822.50 + is_aca_ptc_eligible: true + tax_units: + tax_unit: + members: [person1] + tax_unit_is_filer: true + households: + household: + members: [person1] + state_code: CA + output: + aca_magi: 25_822.50 + aca_magi_fraction: 1.65 + ca_premium_subsidy_eligible: true + +- name: Case 5b, PY2026 income of 165.003% of FPL is ineligible though it truncates to 1.65. + # $25,823 / $15,650 = 165.0032%. aca_magi_fraction truncates to 1.65, but the + # limit applies to the exact ratio, which is above 165 percent. + period: 2026 + absolute_error_margin: 0.0001 + input: + people: + person1: + age: 40 + employment_income: 25_823 + is_aca_ptc_eligible: true + tax_units: + tax_unit: + members: [person1] + tax_unit_is_filer: true + households: + household: + members: [person1] + state_code: CA + output: + aca_magi: 25_823 + aca_magi_fraction: 1.65 + ca_premium_subsidy_eligible: false + +- name: Case 5c, PY2026 income of 165.994% of FPL is ineligible though it truncates to 1.65. + # $25,978 / $15,650 = 165.9936%, the highest whole-dollar income that + # truncates to 1.65. Before the exact-ratio gate this case was eligible. + period: 2026 + absolute_error_margin: 0.0001 + input: + people: + person1: + age: 40 + employment_income: 25_978 + is_aca_ptc_eligible: true + tax_units: + tax_unit: + members: [person1] + tax_unit_is_filer: true + households: + household: + members: [person1] + state_code: CA + output: + aca_magi: 25_978 + aca_magi_fraction: 1.65 + ca_premium_subsidy_eligible: false + +- name: Case 5d, PY2026 income of 166.000% of FPL is ineligible. + # $25,979 / $15,650 = 166.0000%; the truncated fraction is 1.66 as well. + period: 2026 + absolute_error_margin: 0.0001 + input: + people: + person1: + age: 40 + employment_income: 25_979 + is_aca_ptc_eligible: true + tax_units: + tax_unit: + members: [person1] + tax_unit_is_filer: true + households: + household: + members: [person1] + state_code: CA + output: + aca_magi: 25_979 + aca_magi_fraction: 1.66 + ca_premium_subsidy_eligible: false + # ---- Federal APTC gate ---- - name: Case 6, no member is federal-APTC-eligible -> ineligible. @@ -122,7 +284,10 @@ tax_units: tax_unit: members: [person1] - aca_magi_fraction: 1.40 + aca_magi: 14_000 + tax_unit_fpg: + 2025: 10_000 + tax_unit_is_filer: true households: household: members: [person1] @@ -144,7 +309,9 @@ tax_units: tax_unit: members: [person1, person2] - aca_magi_fraction: 1.40 + aca_magi: 14_000 + tax_unit_fpg: + 2025: 10_000 tax_unit_is_filer: true households: household: @@ -166,7 +333,10 @@ tax_units: tax_unit: members: [person1] - aca_magi_fraction: 1.40 + aca_magi: 14_000 + tax_unit_fpg: + 2025: 10_000 + tax_unit_is_filer: true households: household: members: [person1] @@ -187,7 +357,10 @@ tax_units: tax_unit: members: [person1] - aca_magi_fraction: 1.40 + aca_magi: 14_000 + tax_unit_fpg: + 2024: 10_000 + tax_unit_is_filer: true households: household: members: [person1] @@ -206,7 +379,10 @@ tax_units: tax_unit: members: [person1] - aca_magi_fraction: 1.40 + aca_magi: 14_000 + tax_unit_fpg: + 2027: 10_000 + tax_unit_is_filer: true households: household: members: [person1] @@ -225,7 +401,9 @@ tax_units: tax_unit: members: [person1] - aca_magi_fraction: 1.40 + aca_magi: 14_000 + tax_unit_fpg: + 2026: 10_000 tax_unit_is_filer: true households: household: @@ -236,8 +414,8 @@ # ---- Zero-income edge ---- -- name: Case 12, zero-income household (aca_magi_fraction 0) is ineligible. - # aca_magi_fraction 0 < fpl_floor 1.0 -> income test fails. A tax unit with no +- name: Case 12, zero-income household (0% FPL) is ineligible. + # Exact ratio 0 < fpl_floor 1.0 -> income test fails. A tax unit with no # income (0% FPL) sits below the CA 100% floor and receives federal APTC only, # even with an APTC-eligible member. Confirms the fraction-0 edge is on the # ineligible side of the floor rather than a spurious pass-through. @@ -250,7 +428,10 @@ tax_units: tax_unit: members: [person1] - aca_magi_fraction: 0 + aca_magi: 0 + tax_unit_fpg: + 2025: 10_000 + tax_unit_is_filer: true households: household: members: [person1] @@ -261,7 +442,7 @@ # ---- Applicable return filer gate (Program Design (d)(7)) ---- - name: Case 13, non-filer in-band CA unit is ineligible. - # aca_magi_fraction 1.40 in [1.0,1.65] and APTC-eligible, but the tax unit is + # Exact ratio 1.40 in [1.0,1.65] and APTC-eligible, but the tax unit is # not an applicable return filer, so ca_premium_subsidy_eligible is false. # GC 100810(a)(6) requires filing a California return; a non-filer forms no # claiming unit. @@ -274,7 +455,9 @@ tax_units: tax_unit: members: [person1] - aca_magi_fraction: 1.40 + aca_magi: 14_000 + tax_unit_fpg: + 2025: 10_000 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 @@ -297,7 +480,9 @@ tax_units: tax_unit: members: [person1] - aca_magi_fraction: 1.40 + aca_magi: 14_000 + tax_unit_fpg: + 2025: 10_000 tax_unit_is_filer: true households: household: @@ -326,7 +511,9 @@ tax_unit: members: [person1, person2] filing_status: SEPARATE - aca_magi_fraction: 1.40 + aca_magi: 14_000 + tax_unit_fpg: + 2025: 10_000 tax_unit_is_filer: true households: household: @@ -351,7 +538,9 @@ tax_units: tax_unit: members: [person1] - aca_magi_fraction: 1.40 + aca_magi: 14_000 + tax_unit_fpg: + 2025: 10_000 tax_unit_is_filer: true households: household: @@ -360,11 +549,11 @@ output: ca_premium_subsidy_eligible: false -# ---- PY2027 FPL-limit edge (limit raised to 1.99) ---- +# ---- PY2027 FPL-limit edge (at or below 200%) ---- -- name: Case 17, PY2027 at 199% FPL (1.99) is eligible (limit raised to 1.99). - # PY2027 fpl_limit 1.99: 1.99 <= 1.99 -> in-band. Confirms the widened - # PY2027 band admits units up to 199% FPL. +- name: Case 17, PY2027 at 199% FPL (1.99) is eligible. + # Final 2027 Program Design (d)(7): "at or below 200 percent". 1.99 <= 2.0. + # Confirms the widened PY2027 band admits units between 165% and 200% FPL. period: 2027 input: people: @@ -374,7 +563,9 @@ tax_units: tax_unit: members: [person1] - aca_magi_fraction: 1.99 + aca_magi: 19_900 + tax_unit_fpg: + 2026: 10_000 tax_unit_is_filer: true households: household: @@ -383,9 +574,9 @@ output: ca_premium_subsidy_eligible: true -- name: Case 18, PY2027 at 200% FPL (2.00) is ineligible (just above the 1.99 limit). - # PY2027 fpl_limit 1.99: 2.00 > 1.99 -> above the band. Pairs with Case 17 to - # bracket the PY2027 upper edge. +- name: Case 18, PY2027 at exactly 200% FPL (2.00) is eligible (inclusive limit). + # Exact ratio 20_000 / 10_000 = 2.0, which is "at or below 200 percent". + # Pairs with Case 18a to bracket the PY2027 upper edge. period: 2027 input: people: @@ -395,7 +586,32 @@ tax_units: tax_unit: members: [person1] - aca_magi_fraction: 2.00 + aca_magi: 20_000 + tax_unit_fpg: + 2026: 10_000 + tax_unit_is_filer: true + households: + household: + members: [person1] + state_code: CA + output: + ca_premium_subsidy_eligible: true + +- name: Case 18a, PY2027 at 200.5% FPL (2.005) is ineligible. + # Exact ratio 20_050 / 10_000 = 2.005 > 2.0. A limit tested on the truncated + # fraction (2.00) would wrongly admit it. + period: 2027 + input: + people: + person1: + age: 40 + is_aca_ptc_eligible: true + tax_units: + tax_unit: + members: [person1] + aca_magi: 20_050 + tax_unit_fpg: + 2026: 10_000 tax_unit_is_filer: true households: household: @@ -404,6 +620,151 @@ output: ca_premium_subsidy_eligible: false +- name: Case 17a, PY2027 at 199.5% FPL (1.995) is eligible. + # Exact ratio 19_950 / 10_000 = 1.995 <= 2.0. A limit of 1.99 on the exact + # ratio would wrongly exclude incomes between 199% and 200% of FPL. + period: 2027 + input: + people: + person1: + age: 40 + is_aca_ptc_eligible: true + tax_units: + tax_unit: + members: [person1] + aca_magi: 19_950 + tax_unit_fpg: + 2026: 10_000 + tax_unit_is_filer: true + households: + household: + members: [person1] + state_code: CA + output: + ca_premium_subsidy_eligible: true + +# ---- PY2027 200% limit from derived income ---- + +- name: Case 17b, PY2027 income of 199.994% of FPL is eligible. + # $31,919 / 2026 one-person FPG $15,960 = 199.9937%. + period: 2027 + absolute_error_margin: 0.0001 + input: + people: + person1: + age: 40 + employment_income: 31_919 + is_aca_ptc_eligible: true + tax_units: + tax_unit: + members: [person1] + tax_unit_is_filer: true + households: + household: + members: [person1] + state_code: CA + output: + aca_magi: 31_919 + aca_magi_fraction: 1.99 + ca_premium_subsidy_eligible: true + +- name: Case 18b, PY2027 income of exactly 200% of FPL is eligible. + # $31,920 / $15,960 = 200.000%, "at or below 200 percent" (Final 2027 + # Design (d)(7)). This is the OE27 enroller deck's 200% FPL example, which + # applies the 5.28% state rate (PDF pages 37-40). + period: 2027 + absolute_error_margin: 0.0001 + input: + people: + person1: + age: 40 + employment_income: 31_920 + is_aca_ptc_eligible: true + tax_units: + tax_unit: + members: [person1] + tax_unit_is_filer: true + households: + household: + members: [person1] + state_code: CA + output: + aca_magi: 31_920 + aca_magi_fraction: 2.00 + ca_premium_subsidy_eligible: true + +- name: Case 18d, PY2027 income of 200.006% of FPL is ineligible though it truncates to 2.00. + # $31,921 / $15,960 = 200.0063%. + period: 2027 + absolute_error_margin: 0.0001 + input: + people: + person1: + age: 40 + employment_income: 31_921 + is_aca_ptc_eligible: true + tax_units: + tax_unit: + members: [person1] + tax_unit_is_filer: true + households: + household: + members: [person1] + state_code: CA + output: + aca_magi: 31_921 + aca_magi_fraction: 2.00 + ca_premium_subsidy_eligible: false + +- name: Case 18e, PY2027 income of 200.996% of FPL is ineligible though it truncates to 2.00. + # $32,079 / $15,960 = 200.9962%; 201% of FPL is $32,079.60. + period: 2027 + absolute_error_margin: 0.0001 + input: + people: + person1: + age: 40 + employment_income: 32_079 + is_aca_ptc_eligible: true + tax_units: + tax_unit: + members: [person1] + tax_unit_is_filer: true + households: + household: + members: [person1] + state_code: CA + output: + aca_magi: 32_079 + aca_magi_fraction: 2.00 + ca_premium_subsidy_eligible: false + +- name: Case 18c, PY2027 income of 165.006% of FPL is eligible (165% is the PY2026 limit only). + # $26,335 / $15,960 = 165.0063%. Ineligible under the PY2026 limit, but + # inside the PY2027 band, which runs to 200%. + period: 2027 + absolute_error_margin: 0.0001 + input: + people: + person1: + age: 40 + employment_income: 26_335 + is_aca_ptc_eligible: true + tax_units: + tax_unit: + members: [person1] + tax_unit_is_filer: true + households: + household: + members: [person1] + state_code: CA + output: + aca_magi: 26_335 + aca_magi_fraction: 1.65 + ca_premium_subsidy_eligible: true + +# ---- Filing via federal credit take-up ---- + - name: Case 13b, an in-band CA unit that takes up the federal credit files and is eligible. # Case 13 with take-up true and a positive federal credit ($6,000 benchmark # less 4.19% of $22,000 = $5,078.20): a tax unit that takes up the credit diff --git a/policyengine_us/tests/unit/test_ca_premium_subsidy_applicable_percentage.py b/policyengine_us/tests/unit/test_ca_premium_subsidy_applicable_percentage.py index 4aecce7c251..bae4c46633d 100644 --- a/policyengine_us/tests/unit/test_ca_premium_subsidy_applicable_percentage.py +++ b/policyengine_us/tests/unit/test_ca_premium_subsidy_applicable_percentage.py @@ -1,4 +1,4 @@ -"""Properties of the adopted 2026 Design (d)(4) contribution bands. +"""Properties of the adopted 2026 and Final 2027 Design (d)(4) contribution bands. FTB 3849 Table 1-2 distinguishes exact band eligibility from the truncated percentage used for the rate lookup. The physical-income properties use @@ -19,17 +19,18 @@ VARIABLE = "ca_premium_subsidy_applicable_percentage" -def _rates(year, incomes, *, truncate=True): +def _rates(year, incomes, *, truncate=True, fpg=FPG, dtype=float): """Call the registered formula on vectorized inputs without rebuilding US.""" calculation_period = period(year) - incomes = np.asarray(incomes, dtype=float) - fractions = np.maximum(incomes, 0) / FPG + incomes = np.asarray(incomes, dtype=dtype) + fpgs = np.full(incomes.shape, fpg, dtype=dtype) + fractions = np.maximum(incomes, 0) / fpgs if truncate: - fractions = np.floor(100 * np.maximum(incomes, 0) / FPG) / 100 + fractions = np.floor(100 * np.maximum(incomes, 0) / fpgs) / 100 inputs = { "aca_magi": incomes, "aca_magi_fraction": fractions, - "tax_unit_fpg": np.full(incomes.shape, FPG), + "tax_unit_fpg": fpgs, } def tax_unit(name, input_period): @@ -45,16 +46,17 @@ def tax_unit(name, input_period): return formula(tax_unit, calculation_period, system.parameters) -@pytest.mark.parametrize("year", [2026]) +@pytest.mark.parametrize("year", [2026, 2027]) @settings(max_examples=100, deadline=None, derandomize=True) @given(incomes=st.lists(st.integers(-100_000, 600_000), min_size=2, max_size=40)) @example(incomes=[149_999, 150_000, 150_001, 150_999, 151_000]) +@example(incomes=[199_999, 200_000, 200_001, 200_999, 201_000]) def test_rate_never_decreases_with_exact_income(year, incomes): rates = _rates(year, sorted(incomes)) assert np.all(np.diff(rates) >= -1e-12) -@pytest.mark.parametrize("year", [2026]) +@pytest.mark.parametrize("year", [2026, 2027]) @settings(max_examples=100, deadline=None, derandomize=True) @given(income=st.integers(-100_000, 150_000)) @example(income=150_000) @@ -62,7 +64,7 @@ def test_rate_is_zero_at_or_below_exactly_150_percent(year, income): assert _rates(year, [income])[0] == 0 -@pytest.mark.parametrize("year, starting_rate", [(2026, 0.0319)]) +@pytest.mark.parametrize("year, starting_rate", [(2026, 0.0319), (2027, 0.028)]) @settings(max_examples=100, deadline=None, derandomize=True) @given(income=st.integers(150_001, 150_999)) def test_exact_income_above_150_uses_upper_band_truncated_starting_rate( @@ -71,9 +73,20 @@ def test_exact_income_above_150_uses_upper_band_truncated_starting_rate( assert _rates(year, [income])[0] == pytest.approx(starting_rate, rel=0, abs=1e-12) +@pytest.mark.parametrize("year, starting_rate", [(2026, 0.0319), (2027, 0.028)]) +def test_float32_ratio_just_above_150_uses_upper_band(year, starting_rate): + # The model stores income and FPG as float32. This pair's exact ratio is + # 1.50000006, but its float32 quotient rounds down to exactly 1.5. + income, fpg = np.float32(24_600.00390625), np.float32(16_400.001953125) + assert float(income) / float(fpg) > 1.5 + assert income / fpg == np.float32(1.5) + rate = _rates(year, [income], fpg=fpg, dtype=np.float32)[0] + assert rate == pytest.approx(starting_rate, rel=0, abs=1e-7) + + @pytest.mark.parametrize( "year, boundary", - [(2026, 1), (2026, 1.65)], + [(2026, 1), (2026, 1.65), (2027, 1), (2027, 2)], ) @settings(max_examples=100, deadline=None, derandomize=True) @given(epsilon=st.floats(min_value=1e-7, max_value=1e-4)) diff --git a/policyengine_us/tests/unit/test_ca_premium_subsidy_eligible_properties.py b/policyengine_us/tests/unit/test_ca_premium_subsidy_eligible_properties.py new file mode 100644 index 00000000000..91fa8d624b8 --- /dev/null +++ b/policyengine_us/tests/unit/test_ca_premium_subsidy_eligible_properties.py @@ -0,0 +1,165 @@ +"""Properties of the California Premium Subsidy income band. + +Program Design (d)(7) defines an applicable return filer by household income +"at or above 100 percent and at or below 165 percent of the federal poverty +level" in PY2026, and "at or above 100 percent and at or below 200 percent" +in the Final 2027 Design. The bounds below are transcribed from those +Designs, not read from the parameters, and are evaluated in exact rational +arithmetic. + +The model divides nonnegative ACA MAGI by the prior-year poverty guideline. +It must not use aca_magi_fraction for the band: that fraction truncates to +whole percentages, so every income from 165.00% to 165.99% of the guideline +becomes 1.65 and would pass an "at or below 165 percent" test (in PY2027, +200.00% to 200.99% becomes 2.00). FTB 3849 Table 1-2, for the predecessor +California program, likewise truncates for the rate lookup but decides band +edges on the exact percentage. + +The model stores MAGI and the guideline as float32 and divides them in +float64, so the reference reads (d)(7) exactly on the stored float32 values. + +- The parameters match the Designs' bounds. +- With every non-income condition met, eligibility equals the exact reading + of (d)(7) for any stored income and guideline. The examples mix three kinds + of income: whole dollars near each edge (some guidelines are multiples of + $20, which put 165% and 200% on whole dollars); a few float32 steps either + side of each edge; and incomes across the range. Some guidelines are + fractional, like the projected ones. + +Each example builds a fresh simulation, so nothing computed for one example +is cached into the next. +""" + +from fractions import Fraction + +import numpy as np +import pytest +from hypothesis import example, given, settings +from hypothesis import strategies as st + +from policyengine_us import Simulation +from policyengine_us.system import system + +# Program Design (d)(7), by plan year: the floor and the limit, both inclusive. +DESIGN_BANDS = { + # 2026 Design: "at or above 100 percent and at or below 165 percent". + 2026: (Fraction(1), Fraction(165, 100)), + # Final 2027 Design: "at or above 100 percent and at or below 200 percent". + 2027: (Fraction(1), Fraction(2)), +} +EDGES = (Fraction(1), Fraction(165, 100), Fraction(2)) +MAX_TAX_UNITS = 128 + + +def stored(value): + """The exact value of a number after float32 storage.""" + return Fraction(float(np.float32(value))) + + +def design_eligible(year, income, guideline): + floor, limit = DESIGN_BANDS[year] + ratio = max(stored(income), 0) / stored(guideline) + return floor <= ratio <= limit + + +def model_eligible(year, incomes, guidelines): + """Eligibility from a fresh simulation with every non-income condition met.""" + n = len(incomes) + sim = Simulation( + situation={ + "people": {f"p{i}": {"age": {year: 40}} for i in range(n)}, + "tax_units": {f"t{i}": {"members": [f"p{i}"]} for i in range(n)}, + "households": { + f"h{i}": {"members": [f"p{i}"], "state_code": {year: "CA"}} + for i in range(n) + }, + } + ) + sim.set_input("is_aca_ptc_eligible", year, np.ones(n, dtype=bool)) + sim.set_input("files_return_for_aca_ptc", year, np.ones(n, dtype=bool)) + sim.set_input("aca_magi", year, np.asarray(incomes, dtype=float)) + sim.set_input("tax_unit_fpg", year - 1, np.asarray(guidelines, dtype=float)) + return sim.calculate("ca_premium_subsidy_eligible", year) + + +def float32_steps(value, steps): + """The float32 value `steps` representable numbers away from `value`.""" + x = np.float32(value) + toward = np.float32(np.inf if steps > 0 else -np.inf) + for _ in range(abs(steps)): + x = np.nextafter(x, toward) + return float(x) + + +@st.composite +def tax_unit_incomes(draw): + guideline = draw( + st.one_of( + st.integers(10_000, 80_000).map(float), + st.integers(500, 4_000).map(lambda k: 20.0 * k), + st.floats(10_000, 500_000, width=32), + ) + ) + edge_income = draw(st.sampled_from(EDGES)) * stored(guideline) + kind = draw(st.sampled_from(["whole_dollars", "float32_steps", "any"])) + if kind == "whole_dollars": + income = float(int(edge_income) + draw(st.integers(-2, 2))) + elif kind == "float32_steps": + income = float32_steps(float(edge_income), draw(st.integers(-3, 3))) + else: + income = draw(st.floats(-50_000, float(np.float32(3 * guideline)), width=32)) + return income, guideline + + +@pytest.mark.parametrize("year", sorted(DESIGN_BANDS)) +def test_parameters_match_program_design(year): + p = system.parameters(f"{year}-01-01").gov.states.ca.hbex.premium_subsidy + floor, limit = DESIGN_BANDS[year] + assert p.in_effect + assert Fraction(str(p.fpl_floor)) == floor + assert Fraction(str(p.fpl_limit)) == limit + + +@pytest.mark.parametrize("year", sorted(DESIGN_BANDS)) +@settings(max_examples=30, deadline=None, derandomize=True) +@given(units=st.lists(tax_unit_incomes(), min_size=1, max_size=MAX_TAX_UNITS)) +# PY2026, 2025 one-person guideline $15,650: 164.997%, 165.000%, 165.003%, +# 165.994% and 166.000%; truncation maps 165.000% to 165.994% to 1.65. +@example( + units=[ + (25_822, 15_650), + (25_822.5, 15_650), + (25_823, 15_650), + (25_978, 15_650), + (25_979, 15_650), + ] +) +# A $20-multiple guideline puts 100%, 165% and 200% on whole dollars. +@example( + units=[ + (16_000, 16_000), + (26_400, 16_000), + (26_401, 16_000), + (32_000, 16_000), + (32_001, 16_000), + ] +) +# PY2027, 2026 one-person guideline $15,960: 199.994%, 200.000%, 200.006% and +# 200.996%; truncation maps 200.000% to 200.996% to 2.00. +@example(units=[(31_919, 15_960), (31_920, 15_960), (31_921, 15_960), (32_079, 15_960)]) +# The 2025 guideline for 72 people, $406,150: $674,209 is exactly 166%, which +# float32 truncation in aca_magi_fraction misrounds to 165.99%. +@example(units=[(674_209, 406_150), (812_300, 406_150)]) +# Stored float32 incomes a fraction of a cent above 165% of a fractional +# guideline: a float32 quotient rounds both ratios down to 1.65. +@example(units=[(18_711.599609375, 11_340.36328125), (37_907.703125, 22_974.365234375)]) +def test_eligibility_matches_exact_program_design_band(year, units): + incomes, guidelines = zip(*units) + expected = [design_eligible(year, i, g) for i, g in units] + actual = model_eligible(year, incomes, guidelines) + mismatches = [ + (income, guideline, bool(a)) + for (income, guideline), a, e in zip(units, actual, expected) + if bool(a) != e + ] + assert not mismatches diff --git a/policyengine_us/variables/gov/states/ca/hbex/premium_subsidy/ca_premium_subsidy_applicable_percentage.py b/policyengine_us/variables/gov/states/ca/hbex/premium_subsidy/ca_premium_subsidy_applicable_percentage.py index 014fc7bc446..713730c4f55 100644 --- a/policyengine_us/variables/gov/states/ca/hbex/premium_subsidy/ca_premium_subsidy_applicable_percentage.py +++ b/policyengine_us/variables/gov/states/ca/hbex/premium_subsidy/ca_premium_subsidy_applicable_percentage.py @@ -23,7 +23,11 @@ class ca_premium_subsidy_applicable_percentage(Variable): def formula(tax_unit, period, parameters): magi_frac = tax_unit("aca_magi_fraction", period) magi = max_(tax_unit("aca_magi", period), 0) - exact_magi_frac = magi / tax_unit("tax_unit_fpg", period.last_year) + # Divide in float64: a float32 quotient can round a ratio just above + # 150% down onto it, which side="left" would put in the 0% band. + exact_magi_frac = magi.astype(np.float64) / tax_unit( + "tax_unit_fpg", period.last_year + ) p = parameters(period).gov.states.ca.hbex.premium_subsidy.applicable_percentage thresholds = np.array(p.threshold) diff --git a/policyengine_us/variables/gov/states/ca/hbex/premium_subsidy/ca_premium_subsidy_eligible.py b/policyengine_us/variables/gov/states/ca/hbex/premium_subsidy/ca_premium_subsidy_eligible.py index eb31d0a2ee2..654f8e9dad3 100644 --- a/policyengine_us/variables/gov/states/ca/hbex/premium_subsidy/ca_premium_subsidy_eligible.py +++ b/policyengine_us/variables/gov/states/ca/hbex/premium_subsidy/ca_premium_subsidy_eligible.py @@ -10,8 +10,13 @@ class ca_premium_subsidy_eligible(Variable): reference = ( # 2026 Program Design (Final): eligibility bands and the applicable # return filer definition at Program Design (d)(7). + # PDF pages 1-2 "https://board.coveredca.com/meetings/2025/July%2028,%202025/CoveredCA_2026_Premium_Subsidy_Program_Design_Final.pdf#page=1", - "https://board.coveredca.com/meetings/2025/July%2028,%202025/CoveredCA_2026_Premium_Subsidy_Program_Design_Final.pdf#page=2", + # Final 2027 Program Design, (d)(7): "at or below 200 percent". + "https://hbex.coveredca.com/data-research/Final-2027-California-Premium-Subsidy-Program-Design.pdf#page=2", + # FTB 3849 Table 1-2 (predecessor program): exact percentage decides + # the band edge; the truncated percentage only drives the rate lookup. + "https://www.ftb.ca.gov/forms/2020/2020-3849-instructions.pdf#page=6", # GC Title 25 Section 100805 authorizes the subsidy and disclaims the # federal income requirements; Section 100810 defines eligibility. "https://leginfo.legislature.ca.gov/faces/codes_displaySection.xhtml?lawCode=GOV§ionNum=100805.", @@ -35,8 +40,20 @@ def formula(tax_unit, period, parameters): # California band. The unmodeled 26 CFR 1.36B-2(b)(2)(ii)-(v) MFS # exceptions (abandoned-spouse, domestic-abuse relief) are not applied. aptc_eligible = add(tax_unit, period, ["is_aca_ptc_eligible"]) > 0 - magi_frac = tax_unit("aca_magi_fraction", period) - income_eligible = (magi_frac >= p.fpl_floor) & (magi_frac <= p.fpl_limit) + # Program Design (d)(7) bounds household income "at or above 100 + # percent" and "at or below" 165 percent (PY2026) or 200 percent + # (PY2027) of the FPL. Test the exact ratio, not the truncated + # aca_magi_fraction: truncation maps 165.00%-165.99% to 1.65, which + # would admit incomes above 165%. FTB 3849 Table 1-2 (2020) likewise + # decides band edges on the exact percentage. + magi = max_(tax_unit("aca_magi", period), 0) + fpg = tax_unit("tax_unit_fpg", period.last_year) + # Divide in float64: a float32 quotient can round a ratio just above + # a limit down onto it. + exact_magi_frac = magi.astype(np.float64) / fpg + income_eligible = (exact_magi_frac >= p.fpl_floor) & ( + exact_magi_frac <= p.fpl_limit + ) # An applicable return filer (Program Design (d)(7); GC # Section 100810(a)(6)) must file a California return, so the tax unit # must be a filer. This gate replaces reliance on the federal 100% FPL diff --git a/source_conflicts/candidates.yaml b/source_conflicts/candidates.yaml index 8209408f462..f6c204e5ddb 100644 --- a/source_conflicts/candidates.yaml +++ b/source_conflicts/candidates.yaml @@ -794,6 +794,46 @@ candidates: note: The adopted rule's explicit above-150% wording governs the conflicting training example. Recheck if Covered California amends the Design or clarifies the boundary. PR 10301 changes the boundary; PR 10305 handles the final 2027 schedule and eligibility limits separately. +- id: us-ca:premium-subsidy-200-fpl-limit + program: california-premium-subsidy + years: + from: 2027 + type: program + note: Covered California coverage year beginning January 1. + question: Is a household at exactly 200% FPL eligible for the 2027 California Premium Subsidy, as the adopted + Program Design's "at or below 200 percent" reads, or excluded, as the OE27 enroller deck's summary slide reads? + sources: + - kind: statute + citation: California Government Code 100800(c), Exchange-adopted program design + url: https://leginfo.legislature.ca.gov/faces/codes_displaySection.xhtml?lawCode=GOV§ionNum=100800. + - kind: policy + citation: Final 2027 California Premium Subsidy Program Design, (d)(7), PDF page 2 + url: https://hbex.coveredca.com/data-research/Final-2027-California-Premium-Subsidy-Program-Design.pdf#page=2 + - kind: guidance + citation: 2027 California State Premium Subsidy Program Insight, eligibility and Table 1, PDF pages 1-2 + url: https://hbex.coveredca.com/stakeholders/PDFs/2026-03_StatePremiumSub2027_Insight_Final.pdf#page=1 + - kind: agency-practice + citation: OE 2027 Enroller Kick-off, 200% FPL example and summary slide, PDF pages 37-40 + url: https://hbex.coveredca.com/toolkit/webinars-briefings/downloads/OE27_Enroller_Kickoff.pdf#page=37 + readings: + statute: Design (d)(7) defines an applicable return filer by household income at or above 100 percent and + at or below 200 percent of the FPL, so exactly 200% is eligible; the Insight agrees. + other: The OE27 summary slide says the 2027 subsidy applies only to enrollees up to under 200% FPL, which + would exclude exactly 200%. + example: A single filer with $31,920 income, exactly 200% of the 2026 one-person FPG of $15,960, is eligible + and pays 5.28% of income under the Design; the deck's own worked example on PDF pages 37 and 39-40 applies + the same 5.28%, while its summary slide on PDF page 38 would exclude the household. + pe_us: + follows: statute + code: + - policyengine_us/parameters/gov/states/ca/hbex/premium_subsidy/fpl_limit.yaml + - policyengine_us/variables/gov/states/ca/hbex/premium_subsidy/ca_premium_subsidy_eligible.py + prs: + - https://github.com/PolicyEngine/policyengine-us/pull/10305 + state: ruled + note: The adopted Design's explicit at-or-below wording governs the training deck's summary slide. Earlier + PE-US code followed the superseded Program Design V4, which read under 200 percent. Recheck if Covered + California amends the Design or clarifies the limit. - id: us-ca:renter-credit-2025-joint-income-limit program: income-tax years: