From 4160653fe64fe29093cf56b9c4d9a64b278f77c2 Mon Sep 17 00:00:00 2001 From: arihantlodha-cmd Date: Thu, 13 Aug 2026 18:05:57 +0900 Subject: [PATCH] Fix payroll tax revenue double-counted in aggregates.revenue Fixes #1199. Each household's income_payroll_tax_liab already includes T_P = tau_payroll * labor_income, so payroll revenue is inside iit_payroll_tax_revenue. PR #1184 then added get_payroll_tax_revenue (the same amount re-derived from the aggregate wage bill) into that total again when tau_payroll != 0, overstating revenue so the government over-spent and the steady-state resource constraint failed for any model with a nonzero tau_payroll (OG-USA was fine only because its tau_payroll is 0). Removes the erroneous addition. get_payroll_tax_revenue still provides the income-vs-payroll reporting split (iit_revenue = iit_payroll_tax_revenue - payroll_tax_revenue), which is unchanged. Restores the test_revenue golden values to their pre-#1184 amounts (verified identical to the values from before the double-count was introduced). --- CHANGELOG.md | 16 ++++ ogcore/aggregates.py | 12 +-- tests/test_aggregates.py | 184 +++++++++++++++++++-------------------- 3 files changed, 114 insertions(+), 98 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3e7eb0a39..b82b8e42d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,22 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] + +### Bug Fixes + +- Fixes Issue [#1199](https://github.com/PSLmodels/OG-Core/issues/1199): + `aggregates.revenue` counted payroll tax revenue twice. Each household's + `income_payroll_tax_liab` already includes `T_P = tau_payroll * + labor_income`, so payroll revenue is inside `iit_payroll_tax_revenue`; + PR #1184 then added `get_payroll_tax_revenue` (the same take, re-derived + from the aggregate wage bill) into that total again when `tau_payroll` + was nonzero. Revenue was overstated, the government over-spent, and any + model with a nonzero `tau_payroll` failed the steady-state resource + constraint (OG-USA was unaffected only because its `tau_payroll` is 0). + The erroneous addition is removed; `get_payroll_tax_revenue` still + provides the income vs payroll reporting split, which is unchanged. + ## [0.19.1] - 2026-08-10 12:00:00 ### Added diff --git a/ogcore/aggregates.py b/ogcore/aggregates.py index 9626ae926..b7f015726 100644 --- a/ogcore/aggregates.py +++ b/ogcore/aggregates.py @@ -415,15 +415,15 @@ def revenue( cons_tax_revenue = ( (tax.cons_tax_liab(c, p_i, p, method) * pop_weights).sum(1).sum(1) ) + # Payroll tax revenue is already inside iit_payroll_tax_revenue: each + # household's income_payroll_tax_liab includes T_P = tau_payroll * + # labor_income (see tax.income_tax_liab). get_payroll_tax_revenue just + # re-derives that same amount from the aggregate wage bill so the + # income vs payroll split can be reported; it must NOT be added into + # the total again (that double-counted payroll revenue -- Issue #1199). payroll_tax_revenue = get_payroll_tax_revenue( w, L, iit_payroll_tax_revenue, p, method ) - # When payroll taxes are modeled explicitly via tau_payroll, they are - # excluded from the income and payroll tax functions, so payroll tax - # revenue must be added into iit_payroll_tax_revenue (which enters - # total revenue) before the income tax portion is separated out. - if np.any(p.tau_payroll != 0): - iit_payroll_tax_revenue += payroll_tax_revenue business_tax_revenue = tax.get_biz_tax(w, Y, L, K, p_m, p, m, method).sum( -1 ) diff --git a/tests/test_aggregates.py b/tests/test_aggregates.py index 751b72cdb..82b5695ad 100644 --- a/tests/test_aggregates.py +++ b/tests/test_aggregates.py @@ -1474,111 +1474,111 @@ def test_get_C(c, p, method, expected): # vector of output prices p_m = np.ones((p.T, p.M)) -expected1 = 0.7598207140204407 +expected1 = 0.5688319028341413 expected2 = np.array( [ - 0.78077777, - 0.72068128, - 0.77564557, - 0.75220586, - 0.78498646, - 0.82226742, - 0.78953295, - 0.75499567, - 0.75017449, - 0.79555133, - 0.77677308, - 0.78748408, - 0.73859649, - 0.75895348, - 0.75059024, - 0.85599023, - 0.77630208, - 0.84016811, - 0.90505241, - 0.82363203, - 0.80717282, - 0.77141901, - 0.73973490, - 0.72241447, - 0.74309837, - 0.84927868, - 0.78312174, - 0.83015615, - 0.77981290, - 0.74258374, + 0.58978896, + 0.5318829, + 0.58291302, + 0.56616446, + 0.60152253, + 0.63685373, + 0.60718972, + 0.56236328, + 0.56929121, + 0.60536959, + 0.58763365, + 0.59627562, + 0.55409009, + 0.56782614, + 0.56400569, + 0.6636463, + 0.59160813, + 0.64735391, + 0.72066489, + 0.64096484, + 0.61899218, + 0.58806093, + 0.54783766, + 0.5393597, + 0.55685316, + 0.65395071, + 0.58946501, + 0.64129696, + 0.58759922, + 0.5580478, ] ) expected3 = ( np.array( [ - 0.78077777, - 0.72068128, - 0.77564557, - 0.75220586, - 0.78498646, - 0.82226742, - 0.78953295, - 0.75499567, - 0.75017449, - 0.79555133, - 0.77677308, - 0.78748408, - 0.73859649, - 0.75895348, - 0.75059024, - 0.85599023, - 0.77630208, - 0.84016811, - 0.90505241, - 0.82363203, - 0.80717282, - 0.77141901, - 0.73973490, - 0.72241447, - 0.74309837, - 0.84927868, - 0.78312174, - 0.83015615, - 0.77981290, - 0.74258374, + 0.58978896, + 0.5318829, + 0.58291302, + 0.56616446, + 0.60152253, + 0.63685373, + 0.60718972, + 0.56236328, + 0.56929121, + 0.60536959, + 0.58763365, + 0.59627562, + 0.55409009, + 0.56782614, + 0.56400569, + 0.6636463, + 0.59160813, + 0.64735391, + 0.72066489, + 0.64096484, + 0.61899218, + 0.58806093, + 0.54783766, + 0.5393597, + 0.55685316, + 0.65395071, + 0.58946501, + 0.64129696, + 0.58759922, + 0.5580478, ] ) - inv_tax_cred_rev3 ) -expected4 = 0.7598207140204407 +expected4 = 0.5688319028341413 expected5 = np.array( [ - 0.78077777, - 0.72068128, - 0.77564557, - 0.75220586, - 0.78498646, - 0.82226742, - 0.78953295, - 0.75499567, - 0.75017449, - 0.79555133, - 0.77677308, - 0.78748408, - 0.73859649, - 0.75895348, - 0.75059024, - 0.85599023, - 0.77630208, - 0.84016811, - 0.90505241, - 0.82363203, - 0.80717282, - 0.77141901, - 0.73973490, - 0.72241447, - 0.74309837, - 0.84927868, - 0.78312174, - 0.83015615, - 0.77981290, - 0.74258374, + 0.58978896, + 0.5318829, + 0.58291302, + 0.56616446, + 0.60152253, + 0.63685373, + 0.60718972, + 0.56236328, + 0.56929121, + 0.60536959, + 0.58763365, + 0.59627562, + 0.55409009, + 0.56782614, + 0.56400569, + 0.6636463, + 0.59160813, + 0.64735391, + 0.72066489, + 0.64096484, + 0.61899218, + 0.58806093, + 0.54783766, + 0.5393597, + 0.55685316, + 0.65395071, + 0.58946501, + 0.64129696, + 0.58759922, + 0.5580478, ] ) test_data = [