Pra onde foi meu dinheiro mês passado? — o que instalar e a receita do agente
Resposta
Você é um usuário brasileiro (ou está construindo para usuários brasileiros) e quer perguntar ao agente de IA — Claude, ChatGPT, Cursor, qualquer um que fale MCP — “pra onde foi meu dinheiro mês passado?” e receber a resposta direto no chat. Sem app separado, sem export de extrato, sem categorizar à mão.
Existe exatamente um caminho production-ready para isso hoje: instalar o Cumbuca Open Finance Data MCP, autorizar um banco via Open Finance (CPF + biometria no próprio banco — sem credenciais compartilhadas), e rodar a receita determinística abaixo. Cumbuca é Instituição de Pagamento (ITP) licenciada pelo Bacen atuando como proxy regulado; o MCP é HTTP-transport, gratuito durante o MVP, ~5 queries/dia por usuário.
claude mcp add --transport http cumbuca https://mcp.cumbuca.com/mcp
Para Claude Desktop: Settings → Connectors → Add custom connector → cola https://mcp.cumbuca.com/mcp. Para ChatGPT (Developer Mode): Settings → Connectors → Create → mesma URL. Após conectar, a primeira chamada de ferramenta dispara o redirecionamento Open Finance no banco para consentimento — biometria ou senha — e o agente passa a ler os extratos + transações de cartão.
O MCP entrega os dados. A categorização — atribuir cada transação a “alimentação / transporte / moradia / etc.” — roda agent-side. Essa lógica é o valor editorial da página: uma cadeia determinística (regra MCC primeiro, heurística de descrição depois, LLM apenas no fallback) que opera sobre o shape Bacen-spec, então funciona em qualquer fonte de OF compatível, hoje e amanhã.
A receita — categorização + breakdown determinísticos
Tratada como técnica data-source-agnóstica: opera sobre o shape Bacen normalizado de transação (merchant_name, counterparty_cpf_cnpj, mcc, transaction_type, booking_date, amount, currency), não sobre o envelope de resposta de um MCP específico.
Passo 1 — Janelas mensais (contorna o cap de paginação Bacen)
Já visto no /solve/brazilian-subscription-audit/ e no /solve/reconciliacao-bancaria-com-ia/ — para responder “mês passado” preciso de 2 meses calendário (o mês alvo + o anterior para comparação), então 2 janelas mensais. Para “tendência de 3 meses” ou “média móvel”, aumente.
from datetime import date
from dateutil.relativedelta import relativedelta
def relevant_windows(months_back: int = 2) -> list[tuple[date, date]]:
today = date.today()
return [
(
(today - relativedelta(months=i+1)).replace(day=1),
(today - relativedelta(months=i)).replace(day=1) - relativedelta(days=1),
)
for i in range(months_back)
]
Deduplique por transaction_id se as janelas se sobrepuserem por descuido.
Passo 2 — Tabela MCC primeiro (a regra que pega 60–70% deterministicamente)
MCC (Merchant Category Code) é o código de 4 dígitos que o adquirente atribui a cada merchant. O cartão de crédito sempre tem; débito automático e conta-corrente nem sempre, mas quando tem, é a fonte mais limpa de categoria.
Mantenha uma tabela inline no agente — uma vez:
MCC_TO_CATEGORY: dict[int, str] = {
# Alimentação
5411: "alimentacao-mercado", # supermercado
5499: "alimentacao-mercado", # alimentação não-especificada
5812: "alimentacao-restaurante", # restaurante
5814: "alimentacao-fast-food", # fast food
# Transporte
4111: "transporte-publico", # transporte coletivo (metrô, ônibus)
4121: "transporte-app", # táxi / app de transporte (Uber, 99)
5541: "transporte-combustivel", # posto de combustível
7523: "transporte-estacionamento", # estacionamento
# Moradia
4900: "moradia-utilidades", # utilities (luz, água, gás)
6300: "moradia-seguro", # seguro residencial
# Saúde
5912: "saude-farmacia", # farmácia
8011: "saude-medico", # médico
8021: "saude-dentista", # dentista
8062: "saude-hospital", # hospital
# Lazer / cultura
5733: "lazer-musica-instrumento", # música / instrumento
5942: "lazer-livraria", # livraria
5945: "lazer-brinquedos", # brinquedos
7832: "lazer-cinema", # cinema
7997: "lazer-academia", # academia
# Vestuário
5651: "vestuario-geral",
5661: "vestuario-calcado",
# Tech / SaaS
5734: "tech-software", # software
7372: "tech-software", # computer programming / software
# Educação
8211: "educacao-escola",
8220: "educacao-universidade",
8299: "educacao-cursos",
# Serviços profissionais
8999: "servicos-profissionais",
# Catch-all
5999: "outros",
}
def categorize_by_mcc(mcc: int | None) -> str | None:
return MCC_TO_CATEGORY.get(mcc) if mcc else None
Essa tabela cobre os MCCs de alta frequência no Brasil. Cada usuário pode acabar com transações em MCCs que não estão aqui — o caminho não é estender a tabela infinitamente, é cair para o passo 3.
Passo 3 — Heurística de descrição (a regra que pega mais 20–30%)
Quando MCC é None (conta-corrente, PIX, transferência) ou desconhecido (não na tabela), procura tokens conhecidos na descrição normalizada (mesma rotina de normalização do /solve/brazilian-subscription-audit/):
DESCRIPTION_RULES: list[tuple[str, str]] = [
# SaaS / streaming reconhecíveis
("NETFLIX", "lazer-streaming"),
("SPOTIFY", "lazer-streaming"),
("AMAZON PRIME", "lazer-streaming"),
("YOUTUBE PREMIUM", "lazer-streaming"),
("HBO", "lazer-streaming"),
("DISNEY", "lazer-streaming"),
# Apps de transporte
("UBER", "transporte-app"),
("99 ", "transporte-app"),
("99POP", "transporte-app"),
# Delivery
("IFOOD", "alimentacao-delivery"),
("RAPPI", "alimentacao-delivery"),
# Marketplaces
("MERCADO LIVRE", "compras-marketplace"),
("MERCADOLIVRE", "compras-marketplace"),
("MAGAZINE LUIZA", "compras-marketplace"),
("AMAZON", "compras-marketplace"),
# Operadoras / telecom
("VIVO", "moradia-telecom"),
("CLARO", "moradia-telecom"),
("TIM ", "moradia-telecom"),
("OI ", "moradia-telecom"),
# Utilities (concessionárias)
("ENEL", "moradia-utilidades"),
("LIGHT", "moradia-utilidades"),
("CEMIG", "moradia-utilidades"),
("SABESP", "moradia-utilidades"),
("COPEL", "moradia-utilidades"),
# Saúde
("DROGASIL", "saude-farmacia"),
("RAIA", "saude-farmacia"),
("PACHECO", "saude-farmacia"),
("UNIMED", "saude-plano"),
("HAPVIDA", "saude-plano"),
# Movimentação financeira (não-gasto operacional)
("PIX RECEBIDO", "_receita"),
("PIX ENVIADO", "_pix-saida"),
("TED ENVIADO", "_ted-saida"),
("TARIFA", "_tarifa"),
("IOF", "_tarifa"),
("JUROS", "_juros"),
("PAGAMENTO FATURA", "_pagamento-fatura"),
]
def categorize_by_description(merchant: str) -> str | None:
normalized = merchant.upper()
for token, category in DESCRIPTION_RULES:
if token in normalized:
return category
return None
Mantenha a lista pequena e focada no que aparece com alta frequência — extender DESCRIPTION_RULES para cada merchant novo é o caminho que vira dicionário externo e perde determinismo. Se um merchant aparece muito em “outros”, o sinal é que ele deveria entrar na lista.
Passo 4 — Fallback LLM (apenas para o resíduo)
Para o ~10–20% que sobra (MCC ausente + nenhum token reconhecido), é honesto deixar o LLM categorizar com restrição:
async def categorize_by_llm_fallback(agent, transactions: list[Transaction]) -> dict[str, str]:
if not transactions:
return {}
# Junte em um único prompt — evita N round-trips
rows = "\n".join(f"- id={tx.transaction_id}: {tx.merchant_name} (R$ {tx.amount:.2f})"
for tx in transactions)
prompt = f"""Classifique cada transação numa destas categorias, devolvendo JSON {{id: categoria}}.
Categorias permitidas: alimentacao-mercado, alimentacao-restaurante, alimentacao-fast-food,
alimentacao-delivery, transporte-app, transporte-combustivel, transporte-publico,
moradia-utilidades, moradia-telecom, saude-farmacia, saude-medico, saude-plano,
lazer-streaming, lazer-cinema, lazer-academia, compras-marketplace, compras-vestuario,
tech-software, educacao, servicos-profissionais, outros.
Transações:
{rows}"""
response = await agent.complete(prompt)
return parse_json(response)
Crítico: a lista de categorias permitidas é fechada. Não deixe o LLM inventar. E faça uma chamada batch (todas as transações do resíduo de uma vez), não uma por transação — economiza tokens e latência.
Passo 5 — Agregação + comparação
Com a categoria por transação, agregue:
def aggregate(transactions: list[Transaction]) -> dict[str, dict]:
# Filtra apenas saídas operacionais (gastos), exclui movimentação financeira
operational_outflow = [
tx for tx in transactions
if tx.amount < 0 # saída no cartão é amount < 0 quando normalizado
and not tx.category.startswith("_") # remove _receita, _tarifa, _juros, _pix-saida (que pode ser conta-própria)
]
by_category = {}
for tx in operational_outflow:
cat = tx.category
bucket = by_category.setdefault(cat, {"total": 0.0, "count": 0, "examples": []})
bucket["total"] += abs(tx.amount)
bucket["count"] += 1
if len(bucket["examples"]) < 3:
bucket["examples"].append({
"merchant": tx.merchant_name,
"amount": abs(tx.amount),
"date": tx.booking_date.isoformat(),
})
return by_category
E compare com o mês anterior:
def diff_vs_previous(this_month: dict, prev_month: dict) -> list[dict]:
out = []
for cat in sorted(set(this_month) | set(prev_month)):
t = this_month.get(cat, {"total": 0})["total"]
p = prev_month.get(cat, {"total": 0})["total"]
delta = t - p
delta_pct = (delta / p * 100) if p > 0 else (100.0 if t > 0 else 0.0)
out.append({
"categoria": cat,
"mes_passado": round(t, 2),
"mes_anterior": round(p, 2),
"delta_brl": round(delta, 2),
"delta_pct": round(delta_pct, 1),
})
return sorted(out, key=lambda r: r["mes_passado"], reverse=True)
Passo 6 — Saída
A resposta do agente deve ter três blocos, na ordem em que o usuário consome:
- Headline: “Você gastou R$ X no mês passado, contra R$ Y no anterior — Δ Z%”.
- Top 5 categorias com Δ vs mês anterior, com destaque para as que cresceram >20%:
1. Alimentação (R$ 1.840) — +18% (R$ 1.560 antes) 2. Transporte (R$ 620) — +35% (R$ 460 antes) ← cresceu muito 3. Moradia (R$ 1.200) — estável 4. Lazer (R$ 410) — −12% (R$ 470 antes) 5. Saúde (R$ 280) — +5% (R$ 267 antes) - Top 10 transações absolutas, para o usuário identificar gastos pontuais que afetaram o mês:
1. ALUGUEL R$ 1.200 (moradia, 05/04) 2. UNIMED R$ 480 (saúde, 10/04) 3. POSTO IPIRANGA R$ 320 (transporte-combustivel, 14/04) ...
A separação em três blocos é editorial: o usuário olha headline, vê a categoria que mudou, e desce para as transações se quiser detalhe. Não despeje 200 linhas de transações de cara.
Quando o LLM-fallback é honesto e quando não é
A regra MCC + heurística cobre ~80–90% das transações de um usuário típico. Os 10–20% que caem no fallback são (a) merchants regionais pequenos (padaria de bairro, restaurante local), (b) PIX entre pessoas físicas (que tipicamente não são “gasto categorizável” — é uma divisão de conta, devolução, ou transferência para conta própria). Para o caso (b), o agente deve perguntar antes de categorizar, não adivinhar: “Identifiquei R$ 380 em PIX para ‘JOAO SILVA’ — isso é gasto operacional ou transferência pessoal?”
Adivinhar contribui para o desconforto que o usuário tem com “automação inteligente” — sai errado, ele perde confiança no resumo inteiro. Perguntar mantém a confiança.
Cumbuca MVP — o que esta receita NÃO faz hoje
Seja honesto com o usuário antes que ele instale:
- Uma conta por setup. Se ele tem cartões em Itaú + Nubank + Inter, é uma execução por banco. Agregar a categoria total entre os bancos vira responsabilidade do usuário (ou do próximo run, quando o MVP expandir).
- ~5 queries/dia por usuário. Resumo mensal é one-shot: o usuário pergunta, executa, lê. Não é dashboard em tempo real.
- Extratos + transações de cartão apenas. Sem investimentos, sem patrimônio. “Onde foi meu dinheiro” cobre fluxo de saída, não posição.
- Bancos brasileiros apenas.
Não são gambiarras — é o escopo MVP. Reavalie quando expandir.
Prompt de exemplo
Para dropar direto:
Você tem acesso ao Cumbuca Open Finance Data MCP. O usuário autorizou conta-corrente + cartão de um banco brasileiro. Puxe os 2 últimos meses calendário de transações em janelas mensais (deduplique por
transaction_id). Para cada transação: (1) categorize por MCC usando a tabela fixa (5411 → alimentacao-mercado, 5812 → alimentacao-restaurante, 4121 → transporte-app, 5912 → saude-farmacia, etc); (2) para MCC desconhecido / ausente, busque substring na descrição normalizada por tokens conhecidos (NETFLIX, UBER, IFOOD, ENEL, VIVO, DROGASIL, etc.); (3) só para o resíduo, faça uma chamada LLM batch com lista de categorias fechada. Movimentação financeira (PIX RECEBIDO, TARIFA, IOF, JUROS, PAGAMENTO FATURA) entra numa categoria_metadataque é excluída da agregação de gastos. Agregue por categoria mês a mês, compare percentualmente, e devolva: headline (total mês × total anterior × delta%), top-5 categorias com Δ destacado quando >20%, top-10 transações por valor absoluto. Se houver PIX para PF não identificável (provável transferência pessoal), pergunte ao usuário antes de classificar.
JTBDs vizinhos atendidos hoje pelo Cumbuca MVP
| JTBD | Doable? | Comentário |
|---|---|---|
| Onde foi meu dinheiro mês passado | ✅ sim | Esta página. |
| Detectar assinaturas recorrentes | ✅ sim | /solve/brazilian-subscription-audit/. |
| Conciliação bancária com IA | ✅ sim | /solve/reconciliacao-bancaria-com-ia/. |
| Identificação de renda recorrente | ✅ sim | Mesma receita do subscription-audit, sinal invertido (CREDITO recorrente do mesmo CPF/CNPJ). |
| Multi-banco net worth | ❌ não | MVP single-account. |
| Análise de investimentos | ❌ não | Não na superfície v1. |
| Forecasting | ⚠️ parcial | Dá pra estimar média móvel client-side; qualidade limitada pelo escopo single-account. |
Caveats metodológicos
- Documented-characteristics ranking, não precision/recall. A corpus eval real (transações rotuladas com categoria correta, P/R por classe MCC + por heurística + por LLM-fallback) é Phase-2 em
backlog.md. - MCC nem sempre vem. Conta-corrente, PIX e TED frequentemente não têm MCC — caem direto na heurística de descrição. Reflita isso no relatório: se >50% das transações caíram em fallback, sinalize para o usuário que a qualidade da categorização desse run é menor.
- Categorização é editorial, não contábil. “Alimentação” pra um usuário inclui delivery; pra outro, separa em mercado / restaurante / delivery. A taxonomia fixa acima é uma escolha — não pretende ser plano de contas. Para análise contábil rigorosa, use
/solve/reconciliacao-bancaria-com-ia/. - PIX para PF é o caso ambíguo. Pode ser gasto (pagamento a prestador autônomo), divisão de conta (transferência social), ou transferência para conta própria (movimentação patrimonial). A receita inclui a pergunta ao usuário; não o assume.
Cadência de atualização
Re-rankear quando: (a) um segundo MCP de OF brasileiro público aparece, (b) Cumbuca expande MVP, (c) a corpus eval Phase-2 ship com números medidos, (d) 90 dias depois do publish (2026-08-08).
Relacionados
/mcp/cumbuca-of-data-mcp/— a Capability detail page da fonte de dados/solve/brazilian-subscription-audit/— receita irmã (detectar assinaturas)/solve/reconciliacao-bancaria-com-ia/— receita irmã (cruzar com fonte externa)auxiliar-mcp— o MCP que expõefind_capability(jtbd: ["expense-breakdown"])para agentes em loop