Oracle3
Open-source trading engine and MCP server for prediction markets. Fee-aware no-arbitrage trading across related contracts, live on Kalshi, Polymarket and Solana or on paper.
Controle de gastos pessoais no terminal, em Python
Translation pending
FinancePython · JavaScriptGitHub @Lakes777OnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
No updates detected yet. GitHub releases will appear here.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
Loading the full README. The saved excerpt is shown below.
Spendwise · controle de gastos. Aplicativo para registrar e acompanhar gastos pessoais: na linha de comando, em Python puro, ou no navegador, com uma API em FastAPI. Ver ao vivo: https://controle-gastos-lakes777.vercel.app (demonstração com dados de exemplo, só seus) Gravado com dados de exemplo.Read the full README ↗
Spendwise · controle de gastos. Aplicativo para registrar e acompanhar gastos pessoais: na linha de comando, em Python puro, ou no navegador, com uma API em FastAPI.
Ver ao vivo: https://spendwisealp.vercel.app (demonstração com dados de exemplo, só seus)
Gravado com dados de exemplo.
$ python -m gastos resumo --mes 2026-09
alimentação R$ 342,80
mercado R$ 489,90
transporte R$ 127,50
--------------------------
TOTAL R$ 960,20
--simular, linhas que podem ser deixadas de fora e sem nunca importar o mesmo gasto duas vezes.xlsx) ou CSV, geral ou de um mês; o .xlsx sai com valores em R$, datas de verdade e linha de total com fórmula45,90) ou ponto (45.90)Requer Python 3.10+. A linha de comando não tem dependências externas; só a versão web precisa do FastAPI.
git clone https://github.com/Lakes777/spendwise.git
cd spendwise
# Adicionar um gasto (a descrição é opcional; a data padrão é hoje)
python -m gastos adicionar 45,90 mercado "compras da semana"
python -m gastos adicionar 12.50 transporte --data 2026-09-20
# Listar todos os gastos (com o número de cada um)
python -m gastos listar
# Editar o gasto número 2 (só muda o que for informado)
python -m gastos editar 2 --valor 25,00 --categoria transporte
python -m gastos editar 2 --descricao "" # apaga a descrição
# Remover o gasto número 2
python -m gastos remover 2
# Resumo por categoria (geral ou de um mês)
python -m gastos resumo
python -m gastos resumo --mes 2026-09
# Gráfico de barras no terminal
python -m gastos grafico # por categoria, da maior para a menor
python -m gastos grafico --mes 2026-09 # só um mês
python -m gastos grafico --por mes # evolução mês a mês
# Importar o extrato (fatura do cartão ou conta): CSV do Nubank ou OFX de qualquer banco
python -m gastos importar Nubank_2026-09.csv --simular # só mostra o que entraria
python -m gastos importar Nubank_2026-09.csv
python -m gastos importar extrato-inter.ofx # o formato é reconhecido pelo conteúdo
python -m gastos importar extrato-inter.ofx --pular 2,5 # deixa de fora as linhas 2 e 5 da lista
# Seu nome como aparece nos extratos: Pix enviado para ele é transferência entre contas suas
python -m gastos meu-nome adicionar "Ana Souza Lima"
python -m gastos meu-nome # lista os nomes
python -m gastos meu-nome remover "Ana Souza Lima"
# Gastos recorrentes: lançados sozinhos quando o dia chega
python -m gastos recorrente adicionar 1200 aluguel --dia 5
python -m gastos recorrente adicionar 39,90 internet --dia 31 # em mês curto, cai no último dia
python -m gastos recorrente adicionar 55,90 streaming --dia 10 --desde 2026-08 # inclui meses passados
python -m gastos recorrente # lista, com o próximo lançamento
python -m gastos recorrente remover 1 # para de lançar
# Orçamento mensal por categoria
python -m gastos orcamento definir mercado 500 # cria ou troca o limite
python -m gastos orcamento # situação do mês atual
python -m gastos orcamento --mes 2026-08 # de outro mês
python -m gastos orcamento remover mercado
# Exportar (o formato vem da extensão); não sobrescreve arquivo existente sem pedir
python -m gastos exportar gastos.xlsx # planilha do Excel
python -m gastos exportar setembro.xlsx --mes 2026-09
python -m gastos exportar gastos.csv # CSV com ; (Excel em português)
python -m gastos exportar gastos.xlsx --sobrescrever
# Ajuda
python -m gastos --help
Exemplo de listagem:
Nº DATA VALOR CATEGORIA DESCRIÇÃO
2 20/09/2026 R$ 12,50 transporte
1 23/09/2026 R$ 45,90 mercado compras da semana
Exemplo de gráfico:
Gastos por categoria
lanche ██████████████████████████████ R$ 30,00 56%
uber ███████████████████████▋ R$ 23,59 44%
TOTAL R$ 53,59
Exemplo de importação do extrato da conta:
$ python -m gastos importar extrato.csv
Arquivo reconhecido: extrato da conta do Nubank (CSV)
+ 01/09/2026 R$ 45,90 mercado Compra no débito - SUPERMERCADO CONDOR
+ 10/09/2026 R$ 18,50 alimentação Compra no débito - PADARIA BELA VISTA
- 02/09/2026 R$ 2.500,00 Pix recebido - EMPRESA (ignorado: entrada de dinheiro)
- 05/09/2026 R$ 1.200,00 Pagamento de fatura (ignorado: pagamento da fatura; as compras vêm da fatura do cartão)
2 gasto(s) importado(s), 0 já importado(s) antes, 2 ignorado(s)
Quando chega o dia de um gasto recorrente, qualquer comando o lança e avisa:
$ python -m gastos listar
Lançado automaticamente: R$ 1.200,00 em aluguel (05/10/2026)
Nº DATA VALOR CATEGORIA DESCRIÇÃO
1 05/10/2026 R$ 1.200,00 aluguel apartamento
Exemplo de orçamento:
Orçamento de set/2026
lazer R$ 95,00 de R$ 80,00 119% ██████████ ESTOUROU em R$ 15,00
mercado R$ 420,00 de R$ 500,00 84% ████████▍░ ATENÇÃO: sobram R$ 80,00
uber R$ 10,00 de R$ 50,00 20% ██░░░░░░░░ sobram R$ 40,00
Com orçamento definido, adicionar ou editar um gasto já mostra como ficou o mês:
$ python -m gastos adicionar 120 mercado
Gasto adicionado: R$ 120,00 em mercado
Orçamento de mercado em set/2026: R$ 420,00 de R$ 500,00 (84%) - ATENÇÃO: sobram R$ 80,00
Prints com dados de exemplo (modo de demonstração). Na raiz fica o lobby; dele, o Começar leva às 5 abas (Resumo, Gastos, Orçamento, Recorrentes e Importar), cada uma com seu endereço (
/#gastos, por exemplo), com as mesmas animações do portfólio.
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
python -m gastos.web
Depois, abra http://127.0.0.1:8000 no navegador. A página usa o mesmo banco da linha de comando (dados/gastos.db), então um gasto adicionado no terminal aparece na web e vice-versa. A documentação interativa da API fica em http://127.0.0.1:8000/docs.
| Rota | O que faz |
|---|---|
GET/POST /gastos | lista (com ?mes=AAAA-MM) e registra gastos |
GET/PATCH/DELETE /gastos/{id} | vê, edita (só os campos enviados) e apaga |
GET /resumo | total do período e por categoria, com porcentagem |
GET /orcamentos | situação do orçamento do mês (ok, atenção, estourou) |
GET /orcamentos/total | todos os orçamentos do mês somados, com as categorias que estouraram (null se não há nenhum) |
PUT/DELETE /orcamentos/{categoria} | define ou apaga o limite mensal |
GET/POST /recorrentes, DELETE /recorrentes/{id} | gastos que se repetem todo mês |
GET /exportar?formato=xlsx | baixa a planilha (ou csv) |
POST /importar/previa | recebe o arquivo (CSV do Nubank ou OFX) e mostra o que entraria, sem salvar |
POST /importar | salva os gastos revisados na prévia, sem repetir os já importados |
GET/POST /importar/meus-nomes, DELETE /importar/meus-nomes/{nome} | nomes do usuário: Pix enviado para eles não é gasto |
GET /importar/exemplo.csv | uma fatura de exemplo, com datas recentes, para testar |
GET /meses, GET /categorias | meses com gastos e categorias já usadas |
POST /conta/cadastro, POST /conta/entrar, POST /conta/sair | contas (só na versão online) |
GET /conta, POST /conta/excluir | quem está logado; apaga a conta (pede a senha) |
GET/POST /conta/chaves, DELETE /conta/chaves/{id} | lista, cria e apaga chaves de acesso (só online, só pelo site) |
GET /info | se é a demonstração, quem está logado e se o cadastro está aberto |
Variáveis de ambiente opcionais: GASTOS_BANCO (arquivo do banco), GASTOS_DEMO=1 (modo demonstração), HOST e PORT.
Na versão online, quem não entra numa conta usa a demonstração. Quem tem conta entra pelo botão Entrar e vê só os próprios dados, que ficam guardados no Postgres. Para criar conta é preciso um código de convite, definido na variável de ambiente CODIGO_CONVITE do servidor; sem ela, o cadastro fica fechado. No computador (python -m gastos.web com SQLite) não há login: o programa é de quem o roda.
Não há recuperação de senha por e-mail (o projeto não envia e-mails).
Para um programa seu (um bot do Telegram, um script) usar a API online sem guardar a sua senha, crie uma chave de acesso em Sua conta → Chaves de acesso. A chave (sw_...) aparece uma vez só, na criação; depois a lista mostra só o nome, quando foi criada e o último uso. Cada conta tem até 5 chaves, e apagar uma corta o acesso do programa na hora.
O programa manda a chave no cabeçalho Authorization e usa as mesmas rotas da página (gastos, resumo, categorias, orçamentos, recorrentes, importação), sempre na conta dona da chave:
curl -X POST https://spendwisealp.vercel.app/gastos \
-H "Authorization: Bearer sw_..." \
-H "Content-Type: application/json" \
-d '{"valor": "35.00", "categoria": "mercado", "descricao": "pão e leite"}'
# 201: {"id": 42, "valor": "35.00", "categoria": "mercado", "descricao": "pão e leite", "data": "2026-10-04"}
curl https://spendwisealp.vercel.app/categorias -H "Authorization: Bearer sw_..."
# 200: ["alimentação", "mercado", "transporte"]
valor é texto com ponto e até 2 casas ("35", "35.9" ou "35.90"); data ("AAAA-MM-DD") é opcional e vale hoje no horário de Brasília; a categoria vira minúscula. Chave errada ou apagada dá 401 ({"detail": "Chave de acesso inválida ou apagada"}). As rotas da conta (/conta/...: excluir a conta, criar ou listar chaves) recusam a chave com 403: quem tiver a chave do bot não consegue apagar a conta nem criar outras chaves. No computador (SQLite, sem login), o cabeçalho é ignorado.
Online, o app roda em modo demonstração: cada visitante recebe uma cópia própria dos dados de exemplo, e pode adicionar, editar e apagar sem que ninguém mais veja. Os dados de exemplo são sempre dos últimos 3 meses (relativos a hoje), com orçamentos e gastos recorrentes já lançados. Cada visitante é uma conta num Postgres (Neon), e as contas são apagadas depois de um dia.
O arquivo app.py da raiz é a entrada da Vercel, que entrega o endereço do banco em DATABASE_URL. Para testar a demonstração no seu computador, com um Postgres local:
GASTOS_DEMO=1 DATABASE_URL=postgresql://usuario@localhost/banco python -m gastos.web
python -m venv .venv
source .venv/bin/activate
pip install -r requirements-dev.txt
pytest
Os testes do Postgres rodam quando GASTOS_TESTE_POSTGRES tem o endereço de um banco (cada teste usa um schema próprio, apagado no fim); sem ela, são pulados. No GitHub Actions, um Postgres 18 sobe junto com os testes.
A suíte cobre o modelo de dados, o banco SQLite (incluindo a migração do JSON antigo, a edição e a remoção), a exportação para .xlsx e CSV, o gráfico, o orçamento, os gastos recorrentes (simulando datas), a importação do Nubank e do OFX, o fluxo completo da linha de comando e todas as rotas da API (com a data de hoje trocada por uma data fixa) o modo demonstração (visitantes isolados, cookie inválido, pedidos simultâneos, limites e limpeza das contas antigas) e o login (convite, senha em argon2id, bloqueio de tentativas, sessão encerrada, CSRF, isolamento entre usuários e exclusão da conta) e as chaves de acesso (token mostrado uma vez só, só o hash no banco, limite por conta, chave apagada ou de outro usuário, rotas da conta que recusam chave e CSRF continuando a valer para o cookie). Os testes usam pastas temporárias e nunca tocam nos dados reais.
controle-gastos/
├── gastos/
│ ├── __main__.py # linha de comando (argparse)
│ ├── armazenamento.py # banco SQLite (sqlite3, SQL à mão)
│ ├── exportacao.py # exportar para .xlsx e CSV
│ ├── formatacao.py # valores em reais (R$ 1.234,50)
│ ├── grafico.py # gráfico de barras no terminal
│ ├── importacao/ # leitura dos extratos
│ │ ├── __init__.py # ler_extrato: reconhece o formato pelo conteúdo
│ │ ├── comum.py # categorias, valores e o que ignorar (vale para todos)
│ │ ├── nubank.py # CSV do Nubank (fatura e conta)
│ │ └── ofx.py # OFX de qualquer banco (versões 1 e 2)
│ ├── orcamento.py # situação do orçamento (ok, atenção, estourou)
│ ├── recorrentes.py # regras de datas dos gastos recorrentes
│ ├── modelo.py # a classe Gasto
│ └── web/
│ ├── __main__.py # python -m gastos.web (servidor uvicorn)
│ ├── app.py # cria o app FastAPI e serve a página
│ ├── banco_postgres.py # o mesmo Banco, em Postgres, com uma conta por linha
│ ├── contas.py # cadastro com convite, login (argon2id), sessões e chaves de acesso
│ ├── demo.py # modo demonstração: uma conta com exemplos por visitante
│ ├── modelos.py # o que a API recebe e devolve (Pydantic)
│ ├── rotas.py # as rotas da API
│ └── static/ # a página: index.html, estilo.css, app.js e favicon.svg
├── app.py # entrada da Vercel (modo demonstração com Postgres)
├── vercel.json # o que não vai para o servidor (testes, docs)
└── tests/ # testes com pytest
Decimal em vez de float para dinheiro: float acumula erros de arredondamento (0.1 + 0.2 = 0.30000000000000004); Decimal faz contas exatas.sqlite3 da biblioteca padrão: um banco de verdade num único arquivo, sem servidor e sem instalar nada. O SQL é escrito à mão, com marcadores (?) para evitar SQL injection.TEXT, não REAL: o REAL do SQLite é um float e traria de volta os erros de centavos; em texto ("45.90"), o Decimal volta exato. Há um teste que falha se a coluna virar REAL.gastos.json antigo é importado numa única transação e renomeado para gastos.json.migrado (backup). Se algo falhar, nada fica importado pela metade.AUTOINCREMENT, então, depois de remover o gasto 5, nenhum gasto novo recebe o 5. Um número anotado nunca passa a apontar para outro gasto.; (a vírgula já é usada nos centavos) e arquivo em utf-8-sig, cuja marca inicial (BOM) faz o Excel mostrar os acentos certos..xlsx escrito à mão, sem bibliotecas: um .xlsx é um .zip com arquivos XML dentro, então zipfile e xml da biblioteca padrão bastam. O CSV depende das configurações regionais de quem abre (num Windows em inglês, o Excel separa as colunas na vírgula e quebra 30,00 ao meio); no .xlsx, o valor é guardado como número e a data como data, e a planilha abre certa em qualquer idioma. O arquivo gerado foi conferido abrindo no Excel real.=, +, - ou @ seria executado como fórmula pelo Excel; ele é exportado com um ' na frente, e aparece só como texto.▏▎▍▌▋▊▉█ dão precisão de 1/8 de caractere, e um gasto pequeno sempre aparece com pelo menos ▏. Categorias vêm da maior para a menor (fica fácil comparar); meses, em ordem cronológica.Decimal, não a porcentagem. Os casos de fronteira (79,99%, 80%, 100% e um centavo acima) têm testes.origem numa coluna com índice UNIQUE. No extrato da conta, é o identificador que o próprio Nubank dá; na fatura, que não tem identificador, é data + descrição + valor + um contador, para que duas compras iguais no mesmo dia (dois cafés) continuem sendo duas. Importar o mesmo arquivo de novo não repete nada. Atenção: o CSV e o OFX do mesmo banco dão origens diferentes para a mesma compra, então o mesmo período deve ser importado num formato só."41,80", sinal separado - 84,00, Pix no Crédito, descrições longas) viraram testes, com nomes e contas inventados.FITID), que vira a origem; ao lado dele vai o código do banco e um resumo (SHA-256) do número da conta, para duas contas não se confundirem sem guardar o número em si. O arquivo é lido com expressões regulares, que funcionam nas duas versões do formato (1.x em SGML, com tags sem fechamento, e 2.x em XML). Muitos bancos ainda gravam o OFX em Windows-1252; o programa tenta UTF-8 primeiro e, se não der, lê em 1252. As regras do que ignorar (pagamento de fatura, aplicação, Pix no Crédito) são as mesmas do Nubank e aceitam os jeitos diferentes que cada banco escreve ("PAGTO FATURA", "Pagamento de fatura").CHARSET:1252 no cabeçalho, mas vem em UTF-8 (por isso o UTF-8 é tentado primeiro), e escreve o Pix como Pix enviado: "Cp :18236120-Nome", com o código do banco da outra ponta; a descrição vira "Pix enviado - Nome", usando o nome com acentos do campo NAME.--pular 2,5. Um número que não existe na lista cancela tudo, em vez de importar sem aquela linha.<OFX> ou o cabeçalho do CSV), não pela extensão, que o usuário pode ter trocado.ALTER TABLE: bancos de versões anteriores não têm a coluna origem; ao abrir, o programa confere as colunas (PRAGMA table_info) e a acrescenta, sem mexer nos gastos.proximo_mes), que avança na mesma transação em que o gasto é inserido. Rodar o programa várias vezes no mesmo dia não duplica nada, e um gasto lançado que o usuário apagou não volta.--desde inclui meses passados, mas no máximo 12, para um erro de digitação como 2016 não criar 120 gastos.hoje(), que os testes trocam para simular a passagem do tempo (a véspera, o dia certo, meses sem abrir o programa, dia 31 em mês de 30 e ano bissexto).CREATE TABLE IF NOT EXISTS cria as tabelas de orçamentos e de recorrentes em bancos de versões anteriores na primeira vez que são abertos, sem mexer nos gastos.--mes validado e normalizado: 2026-9 vira 2026-09 (senão não acharia nada no banco) e setembro é recusado com uma mensagem clara.Banco, o mesmo cálculo de orçamento e a mesma exportação da linha de comando. Por isso as duas interfaces sempre concordam."45.90", não 45.9. Um número no JSON vira float no JavaScript e traria de volta os erros de centavos. O Pydantic recusa zero, negativo, NaN e mais de 2 casas decimais.secrets.token_urlsafe) e o banco guarda só o SHA-256 dele. "Sair" apaga a sessão no servidor, então um cookie copiado deixa de valer. O cookie é HttpOnly, SameSite=Lax e Secure no HTTPS, e vale 30 dias.SameSite=Lax, todo pedido que altera dados confere o cabeçalho Origin e recusa pedidos vindos de outro site.secrets.token_urlsafe (com o prefixo sw_, fácil de reconhecer se for colada no lugar errado) e o banco guarda só o SHA-256; por isso ela aparece uma vez só. Cada uso anota a data (usada_em), para o usuário ver se uma chave esquecida ainda está em uso. Com o cabeçalho Authorization, vale só a chave: errada, dá 401, sem cair no cookie nem na demonstração. Isso também permite pular a conferência do Origin nesses pedidos, sem abrir brecha de CSRF: o CSRF depende de o navegador mandar o cookie sozinho, e um site de fora não tem como pôr a chave no pedido (e, mesmo que mande uma chave qualquer junto com o cookie, o cookie é ignorado). As rotas da conta recusam a chave, e a criação de chaves trava a linha da conta (FOR UPDATE) para dois pedidos juntos não passarem do limite de 5. A tabela é criada com CREATE TABLE IF NOT EXISTS na primeira vez que o servidor sobe, sem migração manual, e some em cascata com a conta.tipo = 'demo'); o id das contas de usuário (u_...) nem tem o formato aceito pelo cookie da demo, e mesmo assim a demo confere o tipo da conta antes de abri-la. Cada uma dessas proteções tem um teste que falha se ela for removida (conferido quebrando o código de propósito).BancoPostgres tem os mesmos métodos do Banco em SQLite, e as rotas funcionam com qualquer um; um mesmo conjunto de testes roda nos dois para garantir que se comportam igual. O defeito foi reproduzido localmente com uvicorn --workers 4 e confirmado como resolvido.conta, e toda consulta filtra por ela, então um visitante não vê nem altera o gasto de outro, mesmo sabendo o número (há teste para isso). A conta vem de um cookie HttpOnly com um número aleatório (uuid4), conferido por expressão regular. É a mesma estrutura que um login precisaria.INSERT ... ON CONFLICT DO NOTHING); se a página faz vários pedidos juntos, os outros esperam e não repetem os exemplos. O lançamento dos recorrentes trava as linhas (SELECT ... FOR UPDATE), então duas cópias do servidor nunca lançam o mesmo mês. Um teste dispara 8 pedidos ao mesmo tempo com threads, e ele falha se o FOR UPDATE for removido.NUMERIC(12, 2) no Postgres: diferente do SQLite, o Postgres tem um tipo decimal exato para dinheiro; o valor volta como Decimal e o banco também recusa valor negativo (CHECK).ON DELETE CASCADE apaga gastos, orçamentos e recorrentes junto com a conta vencida.America/Sao_Paulo, às 22h de Brasília ele já estaria no dia seguinte.origem; ALTER TABLE ... ADD COLUMN IF NOT EXISTS a acrescenta na primeira vez, sem mexer nos gastos, e há um teste que parte de um banco no formato antigo.innerHTML no front: tudo que vem da API entra na página com textContent, então uma descrição como <script> aparece como texto e não é executada (proteção contra XSS).argparse, sqlite3, csv, zipfile, dataclasses e pathlib resolvem o problema sem dependências externas. FastAPI e uvicorn são usados só pela versão web.dados/ está no .gitignore, então informações financeiras pessoais nunca vão para o repositório.