From 3e0890eaa4b15d71cfd7e3bdca77a4571473b5e4 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Fri, 30 Jan 2026 19:14:46 -0500 Subject: [PATCH] Fix federal income tax calibration to use income_tax_positive Changes CBO income tax calibration target from `income_tax` to `income_tax_positive` to match CBO's receipts definition. CBO reports income tax receipts where refundable credit payments in excess of tax liability are classified as outlays (spending), not negative receipts. The `income_tax_positive` variable (added in policyengine-us) floors income_tax at zero to match this definition. Reference: https://www.cbo.gov/publication/43767 Closes #494 Co-Authored-By: Claude Opus 4.5 --- changelog_entry.yaml | 1 + .../db/etl_national_targets.py | 26 ++++++++++++++----- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/changelog_entry.yaml b/changelog_entry.yaml index 00b36325..22c023c8 100644 --- a/changelog_entry.yaml +++ b/changelog_entry.yaml @@ -26,3 +26,4 @@ - Added DATABASE_GUIDE.md with comprehensive calibration database documentation fixed: - Fixed cross-state recalculation in sparse matrix builder by adding time_period to calculate() calls + - Fixed federal income tax calibration to use income_tax_positive (matches CBO receipts definition) diff --git a/policyengine_us_data/db/etl_national_targets.py b/policyengine_us_data/db/etl_national_targets.py index 5cb910d5..e560b0a3 100644 --- a/policyengine_us_data/db/etl_national_targets.py +++ b/policyengine_us_data/db/etl_national_targets.py @@ -260,19 +260,29 @@ def extract_national_targets(): # CBO projection targets - get for a specific year CBO_YEAR = 2023 # Year the CBO projections are for cbo_vars = [ - "income_tax", + # Note: income_tax_positive matches CBO's receipts definition where + # refundable credit payments in excess of liability are classified + # as outlays, not negative receipts. See: + # https://www.cbo.gov/publication/43767 + "income_tax_positive", "snap", "social_security", "ssi", "unemployment_compensation", ] + # Mapping from target variable to CBO parameter name (when different) + cbo_param_name_map = { + "income_tax_positive": "income_tax", # CBO param is income_tax + } + cbo_targets = [] for variable_name in cbo_vars: + param_name = cbo_param_name_map.get(variable_name, variable_name) try: value = sim.tax_benefit_system.parameters( CBO_YEAR - ).calibration.gov.cbo._children[variable_name] + ).calibration.gov.cbo._children[param_name] cbo_targets.append( { "variable": variable_name, @@ -284,7 +294,7 @@ def extract_national_targets(): ) except (KeyError, AttributeError) as e: print( - f"Warning: Could not extract CBO parameter for {variable_name}: {e}" + f"Warning: Could not extract CBO parameter for {variable_name} (param: {param_name}): {e}" ) # Treasury/JCT targets (EITC) - get for a specific year @@ -334,12 +344,16 @@ def transform_national_targets(raw_targets): """ # Process direct sum targets (non-tax items and some CBO items) - # Note: income_tax from CBO and eitc from Treasury need filer constraint + # Note: income_tax_positive from CBO and eitc from Treasury need filer constraint cbo_non_tax = [ - t for t in raw_targets["cbo_targets"] if t["variable"] != "income_tax" + t + for t in raw_targets["cbo_targets"] + if t["variable"] != "income_tax_positive" ] cbo_tax = [ - t for t in raw_targets["cbo_targets"] if t["variable"] == "income_tax" + t + for t in raw_targets["cbo_targets"] + if t["variable"] == "income_tax_positive" ] all_direct_targets = raw_targets["direct_sum_targets"] + cbo_non_tax