Pular para o conteúdo
domingo, 8 de novembro de 2026Última atualização: 12h57
Despesa Aberta

Os gastos de deputados e senadores federais, lidos por regras públicas e iguais para todos

Últimos

Metodologia

Como as despesas são coletadas, quais regras marcam um gasto como atípico e o código-fonte de cada uma. As mesmas regras, os mesmos limites e o mesmo texto valem para todos os parlamentares, partidos e estados.

De onde vêm os dados

Todos os dias o robô baixa os arquivos oficiais da Cota para o Exercício da Atividade Parlamentar da Câmara dos Deputadose do Senado Federal. Cada linha recebe uma impressão digital (hash), o que impede duplicar registros quando o mesmo arquivo é baixado de novo. Deputados são identificados pelo número de cadastro da Câmara; senadores, pelo nome, comparado à lista oficial do Senado sem acentos e sem diferenciar maiúsculas.

Despesas de lideranças partidárias e órgãos da Câmara (sem um deputado associado) não entram na análise. No Senado, alimentação vem somada a locomoção e hospedagem, por isso a regra de refeição só se aplica à Câmara. O partido mostrado é o informado pela Câmara na própria despesa e, no Senado, o do cadastro oficial (quando disponível).

Período coberto: 2026-01 a 2026-11 · 138.215 despesas analisadas.

Pontuação

Cada alerta tem uma pontuação de 0 a 100, usada só para ordenar a fila de publicação nas redes sociais: pontuação = 100 × peso da regra × intensidade. A intensidade vai de 0 a 1. Nas regras com limite, é o valor dividido pelo limite, com teto em 5 vezes, dividido por 5; nas demais, é 1. A pontuação não é uma medida de gravidade.

Regras


R01_refeicao_alta

Refeição acima do limite

Marca refeições do próprio parlamentar com valor acima de um limite fixo, igual para todos. Só se aplica às categorias que identificam refeição (na Câmara, 'Fornecimento de alimentação do parlamentar'; o Senado publica alimentação junto com locomoção e hospedagem, por isso não há como separar).

peso
0.6
limite
250
categorias
Fornecimento de alimentação do parlamentar
alertas ativos
103
Código-fonte (pipeline/regras/r01_refeicao_alta.py)
from typing import Any

import polars as pl

from pipeline import formatacao as fmt
from pipeline.regras.base import Contexto, Regra, intensidade_por_razao, na_lista, resultado


def aplicar(despesas: pl.DataFrame, params: dict[str, Any], contexto: Contexto) -> pl.DataFrame:
    limite = params["limite"]
    df = despesas.filter(na_lista("categoria", params["categorias"]) & (pl.col("valor") > limite))
    return resultado(
        df.select(
            pl.col("id").alias("despesa_id"),
            intensidade_por_razao(pl.col("valor") / limite).alias("intensidade"),
            pl.col("valor")
            .map_elements(
                lambda v: (
                    f"Refeição de {fmt.reais(v)}; o limite configurado é {fmt.reais(limite)} "
                    f"({fmt.numero(v / limite)} vezes o limite)."
                ),
                pl.String,
            )
            .alias("detalhe"),
        )
    )


REGRA = Regra(
    id="R01_refeicao_alta",
    nome="Refeição acima do limite",
    descricao=(
        "Marca refeições do próprio parlamentar com valor acima de um limite fixo, igual "
        "para todos. Só se aplica às categorias que identificam refeição (na Câmara, "
        "'Fornecimento de alimentação do parlamentar'; o Senado publica alimentação junto "
        "com locomoção e hospedagem, por isso não há como separar)."
    ),
    aplicar=aplicar,
    modulo=__name__,
)

R02_duplicidade

Possível duplicidade

Marca despesas do mesmo parlamentar com o mesmo fornecedor, o mesmo valor e a mesma data de outra despesa já registrada. A primeira delas não é marcada; as repetições sim. Valores pequenos e categorias em que a repetição é comum (como várias diárias de hotel na mesma noite) ficam de fora, conforme a configuração.

peso
1
valor minimo
100
categorias excluidas
Hospedagem, exceto do parlamentar no Distrito Federal
alertas ativos
1.528
Código-fonte (pipeline/regras/r02_duplicidade.py)
from typing import Any

import polars as pl

from pipeline import formatacao as fmt
from pipeline.regras.base import Contexto, Regra, na_lista, resultado

CHAVE = ["parlamentar_id", "fornecedor", "valor", "data"]


def aplicar(despesas: pl.DataFrame, params: dict[str, Any], contexto: Contexto) -> pl.DataFrame:
    df = despesas.filter(
        pl.col("data").is_not_null()
        & (pl.col("valor") >= params["valor_minimo"])
        & ~na_lista("categoria", params.get("categorias_excluidas", []))
    ).with_columns(
        pl.coalesce("fornecedor_cnpj_cpf", pl.col("fornecedor_nome").str.to_uppercase()).alias(
            "fornecedor"
        )
    )
    # A primeira despesa do grupo (pela ordem do id) é a referência; as demais disparam.
    df = (
        df.sort("id")
        .with_columns(
            pl.len().over(CHAVE).alias("ocorrencias"),
            pl.int_range(1, pl.len() + 1).over(CHAVE).alias("ordem"),
        )
        .filter(pl.col("ordem") > 1)
    )
    return resultado(
        df.select(
            pl.col("id").alias("despesa_id"),
            pl.lit(1.0).alias("intensidade"),
            pl.struct("ocorrencias", "valor", "data")
            .map_elements(
                lambda s: (
                    f"Há {s['ocorrencias']} despesas deste parlamentar com o mesmo fornecedor, "
                    f"o mesmo valor ({fmt.reais(s['valor'])}) e a mesma data "
                    f"({fmt.data_curta(s['data'])})."
                ),
                pl.String,
            )
            .alias("detalhe"),
        )
    )


REGRA = Regra(
    id="R02_duplicidade",
    nome="Possível duplicidade",
    descricao=(
        "Marca despesas do mesmo parlamentar com o mesmo fornecedor, o mesmo valor e a mesma "
        "data de outra despesa já registrada. A primeira delas não é marcada; as repetições "
        "sim. Valores pequenos e categorias em que a repetição é comum (como várias diárias "
        "de hotel na mesma noite) ficam de fora, conforme a configuração."
    ),
    aplicar=aplicar,
    modulo=__name__,
)

R03_outlier_categoria

Valor muito acima do comum na categoria

Para cada categoria de despesa e cada ano, calcula o valor abaixo do qual estão 99% das despesas (percentil 99) de todos os parlamentares. Marca as despesas acima desse valor. Categorias com poucas despesas no ano não são avaliadas.

peso
0.8
percentil
0.99
amostra minima
100
alertas ativos
1.356
Código-fonte (pipeline/regras/r03_outlier_categoria.py)
from typing import Any

import polars as pl

from pipeline import formatacao as fmt
from pipeline.regras.base import Contexto, Regra, intensidade_por_razao, resultado

GRUPO = ["categoria", "ano"]


def aplicar(despesas: pl.DataFrame, params: dict[str, Any], contexto: Contexto) -> pl.DataFrame:
    percentil = params["percentil"]
    df = (
        despesas.filter(pl.col("valor") > 0)
        .with_columns(pl.col("competencia").str.slice(0, 4).alias("ano"))
        .with_columns(
            pl.len().over(GRUPO).alias("amostra"),
            pl.col("valor").quantile(percentil, "linear").over(GRUPO).alias("limiar"),
        )
        .filter(
            (pl.col("amostra") >= params["amostra_minima"]) & (pl.col("valor") > pl.col("limiar"))
        )
    )
    rotulo = f"percentil {fmt.numero(percentil * 100, 0)}"
    return resultado(
        df.select(
            pl.col("id").alias("despesa_id"),
            intensidade_por_razao(pl.col("valor") / pl.col("limiar")).alias("intensidade"),
            pl.struct("valor", "limiar", "ano", "amostra")
            .map_elements(
                lambda s: (
                    f"Valor de {fmt.reais(s['valor'])}, acima do {rotulo} da categoria em "
                    f"{s['ano']} ({fmt.reais(s['limiar'])}, calculado sobre "
                    f"{fmt.numero(s['amostra'], 0)} despesas)."
                ),
                pl.String,
            )
            .alias("detalhe"),
        )
    )


REGRA = Regra(
    id="R03_outlier_categoria",
    nome="Valor muito acima do comum na categoria",
    descricao=(
        "Para cada categoria de despesa e cada ano, calcula o valor abaixo do qual estão 99% "
        "das despesas (percentil 99) de todos os parlamentares. Marca as despesas acima "
        "desse valor. Categorias com poucas despesas no ano não são avaliadas."
    ),
    aplicar=aplicar,
    modulo=__name__,
)

R04_cnpj_situacao

Fornecedor com CNPJ irregular na data

Consulta o cadastro do CNPJ do fornecedor na Receita Federal (via BrasilAPI). Marca a despesa se, na data dela, a empresa ainda não tinha sido aberta ou já estava com situação diferente de 'ativa' (baixada, inapta, suspensa ou nula). Empresas que fecharam depois da despesa não são marcadas.

peso
1
alertas ativos
34
Código-fonte (pipeline/regras/r04_cnpj_situacao.py)
from typing import Any

import polars as pl

from pipeline import formatacao as fmt
from pipeline.regras.base import Contexto, Regra, resultado


def _detalhe(s: dict[str, Any]) -> str:
    if s["aberto_depois"]:
        return (
            f"Segundo a Receita Federal, o CNPJ do fornecedor começou a funcionar em "
            f"{fmt.data_curta(s['data_inicio'])}, depois da data da despesa "
            f"({fmt.data_curta(s['data'])})."
        )
    return (
        f"Segundo a Receita Federal, o CNPJ do fornecedor está com situação "
        f"'{s['situacao']}' desde {fmt.data_curta(s['data_situacao'])}, antes da data da "
        f"despesa ({fmt.data_curta(s['data'])})."
    )


def aplicar(despesas: pl.DataFrame, params: dict[str, Any], contexto: Contexto) -> pl.DataFrame:
    df = (
        despesas.filter(
            pl.col("data").is_not_null() & (pl.col("fornecedor_cnpj_cpf").str.len_chars() == 14)
        )
        .join(contexto.cnpjs, left_on="fornecedor_cnpj_cpf", right_on="cnpj", how="inner")
        .with_columns(
            (pl.col("data_inicio") > pl.col("data")).fill_null(False).alias("aberto_depois"),
            (
                pl.col("situacao").is_not_null()
                & (pl.col("situacao").str.to_uppercase() != "ATIVA")
                & (pl.col("data_situacao") <= pl.col("data"))
            )
            .fill_null(False)
            .alias("inativo"),
        )
        .filter(pl.col("aberto_depois") | pl.col("inativo"))
    )
    return resultado(
        df.select(
            pl.col("id").alias("despesa_id"),
            pl.lit(1.0).alias("intensidade"),
            pl.struct("aberto_depois", "data_inicio", "data", "situacao", "data_situacao")
            .map_elements(_detalhe, pl.String)
            .alias("detalhe"),
        )
    )


REGRA = Regra(
    id="R04_cnpj_situacao",
    nome="Fornecedor com CNPJ irregular na data",
    descricao=(
        "Consulta o cadastro do CNPJ do fornecedor na Receita Federal (via BrasilAPI). Marca a "
        "despesa se, na data dela, a empresa ainda não tinha sido aberta ou já estava com "
        "situação diferente de 'ativa' (baixada, inapta, suspensa ou nula). Empresas que "
        "fecharam depois da despesa não são marcadas."
    ),
    aplicar=aplicar,
    modulo=__name__,
)

R05_fim_de_semana

Despesa de expediente em fim de semana ou feriado

Marca despesas de categorias típicas de expediente de escritório (manutenção de escritório, aluguel, material de consumo, consultorias, divulgação, publicações e serviços postais) cujo documento foi emitido num sábado, domingo ou feriado nacional.

peso
0.4
valor minimo
100
categorias
Manutenção de escritório de apoio à atividade parlamentar; Aluguel de imóveis para escritório político, compreendendo despesas concernentes a eles; Aquisição de material de consumo para uso no escritório político, inclusive aquisição ou locação de software, despesas postais, aquisição de publicações, locação de móveis e de equipamentos; Contratação de consultorias, assessorias, pesquisas, trabalhos técnicos e outros serviços de apoio ao exercício do mandato parlamentar; Consultorias, pesquisas e trabalhos técnicos; Divulgação da atividade parlamentar; Assinatura de publicações; Serviços postais
alertas ativos
2.115
Código-fonte (pipeline/regras/r05_fim_de_semana.py)
from datetime import date
from typing import Any

import polars as pl

from pipeline import formatacao as fmt
from pipeline.regras.base import Contexto, Regra, na_lista, resultado
from pipeline.regras.feriados import feriado


def motivo(d: date) -> str | None:
    """'sábado', 'domingo', 'feriado nacional (Tiradentes)' ou None em dia útil."""
    if nome := feriado(d):
        return f"feriado nacional ({nome})"
    if d.weekday() >= 5:
        return fmt.DIAS_DA_SEMANA[d.weekday()]
    return None


def aplicar(despesas: pl.DataFrame, params: dict[str, Any], contexto: Contexto) -> pl.DataFrame:
    df = despesas.filter(
        pl.col("data").is_not_null()
        & (pl.col("valor") >= params["valor_minimo"])
        & na_lista("categoria", params["categorias"])
    ).with_columns(pl.col("data").map_elements(motivo, pl.String).alias("motivo"))
    df = df.filter(pl.col("motivo").is_not_null())
    return resultado(
        df.select(
            pl.col("id").alias("despesa_id"),
            pl.lit(1.0).alias("intensidade"),
            pl.struct("data", "motivo")
            .map_elements(
                lambda s: (
                    f"Documento de despesa de expediente emitido em "
                    f"{fmt.data_curta(s['data'])}, {s['motivo']}."
                ),
                pl.String,
            )
            .alias("detalhe"),
        )
    )


REGRA = Regra(
    id="R05_fim_de_semana",
    nome="Despesa de expediente em fim de semana ou feriado",
    descricao=(
        "Marca despesas de categorias típicas de expediente de escritório (manutenção de "
        "escritório, aluguel, material de consumo, consultorias, divulgação, publicações e "
        "serviços postais) cujo documento foi emitido num sábado, domingo ou feriado "
        "nacional."
    ),
    aplicar=aplicar,
    modulo=__name__,
)

O que um alerta significa

Um alerta é um gasto atípico segundo uma regra estatística ou cadastral. Não é acusação. Ele indica uma despesa que merece um segundo olhar e mostra o documento oficial para quem quiser conferir. Os limites estão no arquivoconfig.toml e o código completo está no repositório público.

Aviso: Atipicidade estatística não é prova de irregularidade. O parlamentar pode ter justificativa.