From 194c1f6237dda80080f3baed132bbecc928facdc Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Fri, 25 Sep 2026 08:11:30 -0400 Subject: [PATCH 1/5] Index IRS parameters to the 12-months-ending-August chained CPI-U 26 U.S.C. 1(f)(6)(B) defines each calendar year's C-CPI-U as the average over the 12 months ending August 31. get_irs_cpi averaged August through July (#9105), and for years without BLS data it read c_cpi_u's February projection points, which since #7394 are CBO calendar-year averages that run about 0.7% above the September-August average (#9608). Add CBO's tax-year CPI projections (Tax Parameters rows 152-153) as their own series and read them for windows with no BLS observation, leaving the calendar-year points for the 50-plus general CPI-U consumers. Route gov.irs.uprating, the Oregon Kids' Credit COLA, and the 63(c)(4) computation through the shared 12-months-ending-August helper. The statute applied to this index reproduces all ten CBO projections of the dependent standard deduction floor, so drop those hardcoded values. Fixes #9105. Fixes #9608. Co-Authored-By: Claude Opus 5.5 --- changelog.d/irs-index-tax-year-cpi.fixed.md | 1 + .../parameters/gov/bls/cpi/c_cpi_u.yaml | 24 ++-- .../parameters/gov/bls/cpi/cpi_u.yaml | 22 ++-- .../bls/cpi/tax_year_projection/c_cpi_u.yaml | 28 ++++ .../bls/cpi/tax_year_projection/cpi_u.yaml | 26 ++++ .../deductions/standard/dependent/amount.yaml | 23 +--- .../parameters/uprating_extensions.py | 121 +++++++++--------- .../standard/basic_standard_deduction.yaml | 27 ++-- .../tax/income/credits/test_or_ctc_sunset.py | 42 +++--- .../parameters/test_uprating_extensions.py | 86 ++++++++++--- 10 files changed, 254 insertions(+), 146 deletions(-) create mode 100644 changelog.d/irs-index-tax-year-cpi.fixed.md create mode 100644 policyengine_us/parameters/gov/bls/cpi/tax_year_projection/c_cpi_u.yaml create mode 100644 policyengine_us/parameters/gov/bls/cpi/tax_year_projection/cpi_u.yaml diff --git a/changelog.d/irs-index-tax-year-cpi.fixed.md b/changelog.d/irs-index-tax-year-cpi.fixed.md new file mode 100644 index 00000000000..6dbc7617ee9 --- /dev/null +++ b/changelog.d/irs-index-tax-year-cpi.fixed.md @@ -0,0 +1 @@ +Compute the IRS uprating index as the chained CPI-U averaged over the 12 months ending August 31 (26 U.S.C. 1(f)(6)(B)), instead of August through July, and project windows with no BLS observation from CBO's tax-year index (Tax Parameters row 153) instead of CBO's calendar-year averages, which ran about 0.7% high. Oregon Kids' Credit projections use CBO's tax-year CPI-U the same way. The dependent standard deduction floor now comes from 63(c)(4) for every year after 2026, reproducing all ten CBO projections for 2027-2036 without hardcoded values. diff --git a/policyengine_us/parameters/gov/bls/cpi/c_cpi_u.yaml b/policyengine_us/parameters/gov/bls/cpi/c_cpi_u.yaml index f9f7cc3021e..3195bb8ed00 100644 --- a/policyengine_us/parameters/gov/bls/cpi/c_cpi_u.yaml +++ b/policyengine_us/parameters/gov/bls/cpi/c_cpi_u.yaml @@ -320,18 +320,18 @@ values: 2026-04-01: 184.499 2026-05-01: 185.589 2026-06-01: 184.992 - # CBO projections from CBO Feb 2026 forecast for future periods. - # CBO projects the average C-CPI-U over the 12mo ending August, - # which determines tax parameters for the following year. - 2027-02-01: 187.2 # 2028 value. - 2028-02-01: 191.1 # 2029 value. - 2029-02-01: 194.9 # 2030 value. - 2030-02-01: 198.8 # 2031 value. - 2031-02-01: 202.7 # 2032 value. - 2032-02-01: 206.7 # 2033 value. - 2033-02-01: 210.8 # 2034 value. - 2034-02-01: 214.9 # 2035 value. - 2035-02-01: 219.2 # 2036 value. + # CBO's February 2026 projections of each calendar year's average C-CPI-U. + # Tax parameters use the average over the 12 months ending August instead; + # CBO's projections of that average are in tax_year_projection/c_cpi_u.yaml. + 2027-02-01: 187.2 # Calendar year 2027 average. + 2028-02-01: 191.1 # Calendar year 2028 average. + 2029-02-01: 194.9 # Calendar year 2029 average. + 2030-02-01: 198.8 # Calendar year 2030 average. + 2031-02-01: 202.7 # Calendar year 2031 average. + 2032-02-01: 206.7 # Calendar year 2032 average. + 2033-02-01: 210.8 # Calendar year 2033 average. + 2034-02-01: 214.9 # Calendar year 2034 average. + 2035-02-01: 219.2 # Calendar year 2035 average. # Values for 2036-2100 are set programmatically in uprating_extensions.py metadata: economy: false diff --git a/policyengine_us/parameters/gov/bls/cpi/cpi_u.yaml b/policyengine_us/parameters/gov/bls/cpi/cpi_u.yaml index 2a76d76e7ec..9ee95b3415e 100644 --- a/policyengine_us/parameters/gov/bls/cpi/cpi_u.yaml +++ b/policyengine_us/parameters/gov/bls/cpi/cpi_u.yaml @@ -1363,16 +1363,18 @@ values: 2026-04-01: 332.407 2026-05-01: 333.979 2026-06-01: 332.568 - # CBO projections from CBO Feb 2026 forecast for future periods. - 2027-02-01: 340.3 # 2028 value. - 2028-02-01: 348.3 # 2029 value. - 2029-02-01: 356.3 # 2030 value. - 2030-02-01: 364.3 # 2031 value. - 2031-02-01: 372.6 # 2032 value. - 2032-02-01: 381.0 # 2033 value. - 2033-02-01: 389.6 # 2034 value. - 2034-02-01: 398.4 # 2035 value. - 2035-02-01: 407.3 # 2036 value. + # CBO's February 2026 projections of each calendar year's average CPI-U. + # Tax parameters use the average over the 12 months ending August instead; + # CBO's projections of that average are in tax_year_projection/cpi_u.yaml. + 2027-02-01: 340.3 # Calendar year 2027 average. + 2028-02-01: 348.3 # Calendar year 2028 average. + 2029-02-01: 356.3 # Calendar year 2029 average. + 2030-02-01: 364.3 # Calendar year 2030 average. + 2031-02-01: 372.6 # Calendar year 2031 average. + 2032-02-01: 381.0 # Calendar year 2032 average. + 2033-02-01: 389.6 # Calendar year 2033 average. + 2034-02-01: 398.4 # Calendar year 2034 average. + 2035-02-01: 407.3 # Calendar year 2035 average. # Values for 2036-2100 are set programmatically in uprating_extensions.py metadata: unit: currency-USD diff --git a/policyengine_us/parameters/gov/bls/cpi/tax_year_projection/c_cpi_u.yaml b/policyengine_us/parameters/gov/bls/cpi/tax_year_projection/c_cpi_u.yaml new file mode 100644 index 00000000000..4cede88a163 --- /dev/null +++ b/policyengine_us/parameters/gov/bls/cpi/tax_year_projection/c_cpi_u.yaml @@ -0,0 +1,28 @@ +description: The Congressional Budget Office projects this average of the Chained Consumer Price Index for All Urban Consumers over the 12 months ending the August before each tax year, the index that adjusts federal tax parameters for that year. +values: + # Keyed by tax year: the 2028 value averages September 2026 through + # August 2027. Windows with BLS observations use c_cpi_u.yaml instead. + 2027-01-01: 181.654 + 2028-01-01: 185.881 + 2029-01-01: 189.775 + 2030-01-01: 193.6 + 2031-01-01: 197.44 + 2032-01-01: 201.335 + 2033-01-01: 205.302 + 2034-01-01: 209.346 + 2035-01-01: 213.471 + 2036-01-01: 217.68 + # Values for 2037-2100 are set programmatically in uprating_extensions.py. +metadata: + unit: /1 + period: year + label: CBO tax-year Chained CPI-U projection + economy: false + reference: + - title: 26 U.S. Code § 1(f)(6) - C-CPI-U + href: https://www.law.cornell.edu/uscode/text/26/1#f_6 + - title: CBO | Tax Parameters and Effective Marginal Tax Rates | February 2026 + # Sheet 1. Tax Parameters, row 153: Chained consumer price index, all + # urban consumers (C-CPIU), under "Inflation indexes for tax year + # (average CPI over the 12 months ending the previous August)". + href: https://www.cbo.gov/system/files/2026-02/53724-2026-02-Tax-Parameters.xlsx diff --git a/policyengine_us/parameters/gov/bls/cpi/tax_year_projection/cpi_u.yaml b/policyengine_us/parameters/gov/bls/cpi/tax_year_projection/cpi_u.yaml new file mode 100644 index 00000000000..e4b45465a4a --- /dev/null +++ b/policyengine_us/parameters/gov/bls/cpi/tax_year_projection/cpi_u.yaml @@ -0,0 +1,26 @@ +description: The Congressional Budget Office projects this average of the Consumer Price Index for All Urban Consumers over the 12 months ending the August before each tax year. +values: + # Keyed by tax year: the 2028 value averages September 2026 through + # August 2027. Windows with BLS observations use cpi_u.yaml instead. + 2027-01-01: 328.701 + 2028-01-01: 337.382 + 2029-01-01: 345.443 + 2030-01-01: 353.398 + 2031-01-01: 361.404 + 2032-01-01: 369.57 + 2033-01-01: 377.913 + 2034-01-01: 386.443 + 2035-01-01: 395.162 + 2036-01-01: 404.081 + # Values for 2037-2100 are set programmatically in uprating_extensions.py. +metadata: + unit: /1 + period: year + label: CBO tax-year CPI-U projection + economy: false + reference: + - title: CBO | Tax Parameters and Effective Marginal Tax Rates | February 2026 + # Sheet 1. Tax Parameters, row 152: Consumer price index, all urban + # consumers (CPIU), under "Inflation indexes for tax year (average CPI + # over the 12 months ending the previous August)". + href: https://www.cbo.gov/system/files/2026-02/53724-2026-02-Tax-Parameters.xlsx diff --git a/policyengine_us/parameters/gov/irs/deductions/standard/dependent/amount.yaml b/policyengine_us/parameters/gov/irs/deductions/standard/dependent/amount.yaml index 8e71e18153f..266a13ab354 100644 --- a/policyengine_us/parameters/gov/irs/deductions/standard/dependent/amount.yaml +++ b/policyengine_us/parameters/gov/irs/deductions/standard/dependent/amount.yaml @@ -7,27 +7,18 @@ values: 2024-01-01: 1_300 2025-01-01: 1_350 2026-01-01: 1_350 - # CBO forecast. - 2027-01-01: 1_400 - 2028-01-01: 1_450 - 2029-01-01: 1_450 - 2030-01-01: 1_500 - 2031-01-01: 1_500 - 2032-01-01: 1_550 - 2033-01-01: 1_600 - 2034-01-01: 1_600 - 2035-01-01: 1_650 - 2036-01-01: 1_700 metadata: unit: currency-USD period: year label: Dependent standard deduction floor - # Values after the CBO horizon are computed in + # Values after the last IRS publication are computed in # parameters/uprating_extensions.py per 26 U.S.C. 63(c)(4)(B)(i): the # statutory $500 increased by the 1(f)(3) cost-of-living adjustment with - # calendar year 1987 as the base year, each increase rounded down to the - # next lowest multiple of $50 under 1(f)(7)(A). Those values supersede - # this uprating rule, which describes the index and rounding. + # calendar year 1987 as the base year (C-CPI-U for the 12 months ending + # August 31 of the prior year), each increase rounded down to the next + # lowest multiple of $50 under 1(f)(7)(A). Applied to CBO's projected + # index, this reproduces every CBO projection of the floor. Those values + # supersede this uprating rule, which describes the index and rounding. uprating: parameter: gov.irs.uprating rounding: @@ -54,5 +45,3 @@ metadata: href: https://www.irs.gov/pub/irs-drop/rp-19-44.pdf#page=13 - title: Rev. Proc. 2018-57, sec. 3.16(2) (tax year 2019) href: https://www.irs.gov/pub/irs-drop/rp-18-57.pdf#page=16 - - title: CBO Tax Parameters and Effective Marginal Tax Rates, Feb 2026, sheet 1, row 68 (dependent filers, unearned), tax years 2027-2036 - href: https://www.cbo.gov/system/files/2026-02/53724-2026-02-Tax-Parameters.xlsx diff --git a/policyengine_us/parameters/uprating_extensions.py b/policyengine_us/parameters/uprating_extensions.py index 1e047d86f67..a78c80019cd 100644 --- a/policyengine_us/parameters/uprating_extensions.py +++ b/policyengine_us/parameters/uprating_extensions.py @@ -21,18 +21,11 @@ ) -def get_irs_cpi(parameters: ParameterNode, year: int) -> float: - """Calculate IRS CPI based on Chained CPI-U average from Sep to Aug.""" - cpi = parameters.gov.bls.cpi.c_cpi_u - end = instant(f"{year}-08-01") - start = end.offset(-MONTHS_IN_YEAR, MONTH) - monthly_cpi_values = [] - for month in range(MONTHS_IN_YEAR): - monthly_cpi_values += [cpi(start.offset(month, MONTH))] - return sum(monthly_cpi_values) / MONTHS_IN_YEAR - - -def get_average_for_12_months_ending_august(cpi: Parameter, year: int) -> float: +def get_average_for_12_months_ending_august( + cpi: Parameter, + year: int, + projection: Parameter, +) -> float: """Average a monthly price index over the 12 months ending August 31. Covers September of ``year - 1`` through August of ``year``: the @@ -40,14 +33,13 @@ def get_average_for_12_months_ending_august(cpi: Parameter, year: int) -> float: ORS 315.273(5). The BLS index series hold monthly observations through the latest BLS - release, then annual projection points at February instants; a monthly - refresh that reaches a February replaces that instant's projection with - the observed value. Windows with observed months average them, carrying - the last observation flat through any unobserved tail; windows with no - observed month read the year's annual projection point, whose - February instant necessarily lies beyond the last observation and so - can only hold a projection. No branch reads an instant a refresh could - have turned from projection into observation. + release, then CBO's calendar-year averages at February instants; a + monthly refresh that reaches a February replaces that instant's + projection with the observed value. Windows with observed months average + them, carrying the last observation flat through any unobserved tail. + Windows with no observed month read ``projection``, CBO's projection of + this 12-month average keyed by the tax year it indexes (``year + 1``); + a calendar-year average would run about four months of inflation high. """ # February instants can hold annual projections, so only non-February # instants identify the end of the observed monthly series (one month @@ -63,17 +55,25 @@ def get_average_for_12_months_ending_august(cpi: Parameter, year: int) -> float: ] observed = [month for month in window_months if month <= last_observation] if not observed: - # Since #7394 the projection points cite CBO's calendar-year - # averages, which run about 0.7% above the 12-months-ending-August - # averages this window stands for, so projected windows read slightly - # high until that series is fixed (#9608). - return cpi(f"{year}-02-01") + return projection(f"{year + 1}-01-01") unobserved_tail = MONTHS_IN_YEAR - len(observed) return ( sum(cpi(month) for month in observed) + cpi(observed[-1]) * unobserved_tail ) / MONTHS_IN_YEAR +def get_irs_cpi(parameters: ParameterNode, year: int) -> float: + """The C-CPI-U for a calendar year under 26 U.S.C. 1(f)(6)(B). + + The average over the 12 months ending August 31 of ``year``, which + indexes tax parameters for ``year + 1``. + """ + cpi = parameters.gov.bls.cpi + return get_average_for_12_months_ending_august( + cpi.c_cpi_u, year, cpi.tax_year_projection.c_cpi_u + ) + + def get_or_ctc_cola(parameters: ParameterNode, tax_year: int) -> float: """Calculate the Oregon Kids' Credit cost-of-living adjustment. @@ -82,9 +82,11 @@ def get_or_ctc_cola(parameters: ParameterNode, tax_year: int) -> float: ending August 31 of the prior calendar year exceeds the monthly averaged index for the second quarter of calendar year 2022. """ - cpi = parameters.gov.bls.cpi.cpi_u - base = sum(cpi(f"2022-{month:02d}-01") for month in (4, 5, 6)) / 3 - window = get_average_for_12_months_ending_august(cpi, tax_year - 1) + cpi = parameters.gov.bls.cpi + base = sum(cpi.cpi_u(f"2022-{month:02d}-01") for month in (4, 5, 6)) / 3 + window = get_average_for_12_months_ending_august( + cpi.cpi_u, tax_year - 1, cpi.tax_year_projection.cpi_u + ) return max(window / base - 1, 0) @@ -102,13 +104,14 @@ def get_irs_cola_denominator(parameters: ParameterNode, base_year: int) -> float "1(f)(3)(C) replaces this denominator with the C-CPI-U for base " f"years after 2016; got {base_year}." ) - cpi = parameters.gov.bls.cpi.cpi_u - chained_2016 = get_average_for_12_months_ending_august( - parameters.gov.bls.cpi.c_cpi_u, 2016 + cpi = parameters.gov.bls.cpi + cpi_2016, cpi_base_year = ( + get_average_for_12_months_ending_august( + cpi.cpi_u, year, cpi.tax_year_projection.cpi_u + ) + for year in (2016, base_year) ) - cpi_2016 = get_average_for_12_months_ending_august(cpi, 2016) - cpi_base_year = get_average_for_12_months_ending_august(cpi, base_year) - return cpi_base_year * chained_2016 / cpi_2016 + return cpi_base_year * get_irs_cpi(parameters, 2016) / cpi_2016 def get_irs_cola( @@ -122,9 +125,7 @@ def get_irs_cola( preceding ``tax_year`` exceeds the denominator for ``base_year``; see ``get_irs_cola_denominator``. """ - chained = get_average_for_12_months_ending_august( - parameters.gov.bls.cpi.c_cpi_u, tax_year - 1 - ) + chained = get_irs_cpi(parameters, tax_year - 1) return max(chained / get_irs_cola_denominator(parameters, base_year) - 1, 0) @@ -148,8 +149,8 @@ def extend_dependent_standard_deduction_parameters( 2016, and 1(f)(7)(A) rounds each increase down to the next lowest multiple of $50. Each year is computed from the statutory base rather than chained from the last rounded value, which can lag the statute by - a full $50 step; IRS and CBO values encoded in the YAML take precedence - for their own years. + a full $50 step; IRS values encoded in the YAML take precedence for + their own years. """ ROUNDING_INTERVAL = 50 dependent = parameters.gov.irs.deductions.standard.dependent @@ -466,25 +467,27 @@ def set_all_uprating_parameters(parameters: ParameterNode) -> ParameterNode: """ END_YEAR = 2100 - # IRS uprating - special case that computes from CPI - IRS_UPRATING_START_YEAR = 2010 - IRS_LAST_PROJECTED_YEAR = 2035 + # CBO's tax-year CPI projections (January values, last projection year + # 2036) cover 12-month windows with no BLS observation. Extend them first + # so every IRS uprating year below has a window average. + for projection in ( + parameters.gov.bls.cpi.tax_year_projection.c_cpi_u, + parameters.gov.bls.cpi.tax_year_projection.cpi_u, + ): + extend_parameter_values( + projection, + last_projected_year=2036, + end_year=END_YEAR, + period_month=1, + period_day=1, + ) + # IRS uprating: the 26 U.S.C. 1(f)(6)(B) C-CPI-U for the calendar year + # before each tax year. + IRS_UPRATING_START_YEAR = 2010 uprating_index = parameters.gov.irs.uprating - - # Calculate the growth rate from the last two years of CPI projections - cpi_second_to_last = get_irs_cpi(parameters, IRS_LAST_PROJECTED_YEAR - 2) - cpi_last = get_irs_cpi(parameters, IRS_LAST_PROJECTED_YEAR - 1) - growth_rate = cpi_last / cpi_second_to_last - - # Apply IRS uprating for year in range(IRS_UPRATING_START_YEAR, END_YEAR + 1): - if year <= IRS_LAST_PROJECTED_YEAR: - # Use actual CPI values through the last projected year - irs_cpi = get_irs_cpi(parameters, year - 1) - else: - # For all years after LAST_PROJECTED_YEAR, apply the constant growth rate - irs_cpi = uprating_index(f"{year - 1}-01-01") * growth_rate + irs_cpi = get_irs_cpi(parameters, year - 1) uprating_index.update(period=f"year:{year}-01-01:1", value=irs_cpi) uprating_index.update(start=instant(f"{END_YEAR}-01-01"), value=irs_cpi) @@ -629,14 +632,14 @@ def set_all_uprating_parameters(parameters: ParameterNode) -> ParameterNode: # Oregon Kids' Credit dollar amounts follow the ORS 315.273(5) # cost-of-living schedule (unchained CPI-U over a 2022 Q2 base), computed - # from the statutory base amounts. Must run after the CPI-U extension - # above so projected windows are available. + # from the statutory base amounts. Must run after the tax-year CPI + # projection extension above so projected windows are available. extend_or_ctc_parameters(parameters, end_year=END_YEAR) # The limits on a dependent's standard deduction follow 26 U.S.C. # 63(c)(4)'s chained CPI-U schedule, computed from the statutory 1987 and - # 1997 bases. Must run after the Chained CPI-U extension above so - # projected windows are available. + # 1997 bases. Must run after the tax-year CPI projection extension above + # so projected windows are available. extend_dependent_standard_deduction_parameters(parameters, end_year=END_YEAR) return parameters diff --git a/policyengine_us/tests/policy/baseline/gov/irs/deductions/standard/basic_standard_deduction.yaml b/policyengine_us/tests/policy/baseline/gov/irs/deductions/standard/basic_standard_deduction.yaml index 483f073599a..1071c619a59 100644 --- a/policyengine_us/tests/policy/baseline/gov/irs/deductions/standard/basic_standard_deduction.yaml +++ b/policyengine_us/tests/policy/baseline/gov/irs/deductions/standard/basic_standard_deduction.yaml @@ -115,7 +115,8 @@ # C-CPI-U Sep 2015-Aug 2016 average 135.993 / CPI-U Sep 2015-Aug 2016 # average 238.649 = 90.886. # Increase = $250 x (182.424 / 90.886 - 1) = $251.80, rounded down to - # $250, so $500. Floor: CBO's $1,400. + # $250, so $500. Floor (63(c)(4)(B)(i), 1987 denominator 63.813): + # $500 x 182.424 / 63.813 = $1,429.36, rounded down to $1,400. # max($1,400, $500 + $3,000) = $3,500, below the $16,550 cap. period: 2027 input: @@ -136,14 +137,11 @@ - name: 2032 dependent filer with $3,000 of wages gets $550 plus earned income # Same 63(c)(4)(B)(ii) adjustment. The 12 months ending August 2031 have - # no observed month, so the window reads the c_cpi_u.yaml projection point - # at 2031-02-01: 202.7. That point is CBO's calendar-year 2031 average, not - # a September-August average (#9608). - # Increase = $250 x (202.7 / 90.886 - 1) = $307.57, rounded down to $300, - # so $550. CBO's tax-year index for 2032 (Feb 2026 Tax Parameters, row - # 153: 201.335) gives the same step: $250 x (201.335 / 90.886 - 1) = - # $303.81, rounded down to $300. Floor: CBO's $1,550. - # max($1,550, $550 + $3,000) = $3,550, below the $18,250 cap. + # no observed month, so the window reads CBO's projection of that average + # (tax_year_projection/c_cpi_u.yaml, 2032): 201.335. + # Increase = $250 x (201.335 / 90.886 - 1) = $303.81, rounded down to + # $300, so $550. Floor: $500 x 201.335 / 63.813 = $1,577.53, so $1,550. + # max($1,550, $550 + $3,000) = $3,550, below the $18,300 cap. period: 2032 input: people: @@ -165,11 +163,12 @@ # Past CBO's 2036 horizon, 63(c)(4)(B)(i) increases the 63(c)(5)(A) $500 # by the 1(f)(3) adjustment with calendar year 1987 as the base year. # C-CPI-U for the 12 months ending August 2041: uprating_extensions.py - # extends c_cpi_u.yaml past 2034 at its 2033-2034 growth rate, - # 214.9 x (214.9 / 210.8)^7 = 245.922. + # extends CBO's tax-year projection (tax_year_projection/c_cpi_u.yaml) + # past 2036 at its 2035-2036 growth rate, + # 217.68 x (217.68 / 213.471)^6 = 244.735. # 1(f)(3)(A)(ii) and (B): CPI-U Sep 1986-Aug 1987 average 111.983 x # 135.993 / 238.649 = 63.813. - # Increase = $500 x (245.922 / 63.813 - 1) = $1,426.89, rounded down to + # Increase = $500 x (244.735 / 63.813 - 1) = $1,417.59, rounded down to # $1,400, so $1,900. period: 2042 input: @@ -188,8 +187,8 @@ basic_standard_deduction: 1_900 - name: 2042 dependent filer with $3,000 of wages gets $650 plus earned income - # Same 2041 C-CPI-U of 245.922 and 1997 denominator of 90.886. - # Increase = $250 x (245.922 / 90.886 - 1) = $426.46, rounded down to + # Same 2041 C-CPI-U of 244.735 and 1997 denominator of 90.886. + # Increase = $250 x (244.735 / 90.886 - 1) = $423.20, rounded down to # $400, so $650. max($1,900, $650 + $3,000) = $3,650. period: 2042 input: diff --git a/policyengine_us/tests/policy/baseline/gov/states/or/tax/income/credits/test_or_ctc_sunset.py b/policyengine_us/tests/policy/baseline/gov/states/or/tax/income/credits/test_or_ctc_sunset.py index 0b9af4b1cf2..57a7f336b83 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/or/tax/income/credits/test_or_ctc_sunset.py +++ b/policyengine_us/tests/policy/baseline/gov/states/or/tax/income/credits/test_or_ctc_sunset.py @@ -125,9 +125,13 @@ def test_projections_compute_from_statutory_bases_not_chained(): def _cola_on_series(values, tax_year): """Run the COLA helper against a synthetic CPI-U series.""" - cpi_u = Parameter("cpi_u", data=dict(values)) - bls = SimpleNamespace(cpi=SimpleNamespace(cpi_u=cpi_u)) - parameters = SimpleNamespace(gov=SimpleNamespace(bls=bls)) + cpi = SimpleNamespace( + cpi_u=Parameter("cpi_u", data=dict(values)), + tax_year_projection=SimpleNamespace( + cpi_u=Parameter("cpi_u_projection", data=_tax_year_projection()) + ), + ) + parameters = SimpleNamespace(gov=SimpleNamespace(bls=SimpleNamespace(cpi=cpi))) return get_or_ctc_cola(parameters, tax_year) @@ -141,8 +145,8 @@ def _monthly(values, start_year, start_month, end_year, end_month, level): # A synthetic mirror of the live series shape: 2022 Q2 base months averaging -# 300, monthly observations through August 2025, then annual projection -# points at February instants. +# 300, monthly observations through August 2025, then calendar-year +# projection points at February instants. def _base_series(): values = {"2022-04-01": 297, "2022-05-01": 300, "2022-06-01": 303} _monthly(values, 2022, 7, 2025, 8, 331) @@ -151,16 +155,24 @@ def _base_series(): return values +# CBO's projections of the 12 months ending the August before each tax +# year. They differ from the calendar-year points above, so each assertion +# shows which series a window read. +def _tax_year_projection(): + return {"2027-01-01": 344, "2028-01-01": 358} + + def test_cola_fully_observed_window_averages_monthly_data(): # Tax year 2026 window (September 2024 - August 2025) is fully observed # at 331: COLA = 331 / 300 - 1. assert _cola_on_series(_base_series(), 2026) == pytest.approx(31 / 300) -def test_cola_unobserved_window_uses_annual_projection_point(): - # Tax year 2027 has no observed months, so the February 2026 annual - # projection point (347) applies: COLA = 347 / 300 - 1. - assert _cola_on_series(_base_series(), 2027) == pytest.approx(47 / 300) +def test_cola_unobserved_window_uses_tax_year_projection(): + # Tax year 2027 has no observed months, so CBO's projection of that + # window (344) applies, not the February 2026 calendar-year point (347): + # COLA = 344 / 300 - 1. + assert _cola_on_series(_base_series(), 2027) == pytest.approx(44 / 300) def test_cola_partial_window_ignores_overwritten_february_projection(): @@ -170,16 +182,16 @@ def test_cola_partial_window_ignores_overwritten_february_projection(): # disagrees: the tax year 2027 window averages the ten observed months # (five at 330, five at 342) with a flat two-month tail at the last # observation (342), giving (5 * 330 + 5 * 342 + 2 * 342) / 12 = 337 and - # COLA = 37 / 300. Reading the overwritten February instant as an - # annual projection would instead give 42 / 300 — so this assertion - # fails against the raw-February-read regression it guards against. + # COLA = 37 / 300. Reading the overwritten February instant as a + # projection would instead give 42 / 300 — so this assertion fails + # against the raw-February-read regression it guards against. values = _base_series() _monthly(values, 2025, 9, 2026, 1, 330) _monthly(values, 2026, 2, 2026, 6, 342) assert _cola_on_series(values, 2027) == pytest.approx(37 / 300) - # Later, fully unobserved windows still read their own projection - # points: tax year 2028 uses February 2027 (361). - assert _cola_on_series(values, 2028) == pytest.approx(61 / 300) + # Later, fully unobserved windows still read their own tax-year + # projection: tax year 2028 uses 358. + assert _cola_on_series(values, 2028) == pytest.approx(58 / 300) def test_cola_observations_ending_on_a_february_stay_conservative(): diff --git a/policyengine_us/tests/policy/baseline/parameters/test_uprating_extensions.py b/policyengine_us/tests/policy/baseline/parameters/test_uprating_extensions.py index 6da33a15d0c..2fdee86c0d6 100644 --- a/policyengine_us/tests/policy/baseline/parameters/test_uprating_extensions.py +++ b/policyengine_us/tests/policy/baseline/parameters/test_uprating_extensions.py @@ -479,14 +479,20 @@ def months(start_year, start_month, end_year, end_month, level): c_cpi_u = { **months(2015, 9, 2016, 8, 150), **months(2019, 9, 2020, 8, 60), - # Monthly observations end in August 2030; later years hold annual - # projection points at February instants. + # Monthly observations end in August 2030; later years hold + # calendar-year projection points at February instants. **months(2029, 9, 2030, 8, 180), - "2032-02-01": 195, + "2032-02-01": 199, } cpi = SimpleNamespace( cpi_u=Parameter("cpi_u", data=cpi_u), c_cpi_u=Parameter("c_cpi_u", data=c_cpi_u), + # CBO's projections of the 12 months ending the August before each + # tax year. + tax_year_projection=SimpleNamespace( + cpi_u=Parameter("cpi_u_projection", data={"2033-01-01": 390}), + c_cpi_u=Parameter("c_cpi_u_projection", data={"2033-01-01": 195}), + ), ) return SimpleNamespace(gov=SimpleNamespace(bls=SimpleNamespace(cpi=cpi))) @@ -499,7 +505,8 @@ def test_irs_cola_follows_1f3_on_a_synthetic_index(): assert get_irs_cola_denominator(parameters, 1997) == pytest.approx(75) # Tax year 2031 reads the fully observed Sep 2029-Aug 2030 window (180). assert get_irs_cola(parameters, 2031, 1997) == pytest.approx(180 / 75 - 1) - # Tax year 2033 has no observed month and reads the 2032 projection point. + # Tax year 2033 has no observed month, so it reads CBO's tax-year + # projection (195), not the 2032 calendar-year point (199). assert get_irs_cola(parameters, 2033, 1997) == pytest.approx(195 / 75 - 1) # "The percentage (if any)": the Sep 2019-Aug 2020 window (60) is below # the denominator, so the tax year 2021 COLA is zero, not negative. @@ -559,10 +566,14 @@ def encoded_dependent_standard_deduction_values(name): def test_dependent_standard_deduction_encoded_values_take_precedence(): - """IRS and CBO values in the YAML survive the statutory extension.""" + """IRS values in the YAML survive the statutory extension. + + Every encoded value is an IRS publication that the statute reproduces, + so precedence for a non-statutory value is checked on the synthetic tree + in the next test. + """ dependent = PARAMETERS.gov.irs.deductions.standard.dependent - statute_differs = [] - for name, base, base_year in DEPENDENT_STANDARD_DEDUCTION_STATUTORY_BASES: + for name, _, _ in DEPENDENT_STANDARD_DEDUCTION_STATUTORY_BASES: parameter = getattr(dependent, name) # The 0001 placeholder in additional_earned_income.yaml is not a year. encoded = { @@ -572,14 +583,6 @@ def test_dependent_standard_deduction_encoded_values_take_precedence(): } for year, value in encoded.items(): assert parameter(f"{year}-01-01") == value, (name, year) - if value != statutory_dependent_standard_deduction_amount( - base, base_year, year - ): - statute_differs.append((name, year)) - # The check only bites where an encoded value differs from the statutory - # computation on the model's index: CBO's floor does in 2031 and 2034 - # (#9608). - assert statute_differs def test_dependent_standard_deduction_extension_starts_after_last_encoded_year(): @@ -595,10 +598,19 @@ def months(start_year, level): cpi_u=Parameter( "cpi_u", data={**months(1986, 40), **months(1996, 100), **months(2015, 200)} ), - # Monthly observations end in August 2030; 2032 holds a projection. + # Monthly observations end in August 2030; 2032 holds a calendar-year + # point, which windows do not read. c_cpi_u=Parameter( "c_cpi_u", - data={**months(2015, 150), **months(2029, 190), "2032-02-01": 205}, + data={**months(2015, 150), **months(2029, 190), "2032-02-01": 211}, + ), + # CBO's projections of the 12 months ending the August before each + # tax year. + tax_year_projection=SimpleNamespace( + cpi_u=Parameter("cpi_u_projection", data={"2033-01-01": 410}), + c_cpi_u=Parameter( + "c_cpi_u_projection", data={"2032-01-01": 190, "2033-01-01": 205} + ), ), ) # A node, so Parameter.update can reach a parent. @@ -630,13 +642,13 @@ def months(start_year, level): assert dependent.additional_earned_income("2030-01-01") == 999 # Denominators: 1987 base 40 x 150 / 200 = 30; 1997 base 100 x 150 / 200 = 75. # 2031 reads the observed Sep 2029-Aug 2030 window (190); 2032 has no - # observed month and no projection point of its own, so it reads 190 too. + # observed month and reads its tax-year projection, also 190. # Floor: $500 x (190 / 30 - 1) = $2,666.67, rounded down to $2,650. # Addition: $250 x (190 / 75 - 1) = $383.33, rounded down to $350. for year in (2031, 2032): assert dependent.amount(f"{year}-01-01") == 3_150 assert dependent.additional_earned_income(f"{year}-01-01") == 600 - # 2033 reads the 2032 projection point (205). + # 2033 reads its tax-year projection (205), not the calendar-year point. # Floor: $500 x (205 / 30 - 1) = $2,916.67, rounded down to $2,900. # Addition: $250 x (205 / 75 - 1) = $433.33, rounded down to $400. assert dependent.amount("2033-01-01") == 3_400 @@ -670,3 +682,39 @@ def test_dependent_standard_deduction_projections_follow_statute(): assert dependent.additional_earned_income("2032-01-01") == 550 assert dependent.additional_earned_income("2042-01-01") == 650 assert dependent.amount("2042-01-01") == 1_900 + + +def test_statute_on_the_index_reproduces_cbo_dependent_floor_projections(): + """CBO's projected floor is 63(c)(4) applied to CBO's projected index. + + CBO Tax Parameters, February 2026, sheet 1: row 68 ("Dependent filers + (unearned)") by tax year. The model derives the floor from row 153, the + C-CPI-U over the 12 months ending the previous August, for 2028-2036, + and from BLS observations for 2027, so matching row 68 in every year + checks both the index and the statute encoding. + """ + cbo_floor = { + 2027: 1_400, + 2028: 1_450, + 2029: 1_450, + 2030: 1_500, + 2031: 1_500, + 2032: 1_550, + 2033: 1_600, + 2034: 1_600, + 2035: 1_650, + 2036: 1_700, + } + dependent = PARAMETERS.gov.irs.deductions.standard.dependent + for year, amount in cbo_floor.items(): + assert dependent.amount(f"{year}-01-01") == amount, year + + +def test_irs_uprating_is_the_c_cpi_u_for_12_months_ending_august(): + """1(f)(6)(B): the index for tax year T averages Sep T-2 through Aug T-1.""" + uprating = PARAMETERS.gov.irs.uprating + # CBO's actual tax year 2026 index (Tax Parameters row 153): 177.114. + assert uprating("2026-01-01") == pytest.approx(177.114, abs=5e-4) + # No BLS observation covers Sep 2026-Aug 2027: CBO's projection. + assert uprating("2028-01-01") == pytest.approx(185.881) + assert uprating("2036-01-01") == pytest.approx(217.68) From 8c48647c78684ada504153e6f32176c5ed640c8d Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Thu, 8 Oct 2026 10:51:45 -0400 Subject: [PATCH 2/5] Complete observed CPI windows through August 2026 --- changelog.d/irs-index-tax-year-cpi.fixed.md | 2 +- policyengine_us/parameters/gov/bls/cpi/c_cpi_u.yaml | 4 +++- policyengine_us/parameters/gov/bls/cpi/cpi_u.yaml | 4 +++- .../tests/core/test_state_uprating_rounding.py | 8 +++++++- 4 files changed, 14 insertions(+), 4 deletions(-) diff --git a/changelog.d/irs-index-tax-year-cpi.fixed.md b/changelog.d/irs-index-tax-year-cpi.fixed.md index 95b71bb3b2a..420fa3b7fa5 100644 --- a/changelog.d/irs-index-tax-year-cpi.fixed.md +++ b/changelog.d/irs-index-tax-year-cpi.fixed.md @@ -1 +1 @@ -Correct IRS September–August inflation windows and separate CBO tax-year projections while preserving published amounts; calculate dependent deduction limits from statutory bases, separate Vermont and Wisconsin unchained CPI schedules, preserve CBO calendar forecast anchors, and encode published 2026 Wisconsin and California tax amounts. +Correct IRS September–August inflation windows and separate CBO tax-year projections while preserving published amounts; calculate dependent deduction limits from statutory bases, separate Vermont and Wisconsin unchained CPI schedules, preserve CBO calendar forecast anchors, and encode published 2026 Wisconsin and California tax amounts. Update observed chained and seasonally adjusted CPI-U through August 2026. diff --git a/policyengine_us/parameters/gov/bls/cpi/c_cpi_u.yaml b/policyengine_us/parameters/gov/bls/cpi/c_cpi_u.yaml index 3195bb8ed00..148074018ad 100644 --- a/policyengine_us/parameters/gov/bls/cpi/c_cpi_u.yaml +++ b/policyengine_us/parameters/gov/bls/cpi/c_cpi_u.yaml @@ -320,6 +320,8 @@ values: 2026-04-01: 184.499 2026-05-01: 185.589 2026-06-01: 184.992 + 2026-07-01: 185.162 + 2026-08-01: 185.739 # CBO's February 2026 projections of each calendar year's average C-CPI-U. # Tax parameters use the average over the 12 months ending August instead; # CBO's projections of that average are in tax_year_projection/c_cpi_u.yaml. @@ -338,7 +340,7 @@ metadata: unit: currency-USD label: Chained CPI-U reference: - - title: BLS | Chained CPI-U, U.S. city average, all items (SUUR0000SA0), monthly values through June 2026 + - title: BLS | Chained CPI-U, U.S. city average, all items (SUUR0000SA0), monthly values through August 2026 href: https://data.bls.gov/timeseries/SUUR0000SA0 - title: CBO | Tax Parameters and Effective Marginal Tax Rates | June 2024 # Source: https://www.cbo.gov/data/budget-economic-data#10 diff --git a/policyengine_us/parameters/gov/bls/cpi/cpi_u.yaml b/policyengine_us/parameters/gov/bls/cpi/cpi_u.yaml index 9ee95b3415e..3361530da39 100644 --- a/policyengine_us/parameters/gov/bls/cpi/cpi_u.yaml +++ b/policyengine_us/parameters/gov/bls/cpi/cpi_u.yaml @@ -1363,6 +1363,8 @@ values: 2026-04-01: 332.407 2026-05-01: 333.979 2026-06-01: 332.568 + 2026-07-01: 332.813 + 2026-08-01: 334.131 # CBO's February 2026 projections of each calendar year's average CPI-U. # Tax parameters use the average over the 12 months ending August instead; # CBO's projections of that average are in tax_year_projection/cpi_u.yaml. @@ -1381,7 +1383,7 @@ metadata: label: CPI-U economy: false reference: - - title: BLS | CPI-U, U.S. city average, all items, seasonally adjusted (CUSR0000SA0), monthly values through June 2026 + - title: BLS | CPI-U, U.S. city average, all items, seasonally adjusted (CUSR0000SA0), monthly values through August 2026 href: https://data.bls.gov/timeseries/CUSR0000SA0 - title: CBO | Tax Parameters and Effective Marginal Tax Rates | June 2024 # Source: https://www.cbo.gov/data/budget-economic-data#10 diff --git a/policyengine_us/tests/core/test_state_uprating_rounding.py b/policyengine_us/tests/core/test_state_uprating_rounding.py index 12f3d0985af..c1ef927fac5 100644 --- a/policyengine_us/tests/core/test_state_uprating_rounding.py +++ b/policyengine_us/tests/core/test_state_uprating_rounding.py @@ -184,8 +184,14 @@ def test_sc_dependent_deductions_round_down_to_ten_dollars(): def test_sc_income_tax_rounding_applies_to_threshold_not_rates(): scale = SYSTEM.parameters.gov.states.sc.tax.income.rates + uprating = SYSTEM.parameters.gov.irs.uprating + factor = Decimal(str(uprating("2027-01-01"))) / Decimal(str(uprating("2026-01-01"))) + expected_threshold = int( + (Decimal(30_000) * factor).quantize(Decimal("1E1"), rounding=ROUND_FLOOR) + ) - assert scale.brackets[1].threshold("2027-01-01") == 30_890 + assert scale.brackets[1].threshold("2026-01-01") == 30_000 + assert scale.brackets[1].threshold("2027-01-01") == expected_threshold assert scale.brackets[1].rate("2027-01-01") == pytest.approx(0.0521) assert scale.brackets[2].rate("2027-01-01") == pytest.approx(0.06) From 6f307d62aac5d2033decc04abd1a8a7c9db60357 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Thu, 8 Oct 2026 10:54:53 -0400 Subject: [PATCH 3/5] Isolate reform and eligibility tests from changing CPI forecasts --- .../high_earner_tax/ri_high_earner_tax.yaml | 35 ++++++++++--------- ...social_security_modification_eligible.yaml | 12 ++++--- .../gov/states/ri/tax/income/integration.yaml | 9 +++-- .../ri/tax/income/ri_high_earner_tax.yaml | 14 ++++++-- .../contrib/states/ca/ab2591/ca_ab2591.yaml | 10 +++--- .../ut_dependent_exemption.yaml | 16 ++++++--- 6 files changed, 60 insertions(+), 36 deletions(-) diff --git a/policyengine_us/tests/policy/baseline/contrib/states/ri/high_earner_tax/ri_high_earner_tax.yaml b/policyengine_us/tests/policy/baseline/contrib/states/ri/high_earner_tax/ri_high_earner_tax.yaml index a94fb67aa54..610af6034ba 100644 --- a/policyengine_us/tests/policy/baseline/contrib/states/ri/high_earner_tax/ri_high_earner_tax.yaml +++ b/policyengine_us/tests/policy/baseline/contrib/states/ri/high_earner_tax/ri_high_earner_tax.yaml @@ -1,22 +1,18 @@ # Test RI H7317 Additional Tax on High-Income Filers # For 2027+: 3% additional tax on income above $640,000 (inflation-adjusted). -# The $640,000 threshold has an explicit value only for 2026-01-01; #8905 -# unfroze its `uprating:` block (previously a silently-ignored sibling of -# `values:`), so 2027 is now gov.irs.uprating(2027)/gov.irs.uprating(2026) * -# 640,000, rounded down to the nearest 50 -- not yet an officially published -# 2027 figure (none exists for this proposed reform). -# -# The baseline tax below these tests also moved: gov/states/ri/tax/income/ -# rate.yaml's bracket thresholds ($79,900 @ bracket[1] / $181,650 @ bracket[2] -# in 2025) were frozen by the same #8905 bug and are now gov.irs.uprating -# projections ($84,450 / $192,000 for 2027) from RI's official 2026 -# thresholds -- this remains a projection pending RI's 2027 publication. +# Retain the original fixture's synthetic 2027 forecast assumptions: +# baseline bracket thresholds of $84,450 / $192,000 and a proposed surtax +# threshold of $659,050. These inputs isolate reform activation and tax +# stacking; they are not published Rhode Island tax-year amounts. - name: RI high earner tax - income below threshold (no surtax) period: 2027 reforms: policyengine_us.reforms.states.ri.high_earner_tax.ri_high_earner_tax_reform.ri_high_earner_tax input: gov.contrib.states.ri.high_earner_tax.in_effect: true + gov.contrib.states.ri.high_earner_tax.brackets[1].threshold: 659_050 + gov.states.ri.tax.income.rate[1].threshold: 84_450 + gov.states.ri.tax.income.rate[2].threshold: 192_000 people: person1: age: 45 @@ -30,7 +26,7 @@ members: [person1] state_code: RI output: - # Income below the 2027 threshold - only baseline tax applies, surtax = 0. + # Income below the synthetic threshold - only baseline tax applies, surtax = 0. # Baseline: .0375*84,450 + .0475*(192,000-84,450) + .0599*(500,000-192,000) = 26,724.70 ri_income_tax_before_non_refundable_credits: 26_724.70 @@ -39,6 +35,9 @@ reforms: policyengine_us.reforms.states.ri.high_earner_tax.ri_high_earner_tax_reform.ri_high_earner_tax input: gov.contrib.states.ri.high_earner_tax.in_effect: true + gov.contrib.states.ri.high_earner_tax.brackets[1].threshold: 659_050 + gov.states.ri.tax.income.rate[1].threshold: 84_450 + gov.states.ri.tax.income.rate[2].threshold: 192_000 people: person1: age: 45 @@ -52,8 +51,7 @@ members: [person1] state_code: RI output: - # Baseline tax (41,699.70) + 3% surtax on income above the 2027 threshold - # (gov.irs.uprating-projected from $640,000 @ 2026 to $659,050 @ 2027) + # Baseline tax (41,699.70) + 3% surtax above the synthetic $659,050 threshold. # Baseline: .0375*84,450 + .0475*(192,000-84,450) + .0599*(750,000-192,000) = 41,699.70 # Surtax: (750,000 - 659,050) * 0.03 = 90,950 * 0.03 = 2,728.5 # Total: 41,699.70 + 2,728.5 = 44,428.20 @@ -64,6 +62,9 @@ reforms: policyengine_us.reforms.states.ri.high_earner_tax.ri_high_earner_tax_reform.ri_high_earner_tax input: gov.contrib.states.ri.high_earner_tax.in_effect: true + gov.contrib.states.ri.high_earner_tax.brackets[1].threshold: 659_050 + gov.states.ri.tax.income.rate[1].threshold: 84_450 + gov.states.ri.tax.income.rate[2].threshold: 192_000 people: person1: age: 45 @@ -77,8 +78,7 @@ members: [person1] state_code: RI output: - # Baseline tax (56,674.70) + 3% surtax on income above the 2027 threshold - # (gov.irs.uprating-projected from $640,000 @ 2026 to $659,050 @ 2027) + # Baseline tax (56,674.70) + 3% surtax above the synthetic $659,050 threshold. # Baseline: .0375*84,450 + .0475*(192,000-84,450) + .0599*(1,000,000-192,000) = 56,674.70 # Surtax: (1,000,000 - 659,050) * 0.03 = 340,950 * 0.03 = 10,228.5 # Total: 56,674.70 + 10,228.5 = 66,903.20 @@ -89,6 +89,9 @@ reforms: policyengine_us.reforms.states.ri.high_earner_tax.ri_high_earner_tax_reform.ri_high_earner_tax input: gov.contrib.states.ri.high_earner_tax.in_effect: false + gov.contrib.states.ri.high_earner_tax.brackets[1].threshold: 659_050 + gov.states.ri.tax.income.rate[1].threshold: 84_450 + gov.states.ri.tax.income.rate[2].threshold: 192_000 people: person1: age: 45 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ri/tax/income/agi/subtractions/social_security/ri_social_security_modification_eligible.yaml b/policyengine_us/tests/policy/baseline/gov/states/ri/tax/income/agi/subtractions/social_security/ri_social_security_modification_eligible.yaml index 7f9b6848742..c12d59b5e29 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ri/tax/income/agi/subtractions/social_security/ri_social_security_modification_eligible.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ri/tax/income/agi/subtractions/social_security/ri_social_security_modification_eligible.yaml @@ -109,14 +109,13 @@ output: ri_social_security_modification_eligible: false -# The 2027 income limit is a gov.irs.uprating CPI projection from the 2025 -# explicit value (#8905 unfroze the sibling-of-values `uprating:` block that -# had been silently ignored, freezing 2026/2027 at the 2025 figure). RI has -# not yet published its own 2027 adjustment; the official figure supersedes -# this projection once released (see #8905). +# Retain the original fixture's synthetic 2027 income limits to isolate the +# strict eligibility boundary after removal of the age requirement. These +# assumptions are not published Rhode Island tax-year amounts. - name: 2027 - Single filer just below income limit is eligible period: 2027 input: + gov.states.ri.tax.income.agi.subtractions.social_security.limit.income.SINGLE: 112_650 adjusted_gross_income: 112_649 filing_status: SINGLE older_spouse_birth_year: 1990 @@ -127,6 +126,7 @@ - name: 2027 - Single filer at income limit is ineligible period: 2027 input: + gov.states.ri.tax.income.agi.subtractions.social_security.limit.income.SINGLE: 112_650 adjusted_gross_income: 112_650 filing_status: SINGLE older_spouse_birth_year: 1990 @@ -137,6 +137,7 @@ - name: 2027 - Joint filer just below income limit is eligible period: 2027 input: + gov.states.ri.tax.income.agi.subtractions.social_security.limit.income.JOINT: 140_850 adjusted_gross_income: 140_849 filing_status: JOINT older_spouse_birth_year: 1990 @@ -147,6 +148,7 @@ - name: 2027 - Joint filer at income limit is ineligible period: 2027 input: + gov.states.ri.tax.income.agi.subtractions.social_security.limit.income.JOINT: 140_850 adjusted_gross_income: 140_850 filing_status: JOINT older_spouse_birth_year: 1990 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ri/tax/income/integration.yaml b/policyengine_us/tests/policy/baseline/gov/states/ri/tax/income/integration.yaml index c0c3dbbfce7..40c3d20a350 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ri/tax/income/integration.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ri/tax/income/integration.yaml @@ -239,6 +239,10 @@ absolute_error_margin: 0.01 period: 2027 input: + # Synthetic forecast assumptions retained from the original fixture to + # isolate credit/surtax interaction; not published RI tax-year amounts. + gov.states.ri.tax.income.rate[1].threshold: 84_450 + gov.states.ri.tax.income.rate[2].threshold: 192_000 people: person1: age: 40 @@ -264,8 +268,7 @@ ri_ctc: 330 ri_high_earner_tax: 2_500 ri_social_security_modification: 10_000 - # Baseline tax uses gov/states/ri/tax/income/rate.yaml's bracket - # thresholds, gov.irs.uprating-projected for 2027 from the official 2026 - # thresholds (84,450 / 192,000): 71,649.70 before credits, + 2,500 surtax, + # Baseline tax uses the synthetic bracket thresholds supplied above + # (84,450 / 192,000): 71,649.70 before credits, + 2,500 surtax, # - 330 CTC = 73,819.70. ri_income_tax: 73_819.70 diff --git a/policyengine_us/tests/policy/baseline/gov/states/ri/tax/income/ri_high_earner_tax.yaml b/policyengine_us/tests/policy/baseline/gov/states/ri/tax/income/ri_high_earner_tax.yaml index e7e572d10ac..bda137fea82 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/ri/tax/income/ri_high_earner_tax.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/ri/tax/income/ri_high_earner_tax.yaml @@ -20,6 +20,9 @@ period: 2028 absolute_error_margin: 0.01 input: + # Synthetic forecast assumption retained from the original fixture to + # isolate the surtax rate; not a published Rhode Island tax-year amount. + gov.states.ri.tax.income.high_earner_tax.threshold: 1_022_600 state_code: RI ri_taxable_income: 1_250_000 output: @@ -30,6 +33,9 @@ period: 2029 absolute_error_margin: 0.01 input: + # Synthetic forecast assumption retained from the original fixture to + # isolate the surtax rate; not a published Rhode Island tax-year amount. + gov.states.ri.tax.income.high_earner_tax.threshold: 1_039_400 state_code: RI ri_taxable_income: 1_250_000 output: @@ -40,12 +46,14 @@ period: 2027 absolute_error_margin: 0.01 input: + # Synthetic forecast assumptions retained from the original fixture to + # isolate tax stacking; not published Rhode Island tax-year amounts. + gov.states.ri.tax.income.rate[1].threshold: 84_450 + gov.states.ri.tax.income.rate[2].threshold: 192_000 state_code: RI ri_taxable_income: 1_250_000 output: - # Baseline tax uses gov/states/ri/tax/income/rate.yaml's bracket - # thresholds, gov.irs.uprating-projected for 2027 from the official 2026 - # thresholds: + # Baseline tax uses the synthetic bracket thresholds supplied above: # .0375*84,450 + .0475*(192,000-84,450) + .0599*(1,250,000-192,000) = 71,649.70 # + 1% high-earner surtax on income above the (explicit, unaffected) # $1,000,000 2027 threshold: (1,250,000 - 1,000,000) * 0.01 = 2,500 diff --git a/policyengine_us/tests/policy/contrib/states/ca/ab2591/ca_ab2591.yaml b/policyengine_us/tests/policy/contrib/states/ca/ab2591/ca_ab2591.yaml index 760f94e4e75..2461bd119f8 100644 --- a/policyengine_us/tests/policy/contrib/states/ca/ab2591/ca_ab2591.yaml +++ b/policyengine_us/tests/policy/contrib/states/ca/ab2591/ca_ab2591.yaml @@ -2,7 +2,7 @@ # Allows taxpayers to elect a standard deduction equal to the federal # poverty line for their household size. # Effective for taxable years beginning on or after July 1, 2027. -# 2027 federal poverty guidelines (CONTIGUOUS_US): +# Existing 2027 federal poverty guideline projections (CONTIGUOUS_US): # first person = $16,334 # additional person = $5,813 # 1-person FPL = $16,334 @@ -168,6 +168,9 @@ absolute_error_margin: 1 reforms: policyengine_us.reforms.states.ca.ab2591.ab2591.ca_ab2591 input: + # Synthetic forecast assumption retained from the original fixture to + # isolate reform inactivity; not a published California tax-year amount. + gov.states.ca.tax.income.deductions.standard.amount.SINGLE: 6_009.22 gov.contrib.states.ca.ab2591.in_effect: false people: person1: @@ -182,9 +185,8 @@ members: [person1] state_code: CA output: - # 2025 indexed value ($5,706) uprated by CA CPI to 2027; the 2027 - # CA CPI point is an IRS-uprating projection, so it reflects the - # observed chained-CPI actuals through June 2026. + # The inactive reform leaves the synthetic baseline amount supplied + # above unchanged. ca_standard_deduction: 6_009.22 - name: Non-California filer unaffected by the reform. diff --git a/policyengine_us/tests/policy/contrib/states/ut/dependent_exemption/ut_dependent_exemption.yaml b/policyengine_us/tests/policy/contrib/states/ut/dependent_exemption/ut_dependent_exemption.yaml index 49278857c1e..da37f9913d1 100644 --- a/policyengine_us/tests/policy/contrib/states/ut/dependent_exemption/ut_dependent_exemption.yaml +++ b/policyengine_us/tests/policy/contrib/states/ut/dependent_exemption/ut_dependent_exemption.yaml @@ -1,8 +1,12 @@ +# The baseline exemption inputs below retain the original fixture's +# synthetic 2026 forecast assumption to isolate reform behavior. The $2,159 +# per-dependent amount is not asserted as a published Utah tax-year amount. - name: Under-18 repeal zeroes the young dependent, preserves the older one. period: 2026 absolute_error_margin: 1 reforms: policyengine_us.reforms.states.ut.dependent_exemption.ut_dependent_exemption_reform input: + gov.states.ut.tax.income.credits.taxpayer.personal_exemption: 2_159 gov.contrib.states.ut.dependent_exemption.in_effect: true gov.contrib.states.ut.dependent_exemption.amount: 0 gov.contrib.states.ut.dependent_exemption.age_limit.in_effect: true @@ -27,11 +31,11 @@ members: [person1, child1, person2] state_code: UT output: - # Only the 19-year-old keeps the uprated baseline per-dependent - # exemption (about $2,159 in 2026). + # Only the 19-year-old keeps the synthetic baseline per-dependent + # exemption supplied above. ut_personal_exemption: 2_159 # About $130 above the same household's baseline tax of $1,379: the - # taxpayer credit shrinks by 6% of the repealed ~$2,159 exemption. + # taxpayer credit shrinks by 6% of the repealed $2,159 exemption. # Asserts the exemption flows through to the bottom line. ut_income_tax: 1_508 @@ -40,6 +44,7 @@ absolute_error_margin: 1 reforms: policyengine_us.reforms.states.ut.dependent_exemption.ut_dependent_exemption_reform input: + gov.states.ut.tax.income.credits.taxpayer.personal_exemption: 2_159 gov.contrib.states.ut.dependent_exemption.in_effect: true people: person1: @@ -60,7 +65,7 @@ members: [person1, child1, person2] state_code: UT output: - # Two dependents at the uprated baseline amount. + # Two dependents at the synthetic baseline amount supplied above. ut_personal_exemption: 4_318 - name: A flat reform amount applies to every dependent when the age limit is off. @@ -125,6 +130,7 @@ absolute_error_margin: 1 reforms: policyengine_us.reforms.states.ut.dependent_exemption.ut_dependent_exemption_reform input: + gov.states.ut.tax.income.credits.taxpayer.personal_exemption: 2_159 gov.contrib.states.ut.dependent_exemption.amount: 0 people: person1: @@ -146,5 +152,5 @@ state_code: UT output: # in_effect is false, so the $0 amount does not apply: two dependents - # at the uprated baseline amount. + # at the synthetic baseline amount supplied above. ut_personal_exemption: 4_318 From 0ef13ac750a86be6e1e90d3b3e13afef20134c68 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Thu, 8 Oct 2026 11:18:53 -0400 Subject: [PATCH 4/5] Test Wisconsin phaseouts against statutory August CPI bases --- .../core/test_state_uprating_rounding.py | 29 +++++++++++-------- 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/policyengine_us/tests/core/test_state_uprating_rounding.py b/policyengine_us/tests/core/test_state_uprating_rounding.py index c1ef927fac5..f8df551ae84 100644 --- a/policyengine_us/tests/core/test_state_uprating_rounding.py +++ b/policyengine_us/tests/core/test_state_uprating_rounding.py @@ -273,31 +273,36 @@ def test_vt_income_tax_thresholds_preserve_published_preliminary_2026( @pytest.mark.parametrize( - ("filing_status", "published_2026"), + ("filing_status", "published_2026", "statutory_base", "base_index"), ( # 2026 Form 1-ES instructions, 2026 Standard Deduction schedules. - ("head_of_household", 20_120), - ("joint", 29_040), - ("separate", 13_780), - ("single", 20_120), + ("head_of_household", 20_120, 10_380, "167.1"), + ("joint", 29_040, 21_360, "238.316"), + ("separate", 13_780, 10_140, "238.316"), + ("single", 20_120, 10_380, "167.1"), ), ) -def test_wi_standard_deduction_phase_out_thresholds_preserve_published_2026( +def test_wi_standard_deduction_phase_out_thresholds_use_published_then_statutory_values( filing_status, published_2026, + statutory_base, + base_index, ): # 2026 Form 1-ES instructions, p. 2, Standard Deduction Schedules. + # Future amounts: Wis. Stat. 71.05(22)(dp)-(dt), using prior-August + # CUUR0000SA0 and the statutory 1999 (single/HOH) or 2015 (joint/MFS) base. scale = getattr( SYSTEM.parameters.gov.states.wi.tax.income.deductions.standard.phase_out, filing_status, ) - uprating = SYSTEM.parameters.gov.irs.uprating - assert scale.brackets[1].threshold("2026-01-01") == published_2026 - # Later years project from the published 2026 start, rounded to the - # nearest $10. Expected values follow the loaded index. - factor = uprating("2027-01-01") / uprating("2026-01-01") - expected_2027 = round(published_2026 * factor / 10) * 10 + nsa = SYSTEM.parameters.gov.bls.cpi.cpi_u_nsa + assert nsa("2026-08-01") == 334.980 + base_year = 1999 if filing_status in ("single", "head_of_household") else 2015 + assert nsa(f"{base_year}-08-01") == float(base_index) + expected_2027 = _wi_independent_indexed_amount( + statutory_base, "334.980", base_index, published_2026 + ) assert scale.brackets[1].threshold("2027-01-01") == expected_2027 From 36a8a9705e86a8da9ce201a01f1d363854095365 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Thu, 8 Oct 2026 11:31:39 -0400 Subject: [PATCH 5/5] Preserve the Utah reform fixture phaseout assumption --- .../states/ut/dependent_exemption/ut_dependent_exemption.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/policyengine_us/tests/policy/contrib/states/ut/dependent_exemption/ut_dependent_exemption.yaml b/policyengine_us/tests/policy/contrib/states/ut/dependent_exemption/ut_dependent_exemption.yaml index da37f9913d1..ae021fe146e 100644 --- a/policyengine_us/tests/policy/contrib/states/ut/dependent_exemption/ut_dependent_exemption.yaml +++ b/policyengine_us/tests/policy/contrib/states/ut/dependent_exemption/ut_dependent_exemption.yaml @@ -7,6 +7,9 @@ reforms: policyengine_us.reforms.states.ut.dependent_exemption.ut_dependent_exemption_reform input: gov.states.ut.tax.income.credits.taxpayer.personal_exemption: 2_159 + # Retain the original fixture's synthetic phase-out forecast as well; + # this is not asserted as a published Utah tax-year threshold. + gov.states.ut.tax.income.credits.taxpayer.phase_out.threshold.HEAD_OF_HOUSEHOLD: 27_939.15 gov.contrib.states.ut.dependent_exemption.in_effect: true gov.contrib.states.ut.dependent_exemption.amount: 0 gov.contrib.states.ut.dependent_exemption.age_limit.in_effect: true