# SPDX-License-Identifier: MIT """Monta serie.json e fontes/serie-calculada.csv a partir dos arquivos em fontes/, e injeta os dados no index.html. python3 calcular.py (requer openpyxl e xlrd) Déficit da previdência por ano (R$ bi), 2008–2025: INSS (RGPS) = benefícios previdenciários (RTN 2.2, 4.1) − arrecadação líquida do RGPS (RTN 2.2, 1.3) servidores e pensões = aposentadorias e pensões civis (RTN 2.5, 2.2.3) militares + a parte de pensão das "reformas e pensões militares" (RTN 2.5, 2.2.4) − contribuição do servidor civil ao PSS (RTN 2.2, 1.4.3) − contribuição de militares e pensionistas para a pensão militar (RREO) Os militares inativos (reserva remunerada e reforma) ficam fora da conta: pela Lei 6.880/1980 (art. 3º e art. 53-A) eles continuam militares e são pagos pelo Tesouro como pessoal, sem contribuição. A linha 2.2.4 do RTN soma reforma e pensão; a divisão de cada ano usa a proporção do RREO de dezembro (pensões ÷ pensões + inatividade), aplicada ao valor pago do RTN. O gasto com os inativos é calculado e mostrado à parte. Correção: cada déficit é corrigido meio ano no próprio ano e o ano inteiro nos seguintes, até dez/2025, por três taxas — a taxa de juros implícita da dívida bruta do governo geral (o que a dívida de fato pagou no ano, segundo o Banco Central), o IPCA (reais de hoje) e a Selic (para comparação). """ import csv, json, re from pathlib import Path import openpyxl import xlrd D = Path(__file__).resolve().parent F = D / "fontes" # ---------------------------------------------------------------- RTN wb = openpyxl.load_workbook(F / "RTN-dez2025-serie-historica.xlsx", read_only=True, data_only=True) def serie(tabela, prefixo): linhas = list(wb[tabela].iter_rows(values_only=True)) cab = next(r for r in linhas if r and sum(1 for c in r if str(c).strip().isdigit() and len(str(c).strip()) == 4) > 5) anos = {i: int(str(c).strip()) for i, c in enumerate(cab) if str(c).strip().isdigit() and len(str(c).strip()) == 4} for r in linhas: rot = " ".join(str(c) for c in r[:2] if c is not None) if rot.strip().startswith(prefixo): return {anos[i]: float(r[i]) / 1e3 for i in anos if isinstance(r[i], (int, float))} raise KeyError(prefixo) arr, ben = serie("2.2", "1.3 "), serie("2.2", "4.1 ") pss, prim = serie("2.2", "1.4.3"), serie("2.2", "5. RESULTADO") civ, mil = serie("2.5", "2.2.3"), serie("2.5", "2.2.4") # ---------------------------------------------------------------- RREO: militares # (contribuição para a pensão militar, despesa com pensões, despesa com reforma/inatividade), R$ bi. # # 2008–2013: RREO de dezembro em PDF, Anexo V ("Demonstrativo das receitas e despesas # previdenciárias do regime próprio dos servidores públicos"), R$ milhares, despesas liquidadas. # Transcritos aqui porque a camada de texto de parte dessas páginas vem embaralhada; a página # de cada um está indicada para conferir. Inatividade = "Pessoal Militar" − "Pensões". PDF = { 2008: (1_512_857, 8_562_625, 20_003_126 - 8_562_625), # RREO-Uniao-dez2009.pdf, p. 42, coluna 2008 2009: (1_681_261, 9_011_166, 20_637_925 - 9_011_166), # RREO-Uniao-dez2009.pdf, p. 42, coluna 2009 2010: (1_869_021, 9_206_518, 21_388_224 - 9_206_518), # RREO-Uniao-dez2011.pdf, p. 43, coluna 2010 2011: (2_025_441, 9_535_143, 21_953_405 - 9_535_143), # RREO-Uniao-dez2011.pdf, p. 43, coluna 2011 2012: (2_001_211, 9_254_043, 23_321_609 - 9_254_043), # RREO-Uniao-dez2013.pdf, p. 37, coluna 2012 2013: (2_170_714, 9_971_111, 24_953_267 - 9_971_111), # RREO-Uniao-dez2013.pdf, p. 37, coluna 2013 } militares = {a: tuple(v / 1e6 for v in t) for a, t in PDF.items()} def siconfi(arquivo): return json.load(open(F / arquivo)) def valor(linhas, cod_prefixo, ano=None, coluna_prefixo=None): """O valor da linha cujo cod_conta começa com cod_prefixo, na coluna do ano (ou na única coluna com aquele prefixo, quando o relatório não põe o ano no nome da coluna).""" achados = set() for x in linhas: if not x.get("cod_conta", "").startswith(cod_prefixo): continue col = re.sub(r"\s+", " ", x["coluna"].replace("/", " / ")).strip() if coluna_prefixo and not col.upper().startswith(coluna_prefixo): continue if ano is not None and not col.endswith(f"/ {ano}"): continue achados.add(round(float(x["valor"]) / 1e9, 6)) if len(achados) != 1: raise ValueError(f"{cod_prefixo} {ano} {coluna_prefixo}: {achados}") return achados.pop() # 2014–2016: Siconfi, RREO de dezembro, formato antigo do Anexo 4 (mesmo critério dos PDFs). for arquivo, anos in (("Siconfi-RREO-Uniao-2015-6bim-Anexo04-RPPS.json", (2014, 2015)), ("Siconfi-RREO-Uniao-2016-6bim-Anexo04.2.json", (2016,))): L = siconfi(arquivo) for a in anos: c = valor(L, "ReceitaDeContribuicoesDosSeguradosPessoalMilitarBruta", a) tot = valor(L, "DespesasPrevidenciariasExcetoIntraOrcamentariasPessoalMilitar", a) p = valor(L, "DespesasExcetoIntraOrcamentariasPessoalMilitarPensoes", a) militares[a] = (c, p, tot - p) # 2017–2025: Siconfi, formato novo do Anexo 4 (pensões militares e militares inativos em quadros # separados). 2017 vem da coluna do ano anterior do RREO de 2018, já no critério novo. NOVO = {2017: ("Siconfi-RREO-Uniao-2018-6bim-Anexo04.2.json", 2017)} for a in range(2018, 2026): NOVO[a] = (f"Siconfi-RREO-Uniao-{a}-6bim-Anexo04.{2 if a <= 2022 else 3}.json", a) for a, (arquivo, col_ano) in NOVO.items(): L = siconfi(arquivo) tem_ano = any(x["coluna"].strip().endswith(f"/ {col_ano}") for x in L) k = col_ano if tem_ano else None c = valor(L, "TotalReceitasRPPSFinanceiro", k, coluna_prefixo=None if tem_ano else "RECEITAS REALIZADAS") p = valor(L, "TotalDasDespesasRPPSFinanceiro", k, coluna_prefixo="DESPESAS LIQUIDADAS") i = valor(L, "DespesasComInativosMilitares", k, coluna_prefixo="DESPESAS LIQUIDADAS") militares[a] = (c, p, i) # ---------------------------------------------------------------- Banco Central def sgs(arquivo): return json.load(open(F / arquivo)) def acumulado_anual(arquivo): fator = {} for d in sgs(arquivo): a = int(d["data"][-4:]); fator[a] = fator.get(a, 1.0) * (1 + float(d["valor"]) / 100) return fator selic = acumulado_anual("BCB-SGS-4390-selic-mensal-2008-2025.json") ipca = acumulado_anual("BCB-SGS-433-IPCA-mensal-2008-2025.json") dbgg = {d["data"]: float(d["valor"]) for d in sgs("BCB-SGS-13761-DBGG-2007-2025.json")} pct = {d["data"]: float(d["valor"]) for d in sgs("BCB-SGS-13762-DBGG-pct-PIB-2007-2026.json")} # Taxa de juros implícita da DBGG (% a.a., janeiro a dezembro), do Banco Central. impl = {} sh = xlrd.open_workbook(F / "BCB-taxa-implicita-DLSP-DBGG-serie-ate-nov2018.xls").sheet_by_name("DBGG_12meses") anos, meses = sh.row_values(4), sh.row_values(6) total = next(sh.row_values(i) for i in range(sh.nrows) if str(sh.row_values(i)[0]).strip().startswith("Dívida bruta total")) ano = None for j in range(1, sh.ncols): if str(anos[j]).strip(): ano = int(float(anos[j])) if str(meses[j]).strip() == "Dezembro" and ano <= 2011: # 12 meses até dezembro = o ano impl[ano] = float(total[j]) for arquivo in ("BCB-estatisticas-fiscais-tabelas-2019-01.xlsx", "BCB-estatisticas-fiscais-tabelas-2026-01.xlsx"): ws = openpyxl.load_workbook(F / arquivo, read_only=True, data_only=True)["Tabela 23"] linhas = [r for r in ws.iter_rows(values_only=True)] cab = next(r for r in linhas if r and str(r[0]).strip() == "Discriminação") tot = next(r for r in linhas if r and str(r[0]).strip().startswith("Dívida bruta total")) for c, v in zip(cab[1:], tot[1:]): if c is not None and str(c).strip().isdigit(): impl[int(str(c).strip())] = float(v) implicita = {a: 1 + impl[a] / 100 for a in range(2008, 2026)} # ---------------------------------------------------------------- conta def para_dez2025(a): """Fator que leva um fluxo do ano a para reais de dez/2025: meio ano no próprio ano, ano inteiro nos seguintes.""" f = ipca[a] ** 0.5 for z in range(a + 1, 2026): f *= ipca[z] return f def r1(v): return round(v, 1) linhas = [dict(ano=2007, inss=None, servidores_pensoes=None, total=None, inss_real=None, servidores_pensoes_real=None, total_real=None, pensao_militar=None, contrib_pensao_militar=None, inativos_militares=None, inativos_militares_real=None, primario=None, primario_sem_previdencia=None, ipca_pct=None, implicita_pct=None, selic_pct=None, acumulado_nominal=0.0, acumulado_ipca=0.0, acumulado_implicita=0.0, acumulado_selic=0.0, inativos_acumulado_ipca=0.0, dbgg=r1(dbgg["01/12/2007"] / 1e3), dbgg_pct_pib=pct["01/12/2007"])] cn = ci = ct = cs = inat_i = 0.0 for a in range(2008, 2026): contrib, pens, inat = militares[a] parte_pensao = pens / (pens + inat) pensao_mil = mil[a] * parte_pensao inativos = mil[a] - pensao_mil rg = ben[a] - arr[a] sp = civ[a] + pensao_mil - pss[a] - contrib t = rg + sp cn += t ci = ci * ipca[a] + t * ipca[a] ** 0.5 ct = ct * implicita[a] + t * implicita[a] ** 0.5 cs = cs * selic[a] + t * selic[a] ** 0.5 inat_i = inat_i * ipca[a] + inativos * ipca[a] ** 0.5 fr = para_dez2025(a) linhas.append(dict( ano=a, inss=r1(rg), servidores_pensoes=r1(sp), total=r1(t), inss_real=r1(rg * fr), servidores_pensoes_real=r1(sp * fr), total_real=r1(t * fr), pensao_militar=r1(pensao_mil), contrib_pensao_militar=round(contrib, 2), inativos_militares=r1(inativos), inativos_militares_real=r1(inativos * fr), primario=r1(prim[a]), primario_sem_previdencia=r1(prim[a] + t), ipca_pct=round((ipca[a] - 1) * 100, 2), implicita_pct=round(impl[a], 2), selic_pct=round((selic[a] - 1) * 100, 2), acumulado_nominal=r1(cn), acumulado_ipca=r1(ci), acumulado_implicita=r1(ct), acumulado_selic=r1(cs), inativos_acumulado_ipca=r1(inat_i), dbgg=r1(dbgg[f"01/12/{a}"] / 1e3), dbgg_pct_pib=pct[f"01/12/{a}"])) # ---------------------------------------------------------------- saída with open(F / "serie-calculada.csv", "w", newline="") as f: w = csv.DictWriter(f, fieldnames=list(linhas[0])); w.writeheader(); w.writerows(linhas) json.dump(linhas, open(D / "serie.json", "w"), ensure_ascii=False) html = D / "index.html" pagina = html.read_text() pagina, n = re.subn(r'()', lambda m: m.group(1) + json.dumps(linhas, ensure_ascii=False) + m.group(2), pagina, flags=re.S) assert n == 1, "marcador dos dados não encontrado em index.html" html.write_text(pagina) u, div = linhas[-1], linhas[-1]["dbgg"] print("2025:", u) print(f"acumulado 2008–2025: nominal {u['acumulado_nominal']:.0f} bi ({u['acumulado_nominal']/div*100:.1f}%), " f"IPCA {u['acumulado_ipca']:.0f} ({u['acumulado_ipca']/div*100:.1f}%), " f"taxa implícita {u['acumulado_implicita']:.0f} ({u['acumulado_implicita']/div*100:.1f}%), " f"Selic {u['acumulado_selic']:.0f} ({u['acumulado_selic']/div*100:.1f}%) — dívida {div:.0f}") print("primário acumulado", r1(sum(prim[a] for a in range(2008, 2026))), "sem previdência", r1(sum(x["primario_sem_previdencia"] for x in linhas[1:])), "anos positivos", sum(1 for x in linhas[1:] if x["primario_sem_previdencia"] > 0)) print("inativos militares 2008–2025:", r1(sum(x["inativos_militares"] for x in linhas[1:])), "bi nominais;", u["inativos_acumulado_ipca"], "bi em reais de dez/2025; 2025:", u["inativos_militares"], "bi") print("militares por ano (contrib, pensões, inatividade):", {a: tuple(round(v, 2) for v in m) for a, m in sorted(militares.items())})