Skip to content

Commit 9649e4b

Browse files
authored
Merge pull request #460 from PolicyEngine/tob-calibrate
Add --use-tob flag for TOB revenue calibration
2 parents 28e6075 + be4027f commit 9649e4b

File tree

9 files changed

+620
-94
lines changed

9 files changed

+620
-94
lines changed

changelog_entry.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
- bump: minor
2+
changes:
3+
added:
4+
- Added --use-tob flag for TOB (Taxation of Benefits) revenue calibration targeting OASDI and HI trust fund revenue

policyengine_us_data/datasets/cps/long_term/README.md

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66
Run projections using `run_household_projection.py`:
77

88
```bash
9-
# Recommended: GREG with all three constraint types
10-
python run_household_projection.py 2100 --greg --use-ss --use-payroll --save-h5
9+
# Recommended: GREG with all constraint types
10+
python run_household_projection.py 2100 --greg --use-ss --use-payroll --use-tob --save-h5
1111

1212
# IPF with only age distribution constraints (faster, less accurate)
1313
python run_household_projection.py 2050
@@ -21,6 +21,7 @@ python run_household_projection.py 2100 --greg --use-ss
2121
- `--greg`: Use GREG calibration instead of IPF
2222
- `--use-ss`: Include Social Security benefit totals as calibration target (requires `--greg`)
2323
- `--use-payroll`: Include taxable payroll totals as calibration target (requires `--greg`)
24+
- `--use-tob`: Include TOB (Taxation of Benefits) revenue as calibration target (requires `--greg`)
2425
- `--save-h5`: Save year-specific .h5 files to `./projected_datasets/` directory
2526

2627
**Estimated runtime:** ~2 minutes/year without `--save-h5`, ~3 minutes/year with `--save-h5`
@@ -36,7 +37,7 @@ python run_household_projection.py 2100 --greg --use-ss
3637

3738
**GREG (Generalized Regression Estimator)**
3839
- Solves for weights matching multiple constraints simultaneously
39-
- Can enforce age distribution + Social Security benefits + taxable payroll
40+
- Can enforce age distribution + Social Security benefits + taxable payroll + TOB revenue
4041
- One-shot solution using `samplics` package
4142
- **Recommended** for accurate long-term projections
4243

@@ -57,17 +58,29 @@ python run_household_projection.py 2100 --greg --use-ss
5758
- Calculated as: `taxable_earnings_for_social_security` + `social_security_taxable_self_employment_income`
5859
- Source: SSA Trustee Report 2024 (`social_security_aux.csv`)
5960

61+
4. **TOB Revenue** (`--use-tob`, GREG only)
62+
- Taxation of Benefits revenue for OASDI and Medicare HI trust funds
63+
- OASDI: `tob_revenue_oasdi` (tier 1 taxation, 0-50% of benefits)
64+
- HI: `tob_revenue_medicare_hi` (tier 2 taxation, 50-85% of benefits)
65+
- Source: SSA Trustee Report 2024 (`social_security_aux.csv`)
66+
6067
---
6168

6269
### Data Sources
6370

64-
All data from **SSA 2024 Trustee Report**:
71+
**SSA 2025 OASDI Trustees Report**
72+
- URL: https://www.ssa.gov/OACT/TR/2025/
73+
- File: `SingleYearTRTables_TR2025.xlsx`
74+
- Tables: IV.B2 (OASDI TOB % of taxable payroll), VI.G6 (taxable payroll in billions), VI.G9 (OASDI costs)
6575

66-
- `SSPopJul_TR2024.csv` - Population projections 2025-2100 by single year of age
67-
- `social_security_aux.csv` - OASDI costs and taxable payroll projections 2025-2100
68-
- Extracted from `SingleYearTRTables_TR2025.xlsx` Table VI.G9 using `extract_ssa_costs.py`
76+
**CMS 2025 Medicare Trustees Report**
77+
- URL: https://www.cms.gov/data-research/statistics-trends-and-reports/trustees-report-trust-funds
78+
- File: `tr2025-tables-figures.zip` → CSV folder → "Medicare Sources of Non-Interest Income..."
79+
- Column: Tax on Benefits (values in millions, 2024-2099)
6980

70-
Files located in: `policyengine_us_data/storage/`
81+
**Local files** (in `policyengine_us_data/storage/`):
82+
- `SSPopJul_TR2024.csv` - Population projections 2025-2100 by single year of age
83+
- `social_security_aux.csv` - OASDI costs, taxable payroll, and TOB revenue projections 2025-2100
7184

7285
---
7386

policyengine_us_data/datasets/cps/long_term/calibration.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,10 @@ def calibrate_greg(
8585
payroll_target=None,
8686
h6_income_values=None,
8787
h6_revenue_target=None,
88+
oasdi_tob_values=None,
89+
oasdi_tob_target=None,
90+
hi_tob_values=None,
91+
hi_tob_target=None,
8892
n_ages=86,
8993
):
9094
"""
@@ -101,6 +105,10 @@ def calibrate_greg(
101105
payroll_target: Optional taxable payroll target total
102106
h6_income_values: Optional H6 reform income values per household
103107
h6_revenue_target: Optional H6 reform total revenue impact target
108+
oasdi_tob_values: Optional OASDI TOB revenue values per household
109+
oasdi_tob_target: Optional OASDI TOB revenue target total
110+
hi_tob_values: Optional HI TOB revenue values per household
111+
hi_tob_target: Optional HI TOB revenue target total
104112
n_ages: Number of age groups
105113
106114
Returns:
@@ -116,6 +124,8 @@ def calibrate_greg(
116124
(ss_values is not None and ss_target is not None)
117125
or (payroll_values is not None and payroll_target is not None)
118126
or (h6_income_values is not None and h6_revenue_target is not None)
127+
or (oasdi_tob_values is not None and oasdi_tob_target is not None)
128+
or (hi_tob_values is not None and hi_tob_target is not None)
119129
)
120130

121131
if needs_aux_df:
@@ -135,6 +145,14 @@ def calibrate_greg(
135145
aux_df["h6_revenue"] = h6_income_values
136146
controls["h6_revenue"] = h6_revenue_target
137147

148+
if oasdi_tob_values is not None and oasdi_tob_target is not None:
149+
aux_df["oasdi_tob"] = oasdi_tob_values
150+
controls["oasdi_tob"] = oasdi_tob_target
151+
152+
if hi_tob_values is not None and hi_tob_target is not None:
153+
aux_df["hi_tob"] = hi_tob_values
154+
controls["hi_tob"] = hi_tob_target
155+
138156
aux_vars = aux_df
139157
else:
140158
aux_vars = X
@@ -160,6 +178,10 @@ def calibrate_weights(
160178
payroll_target=None,
161179
h6_income_values=None,
162180
h6_revenue_target=None,
181+
oasdi_tob_values=None,
182+
oasdi_tob_target=None,
183+
hi_tob_values=None,
184+
hi_tob_target=None,
163185
n_ages=86,
164186
max_iters=100,
165187
tol=1e-6,
@@ -180,6 +202,10 @@ def calibrate_weights(
180202
payroll_target: Optional payroll target (for GREG with payroll)
181203
h6_income_values: Optional H6 reform income values per household
182204
h6_revenue_target: Optional H6 reform total revenue impact target
205+
oasdi_tob_values: Optional OASDI TOB revenue values per household
206+
oasdi_tob_target: Optional OASDI TOB revenue target total
207+
hi_tob_values: Optional HI TOB revenue values per household
208+
hi_tob_target: Optional HI TOB revenue target total
183209
n_ages: Number of age groups
184210
max_iters: Max iterations for IPF
185211
tol: Convergence tolerance for IPF
@@ -204,6 +230,10 @@ def calibrate_weights(
204230
payroll_target,
205231
h6_income_values,
206232
h6_revenue_target,
233+
oasdi_tob_values,
234+
oasdi_tob_target,
235+
hi_tob_values,
236+
hi_tob_target,
207237
n_ages,
208238
)
209239
except Exception as e:

0 commit comments

Comments
 (0)