Skip to content

Commit b17e083

Browse files
authored
Merge pull request #669 from PolicyEngine/codex/us-data-calibration-contract
Add long-run calibration contracts
2 parents 2da7cf8 + 77766c0 commit b17e083

32 files changed

Lines changed: 11283 additions & 132 deletions

policyengine_us_data/datasets/cps/enhanced_cps.py

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@
1515
ExtendedCPS_2024_Half,
1616
CPS_2024,
1717
)
18+
from policyengine_us_data.utils.randomness import seeded_rng
19+
from policyengine_us_data.utils.takeup import (
20+
ACA_POST_CALIBRATION_PERSON_TARGETS,
21+
extend_aca_takeup_to_match_target,
22+
)
1823
import logging
1924

2025
try:
@@ -23,6 +28,48 @@
2328
torch = None
2429

2530

31+
def _get_period_array(period_values: dict, period: int) -> np.ndarray:
32+
"""Get a period array from a TIME_PERIOD_ARRAYS variable dict."""
33+
value = period_values.get(period)
34+
if value is None:
35+
value = period_values.get(str(period))
36+
if value is None:
37+
raise KeyError(f"Missing period {period}")
38+
return np.asarray(value)
39+
40+
41+
def create_aca_2025_takeup_override(
42+
base_takeup: np.ndarray,
43+
person_enrolled_if_takeup: np.ndarray,
44+
person_weights: np.ndarray,
45+
person_tax_unit_ids: np.ndarray,
46+
tax_unit_ids: np.ndarray,
47+
target_people: float = ACA_POST_CALIBRATION_PERSON_TARGETS[2025],
48+
) -> np.ndarray:
49+
"""Add 2025 ACA takers until weighted APTC enrollment hits target."""
50+
tax_unit_id_to_idx = {
51+
int(tax_unit_id): idx for idx, tax_unit_id in enumerate(tax_unit_ids)
52+
}
53+
person_tax_unit_idx = np.array(
54+
[tax_unit_id_to_idx[int(tax_unit_id)] for tax_unit_id in person_tax_unit_ids],
55+
dtype=np.int64,
56+
)
57+
enrolled_person_weights = np.zeros(len(tax_unit_ids), dtype=np.float64)
58+
np.add.at(
59+
enrolled_person_weights,
60+
person_tax_unit_idx,
61+
person_enrolled_if_takeup.astype(np.float64) * person_weights,
62+
)
63+
draws = seeded_rng("takes_up_aca_if_eligible").random(len(tax_unit_ids))
64+
65+
return extend_aca_takeup_to_match_target(
66+
base_takeup=np.asarray(base_takeup, dtype=bool),
67+
entity_draws=draws,
68+
enrolled_person_weights=enrolled_person_weights,
69+
target_people=target_people,
70+
)
71+
72+
2673
def reweight(
2774
original_weights,
2875
loss_matrix,
@@ -142,6 +189,7 @@ def generate(self):
142189

143190
sim = Microsimulation(dataset=self.input_dataset)
144191
data = sim.dataset.load_dataset()
192+
base_year = int(sim.default_calculation_period)
145193
data["household_weight"] = {}
146194
original_weights = sim.calculate("household_weight")
147195
original_weights = original_weights.values + np.random.normal(
@@ -216,6 +264,52 @@ def generate(self):
216264
f"{int(np.sum(w > 0))} non-zero"
217265
)
218266

267+
if 2025 in ACA_POST_CALIBRATION_PERSON_TARGETS:
268+
sim.set_input(
269+
"household_weight",
270+
base_year,
271+
_get_period_array(data["household_weight"], base_year).astype(
272+
np.float32
273+
),
274+
)
275+
sim.set_input(
276+
"takes_up_aca_if_eligible",
277+
2025,
278+
np.ones(
279+
len(_get_period_array(data["tax_unit_id"], base_year)),
280+
dtype=bool,
281+
),
282+
)
283+
sim.delete_arrays("aca_ptc")
284+
285+
data["takes_up_aca_if_eligible"][2025] = create_aca_2025_takeup_override(
286+
base_takeup=_get_period_array(
287+
data["takes_up_aca_if_eligible"],
288+
base_year,
289+
),
290+
person_enrolled_if_takeup=np.asarray(
291+
sim.calculate(
292+
"aca_ptc",
293+
map_to="person",
294+
period=2025,
295+
use_weights=False,
296+
)
297+
)
298+
> 0,
299+
person_weights=np.asarray(
300+
sim.calculate(
301+
"person_weight",
302+
period=2025,
303+
use_weights=False,
304+
)
305+
),
306+
person_tax_unit_ids=_get_period_array(
307+
data["person_tax_unit_id"],
308+
base_year,
309+
),
310+
tax_unit_ids=_get_period_array(data["tax_unit_id"], base_year),
311+
)
312+
219313
logging.info("Post-generation weight validation passed")
220314

221315
self.save_dataset(data)
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# Long-Run Calibration Assumption Comparison
2+
3+
This note distinguishes between:
4+
5+
- hard microsimulation calibration targets, which directly shape household weights
6+
- tax-side assumptions used to make those targets more comparable to the public Trustees/OACT methodology
7+
8+
The current long-run baseline now adopts a named tax-side assumption,
9+
`trustees-core-thresholds-v1`, before hard-targeting TOB.
10+
11+
| Component | Current `policyengine-us-data` approach | Trustees / OACT published approach | Calibration use |
12+
| --- | --- | --- | --- |
13+
| Population by age | SSA single-year age projections | SSA single-year age projections | Hard target |
14+
| OASDI benefits | Named long-term target source package | Trustees or OACT-patched annual OASDI path | Hard target |
15+
| Taxable payroll | Named long-term target source package | Trustees annual taxable payroll path | Hard target |
16+
| Social Security benefit-tax thresholds | Literal current-law statutory thresholds remain fixed in nominal dollars | Trustees also describe the statutory `$25k/$32k/$0` and `$34k/$44k` thresholds as remaining fixed in nominal dollars | Not separately targeted |
17+
| Federal income-tax brackets | Core ordinary thresholds are wage-indexed after `2034` via `trustees-core-thresholds-v1` | Trustees assume periodic future bracket adjustments; after the tenth projection year, ordinary federal income-tax brackets are assumed to rise with average wages to avoid indefinite bracket creep | Tax-side assumption |
18+
| Standard deduction / aged-blind addition / capital gains thresholds / AMT thresholds | Included in the same `trustees-core-thresholds-v1` bundle | Not parameterized publicly line-by-line, but these are the main additional federal thresholds most likely to affect long-run TOB | Tax-side assumption |
19+
| OASDI TOB | Computed under the core-threshold tax assumption and targeted in `ss-payroll-tob` profiles | Trustees/OACT publish annual revenue paths or ratios, but not a full public household-level micro rule schedule | Hard target |
20+
| HI TOB | Computed under the core-threshold tax assumption and targeted in `ss-payroll-tob` profiles | Trustees publish current-law HI TOB path; OACT OBBBA updates do not currently provide a full public annual HI replacement series | Hard target |
21+
| OBBBA OASDI update | Available through named target source `oact_2025_08_05_provisional` | August 5, 2025 OACT letter provides annual OASDI changes through 2100 | Benchmark / target-source input |
22+
| OBBBA HI update | Provisional bridge only in named target source | No equivalent full public annual HI replacement path located yet | Benchmark only |
23+
24+
## Practical interpretation
25+
26+
- `ss-payroll` remains the core non-TOB hard-target profile.
27+
- `ss-payroll-tob` now means: calibrate on age + OASDI benefits + taxable payroll + TOB under `trustees-core-thresholds-v1`.
28+
- The core-threshold bundle is a best-public approximation, not a literal public Trustees rules schedule.
29+
- Trustees-consistent long-run TOB requires keeping two different tax-side ideas separate:
30+
- the Social Security benefit-tax thresholds remain fixed in nominal dollars
31+
- ordinary federal income-tax brackets are assumed to rise with average wages after the tenth projection year
32+
33+
## Primary-source references
34+
35+
- [SSA 2025 Trustees Report, V.C.7](https://www.ssa.gov/oact/tr/2025/V_C_prog.html)
36+
- States that the law specifies fixed threshold amounts for taxation of Social Security benefits and that those thresholds remain constant in future years.
37+
- Also states that, after the tenth year of the projection period, income-tax brackets are assumed to rise with average wages rather than with `C-CPI-U`.
38+
- [26 U.S.C. § 86](https://www.law.cornell.edu/uscode/text/26/86)
39+
- Statutory basis for the Social Security benefit-tax threshold structure.
40+
- [SSA 2025 Trustees Report, Table VI.G6](https://www.ssa.gov/OACT/TR/2025/VI_G3_OASDHI_dollars.html)
41+
- Published annual average wage index path through `2100`.
42+
- [42 U.S.C. § 430](https://www.law.cornell.edu/uscode/text/42/430) and [20 CFR § 404.1048](https://www.law.cornell.edu/cfr/text/20/404.1048)
43+
- Statutory and regulatory basis for deriving the Social Security contribution and benefit base from the wage index.

0 commit comments

Comments
 (0)