"""
Data targets for the OG-ZAF steady-state dashboard, and the function that
computes the matching model moments from a solved ``SS_vars.pkl``.
Every target carries its source. Ratios are to GDP (Stats SA, rebased 2021
vintage) unless stated. Fiscal rows first, then external, production and
households.
"""
import numpy as np
# moment: (target, source)
TARGETS = {
# Fiscal: revenue by instrument (FY2025/26 outcome, GDP R7,787bn)
"PIT/Y": (0.1015, "PIT R790.6bn, MTBPS 2026 Table 3.1"),
"CIT/Y": (
0.0502,
"CIT R345.4bn + dividends tax R45.9bn, MTBPS 2026 Table 3.1",
),
"payroll/Y": (
0.0067,
"Skills Development Levy R26.0bn (MTBPS 2026) + UIF "
"contributions R26.7bn (UIF Annual Report 2025/26)",
),
"cons tax/Y": (
0.0892,
"VAT + fuel levy + excise + customs + other indirect taxes "
"(9.87%) less SACU payments (0.95%), MTBPS 2026 / BR 2026",
),
"wealth tax/Y": (
0.0161,
"municipal property rates R101.5bn (Stats SA P9114, 2024/25) "
"+ transfer duty and securities transfer tax (BR 2026)",
),
"bequest tax/Y": (
0.0008,
"estate duty R4.8bn + donations tax R1.2bn, BR 2026 annexure",
),
"r_gov": (
0.037,
"debt service / average gross loan debt 7.08% nominal (MTBPS "
"2026) less ~3.4% expected inflation",
),
"D/Y": (0.765, "debt_ratio_ss: MTBPS 2026 gross loan debt 2029/30"),
"D_f/D": (0.25, "non-resident share of domestic bonds, BR 2026"),
# Spending
"TR/Y": (0.036, "social grants incl. SRD, BR 2026 Table 5.7"),
"I_g/Y": (0.0242, "general government GFCF 2025, SARB QB S-127"),
# External
"K_f/K": (
0.205,
"upper bound: inward DI equity 22.7% + portfolio equity 34.8% "
"of GDP (SARB IIP end-2025) / private K/Y; DI equity alone 0.08",
),
# Production
"K/Y": (
2.80,
"private capital: PWT 11.0 cn/cgdpo 3.82 (2023) less the "
"general government share 26.7% (SARB QB E-5)",
),
"r": (0.12, "PWT 11.0 real internal rate of return, 2023"),
}
[docs]
def ss_moments(ss):
"""
Model side of every dashboard row from a solved steady state.
Args:
ss (dict): a solved ``SS_vars.pkl``
Returns:
m (dict): model moments, keyed like ``TARGETS``, plus untargeted
ratios
"""
Y = float(np.squeeze(ss["Y"]))
def sc(key):
return float(np.sum(ss[key]))
C = sc("C")
nx = Y - C - sc("I_total") - sc("I_g") - sc("G")
m = {
"PIT/Y": sc("iit_revenue") / Y,
"payroll/Y": sc("payroll_tax_revenue") / Y,
"CIT/Y": sc("business_tax_revenue") / Y,
"cons tax/Y": sc("cons_tax_revenue") / Y,
"wealth tax/Y": sc("wealth_tax_revenue") / Y,
"bequest tax/Y": sc("bequest_tax_revenue") / Y,
"total tax/Y": sc("total_tax_revenue") / Y,
"r_gov": sc("r_gov"),
"D/Y": sc("D") / Y,
"D_f/D": sc("D_f") / sc("D") if sc("D") else np.nan,
"G/Y": sc("G") / Y,
"TR/Y": sc("TR") / Y,
"I_g/Y": sc("I_g") / Y,
"pensions/Y": sc("agg_pension_outlays") / Y,
"K_f/K": sc("K_f") / sc("K"),
"NX/Y": nx / Y,
"K/Y": sc("K") / Y,
"(I+I_g)/Y": (sc("I_total") + sc("I_g")) / Y,
"C/Y purch.": (C + sc("cons_tax_revenue")) / Y,
"B/Y": sc("B") / Y,
"BQ/Y": sc("BQ") / Y,
"r": sc("r"),
"factor": sc("factor"),
}
return m