Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions changelog_entry.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
- bump: minor
changes:
added:
- Added --use-tob flag for TOB (Taxation of Benefits) revenue calibration targeting OASDI and HI trust fund revenue
29 changes: 21 additions & 8 deletions policyengine_us_data/datasets/cps/long_term/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
Run projections using `run_household_projection.py`:

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

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

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

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

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

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

---

### Data Sources

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

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

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

---

Expand Down
30 changes: 30 additions & 0 deletions policyengine_us_data/datasets/cps/long_term/calibration.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,10 @@ def calibrate_greg(
payroll_target=None,
h6_income_values=None,
h6_revenue_target=None,
oasdi_tob_values=None,
oasdi_tob_target=None,
hi_tob_values=None,
hi_tob_target=None,
n_ages=86,
):
"""
Expand All @@ -101,6 +105,10 @@ def calibrate_greg(
payroll_target: Optional taxable payroll target total
h6_income_values: Optional H6 reform income values per household
h6_revenue_target: Optional H6 reform total revenue impact target
oasdi_tob_values: Optional OASDI TOB revenue values per household
oasdi_tob_target: Optional OASDI TOB revenue target total
hi_tob_values: Optional HI TOB revenue values per household
hi_tob_target: Optional HI TOB revenue target total
n_ages: Number of age groups

Returns:
Expand All @@ -116,6 +124,8 @@ def calibrate_greg(
(ss_values is not None and ss_target is not None)
or (payroll_values is not None and payroll_target is not None)
or (h6_income_values is not None and h6_revenue_target is not None)
or (oasdi_tob_values is not None and oasdi_tob_target is not None)
or (hi_tob_values is not None and hi_tob_target is not None)
)

if needs_aux_df:
Expand All @@ -135,6 +145,14 @@ def calibrate_greg(
aux_df["h6_revenue"] = h6_income_values
controls["h6_revenue"] = h6_revenue_target

if oasdi_tob_values is not None and oasdi_tob_target is not None:
aux_df["oasdi_tob"] = oasdi_tob_values
controls["oasdi_tob"] = oasdi_tob_target

if hi_tob_values is not None and hi_tob_target is not None:
aux_df["hi_tob"] = hi_tob_values
controls["hi_tob"] = hi_tob_target

aux_vars = aux_df
else:
aux_vars = X
Expand All @@ -160,6 +178,10 @@ def calibrate_weights(
payroll_target=None,
h6_income_values=None,
h6_revenue_target=None,
oasdi_tob_values=None,
oasdi_tob_target=None,
hi_tob_values=None,
hi_tob_target=None,
n_ages=86,
max_iters=100,
tol=1e-6,
Expand All @@ -180,6 +202,10 @@ def calibrate_weights(
payroll_target: Optional payroll target (for GREG with payroll)
h6_income_values: Optional H6 reform income values per household
h6_revenue_target: Optional H6 reform total revenue impact target
oasdi_tob_values: Optional OASDI TOB revenue values per household
oasdi_tob_target: Optional OASDI TOB revenue target total
hi_tob_values: Optional HI TOB revenue values per household
hi_tob_target: Optional HI TOB revenue target total
n_ages: Number of age groups
max_iters: Max iterations for IPF
tol: Convergence tolerance for IPF
Expand All @@ -204,6 +230,10 @@ def calibrate_weights(
payroll_target,
h6_income_values,
h6_revenue_target,
oasdi_tob_values,
oasdi_tob_target,
hi_tob_values,
hi_tob_target,
n_ages,
)
except Exception as e:
Expand Down
Loading