Repository navigation
Description
CountryTaxBenefitSystem(reform=(policy, assumption)) can drop the policy's values for the parameters a Trustees economic assumption rewrites, and it raises no error or warning. Both assumptions do this: trustees_2026_economic_assumptions_reform, and any reform that calls apply_trustees_2025_economic_assumptions. With the assumption first, the same pair keeps the policy's values.
We tell callers in the 2026 module's docstring to list the assumption first, and no code enforces that order. apply_trustees_2025_economic_assumptions has no docstring. Neither assumption runs unless a caller applies it, so default results don't change.
For example, take the 2026 assumption and a policy that sets the payroll cap, gov.irs.payroll.social_security.cap, to $10 million from 2030. A worker earns $500,000 of wages in 2030.
| Reform tuple | Cap, 2029 | Cap, 2030 | Cap, 2050 | employee_social_security_tax, 2030 |
|---|---|---|---|---|
(assumption, policy) |
$209,100 | $10,000,000 | $10,000,000 | $31,000.00 |
(policy, assumption) |
$209,100 | $217,800 | $462,900 | $13,503.60 |
In policy-first order, the system caps the worker's taxable wages at $217,800, the assumption's own cap for 2030. The worker pays 6.2% of $217,800, and under the policy they would pay 6.2% of $500,000.
What the policy-first order loses
- Both assumptions recompute three parameters for every year after a seed year: the payroll cap, the benefit wage base (
gov.ssa.social_security.wage_base) and the owners' draw copy of the cap (gov.contrib.ssa.owners_draw.current_law_cap). The 2026 assumption seeds from 2027 and the 2025 one from 2035. They set each later year to the larger of the previous year's value and the wage-indexed formula, so they erase a policy value that starts after the seed year. Under the 2026 assumption, a $10 million cap from 2028, 2030, 2036 or 2037 reads the assumption's path in every year. Under the 2025 assumption, a $10 million cap from 2030 stands, and one from 2036 reads $261,900 in 2036. A policy value that covers the seed year stands until the formula exceeds it. For example, an owners' draw copy of $200,000 from 2027 reads $201,000 in 2028 under the 2026 assumption and $261,900 in 2036 under the 2025 one. - Both assumptions rewrite seven more wage-indexed amounts for every year from 2027: the quarter-of-coverage threshold, both earnings-test exempt amounts, both PIA bend points and both substantial gainful activity amounts. A policy that triples any of the seven from 2027 or from 2030 keeps none of its values. For example, a first bend point of $4,551 from 2030 reads $1,517 in 2030 under the 2026 assumption.
- The 2026 assumption also re-derives 15 parameters whose
upratingmetadata names one of its two paths. Thirteen namegov.ssa.uprating, including both SSI federal benefit amounts, and two thresholds undergov.irs.income.exemption.overtimenamegov.ssa.nawi. For example, an SSI individual amount of $2,000 a month from 2030 reads $1,105.56 in 2030 and $1,776.57 in 2050. The 2025 assumption writes none of the 15.
With the assumption first, each of these policies keeps its value in every year it sets.
A one-year policy carries forward only from the seed year
A built system applies its reform set twice, so in either order the assumption runs at least once after the policy and reads the policy's value for the seed year. Under the 2026 assumption, a cap for 2027 alone becomes the floor for every later year:
| Cap policy | 2027 | 2028 | 2029 | 2030 | 2033 | 2034 |
|---|---|---|---|---|---|---|
| None | $192,300 | $201,000 | $209,100 | $217,800 | $248,100 | $258,900 |
| $250,000 for 2027 only | $250,000 | $250,000 | $250,000 | $250,000 | $250,000 | $258,900 |
| $250,000 for 2028 only, after the assumption | $192,300 | $250,000 | $209,100 | $217,800 | $248,100 | $258,900 |
A cap of inf for 2027 only removes the cap in every year through 2100. The wage base and the owners' draw copy carry a 2027 value the same way. Under the 2025 assumption, a $350,000 cap for 2035 only holds through 2044, while one for 2034 only or 2036 only moves no other year. With no assumption, a $250,000 cap for 2027 only leaves 2028 at $199,200.
What the code should do
Each policy's values should stand in every year the policy sets, in either order, or the constructor should refuse an order it can't honor.
The carry should follow one rule in every year. Under 42 U.S.C. 430, the Commissioner of Social Security determines each new base as the base "in effect in the year in which the determination is made or, if larger," the wage-indexed product. The assumptions apply that floor to a policy's value only when the value covers the seed year, and a system with no assumption doesn't apply it.
Where the code does this
CountryTaxBenefitSystem.__init__applies the reform set once and then again with the structural reforms. In(policy, assumption)order the assumption therefore writes after the policy in both runs.extend_social_security_payroll_caploops over every year afterlast_projected_year. For year Y it reads the value for Y - 1 and writes the larger of that and the formula, whatever Y holds. It reads its 1994 base from the same parameter, so a policy value that covers 1994 changes the formula for every later year.extend_social_security_wage_indexed_parametersrewrites the wage base and the seven other wage-indexed amounts for every year from 2027.- The 2026 assumption calls the cap extension for the live cap, the owners' draw copy and the wage base with
last_projected_year2027, then the wage-indexed extension, thenapply_trustees_2026_path_readers. _rederive_readerreads each path reader's value at the last instant its YAML file states and writes every year after the path's last unchanged year: from 2028 forgov.ssa.upratingreaders and from 2026 forgov.ssa.nawireaders.- The 2025 assumption makes the same three cap calls with
last_projected_year=2035, then the wage-indexed extension. - A 2026 test replays both orders for a cap policy that starts in any year from 2027 and checks the wage base and the owners' draw copy, not the cap. We note in its strategy's docstring that the second pass overwrites a policy cap that starts in 2028 or later.
We listed the carry and the owners' draw copy case among the known limits in #10650's description.
Reproduction
Run this at commit 3f6e604:
from policyengine_core.reforms import Reform
from policyengine_us import CountryTaxBenefitSystem, Simulation
from policyengine_us.reforms.ssa.trustees_2026 import (
trustees_2026_economic_assumptions_reform as assumption,
)
CAP = "gov.irs.payroll.social_security.cap"
SSI = "gov.ssa.ssi.amount.individual"
def policy(path, period, value):
return Reform.from_dict({path: {period: value}}, country_id="us")
def show(label, reform, path, years, wages_year=None):
system = CountryTaxBenefitSystem(reform=reform)
parameter = system.parameters.get_child(path)
print(label, [round(float(parameter(f"{year}-01-01")), 2) for year in years])
if wages_year:
situation = {
"people": {
"worker": {
"age": {wages_year: 40},
"employment_income": {wages_year: 500_000},
}
}
}
simulation = Simulation(situation=situation, tax_benefit_system=system)
tax = simulation.calculate("employee_social_security_tax", wages_year)[0]
print(" employee Social Security tax on $500,000 of wages:", round(float(tax), 2))
cap_from_2030 = policy(CAP, "2030-01-01", 10_000_000)
show("(assumption, cap):", (assumption, cap_from_2030), CAP, (2029, 2030, 2050), 2030)
show("(cap, assumption):", (cap_from_2030, assumption), CAP, (2029, 2030, 2050), 2030)
ssi_from_2030 = policy(SSI, "2030-01-01", 2_000)
show("(SSI, assumption):", (ssi_from_2030, assumption), SSI, (2029, 2030, 2050))
cap_2027_only = policy(CAP, "2027-01-01.2027-12-31", 250_000)
show("(assumption, cap for 2027 only):", (assumption, cap_2027_only), CAP, range(2027, 2035))It prints:
(assumption, cap): [209100.0, 10000000.0, 10000000.0]
employee Social Security tax on $500,000 of wages: 31000.0
(cap, assumption): [209100.0, 217800.0, 462900.0]
employee Social Security tax on $500,000 of wages: 13503.6
(SSI, assumption): [1079.63, 1105.56, 1776.57]
(assumption, cap for 2027 only): [250000.0, 250000.0, 250000.0, 250000.0, 250000.0, 250000.0, 250000.0, 258900.0]
A possible fix (not run)
We could keep a copy of each parameter the assumptions rewrite from before any reform runs, as the constructor already keeps one of the cap. The extensions would then take their 1994 base and seed-year value from that copy and leave alone any year a policy changed. As a smaller step, the code could raise an error when a policy that sets one of these parameters precedes an assumption, and the carry would stay.
In the same change, we could update the cap docstring of extend_social_security_wage_indexed_parameters, where we suggest passing a copy of the cap. Both assumptions pass the wage base itself, so the call rewrites the wage base with its own values.
Activity