|
| 1 | +""" |
| 2 | +Sanity-check that weighted PUF (filer) totals on the TMD file match CBO's |
| 3 | +individual income tax microsimulation projections at four anchor years. |
| 4 | +
|
| 5 | +Tested aggregates: number of returns, AGI (`c00100`), and individual |
| 6 | +income tax liability (`iitax`). |
| 7 | +
|
| 8 | +Comparator is the calendar-year 1040-universe liability series in |
| 9 | +CBO's February 2026 Revenue file, sheet 3 — built from the same SOI 2022 |
| 10 | +PUF the TMD file uses. See `tests/expected_cbo_levels_2022_data.yaml` |
| 11 | +for the exact CBO line items and the data source. |
| 12 | +
|
| 13 | +Tolerances widen with the projection horizon to reflect compounding |
| 14 | +growfactor uncertainty. |
| 15 | +""" |
| 16 | + |
| 17 | +import yaml |
| 18 | +import pytest |
| 19 | +import taxcalc |
| 20 | + |
| 21 | +from tmd.imputation_assumptions import TAXYEAR, CREDIT_CLAIMING |
| 22 | + |
| 23 | +# Per-year relative tolerance for all three aggregates. |
| 24 | +RELTOL = { |
| 25 | + 2022: 0.01, |
| 26 | + 2026: 0.02, |
| 27 | + 2031: 0.05, |
| 28 | + 2036: 0.06, |
| 29 | +} |
| 30 | + |
| 31 | +VARIABLES = ("n_returns_mil", "agi_bil", "iitax_bil") |
| 32 | + |
| 33 | +DUMP = False # if True, always fail with full output table |
| 34 | + |
| 35 | + |
| 36 | +@pytest.mark.skipif( |
| 37 | + TAXYEAR != 2022, |
| 38 | + reason="expected values are calibrated to TAXYEAR=2022", |
| 39 | +) |
| 40 | +def test_revenue_levels_cbo( |
| 41 | + tests_folder, tmd_variables, tmd_weights_path, tmd_growfactors_path |
| 42 | +): |
| 43 | + epath = tests_folder / "expected_cbo_levels_2022_data.yaml" |
| 44 | + with open(epath, "r", encoding="utf-8") as f: |
| 45 | + exp = yaml.safe_load(f) |
| 46 | + |
| 47 | + pol = taxcalc.Policy() |
| 48 | + pol.implement_reform(CREDIT_CLAIMING) |
| 49 | + rec = taxcalc.Records( |
| 50 | + data=tmd_variables, |
| 51 | + start_year=TAXYEAR, |
| 52 | + gfactors=taxcalc.GrowFactors( |
| 53 | + growfactors_filename=str(tmd_growfactors_path) |
| 54 | + ), |
| 55 | + weights=str(tmd_weights_path), |
| 56 | + adjust_ratios=None, |
| 57 | + exact_calculations=True, |
| 58 | + weights_scale=1.0, |
| 59 | + ) |
| 60 | + sim = taxcalc.Calculator(policy=pol, records=rec) |
| 61 | + |
| 62 | + actual = {} |
| 63 | + for year in sorted(RELTOL): |
| 64 | + sim.advance_to_year(year) |
| 65 | + sim.calc_all() |
| 66 | + wt = sim.array("s006") |
| 67 | + puf = sim.array("data_source") == 1 |
| 68 | + wpuf = wt * puf |
| 69 | + actual[year] = { |
| 70 | + "n_returns_mil": wpuf.sum() * 1e-6, |
| 71 | + "agi_bil": (wpuf * sim.array("c00100")).sum() * 1e-9, |
| 72 | + "iitax_bil": (wpuf * sim.array("iitax")).sum() * 1e-9, |
| 73 | + } |
| 74 | + |
| 75 | + emsg = "" |
| 76 | + for year, tol in RELTOL.items(): |
| 77 | + for var in VARIABLES: |
| 78 | + act = actual[year][var] |
| 79 | + ex = exp[year][var] |
| 80 | + reldiff = act / ex - 1.0 |
| 81 | + if abs(reldiff) >= tol or DUMP: |
| 82 | + emsg += ( |
| 83 | + f"\n{var:<14s} year={year} " |
| 84 | + f"act={act:10.3f} exp={ex:10.3f} " |
| 85 | + f"reldiff={reldiff:+7.4f} tol={tol:.3f}" |
| 86 | + ) |
| 87 | + |
| 88 | + if DUMP: |
| 89 | + assert False, f"test_revenue_levels_cbo DUMP output: {emsg}" |
| 90 | + if emsg: |
| 91 | + raise ValueError(emsg) |
0 commit comments