This is a submission for the Hacktoberfest Open-Source AI Challenge Week 2: Side Hustle
What I Built
Local Receipts is a receipt organizer for Brazilian freelancers and MEIs (micro-entrepreneurs). You give it the text or a photo of a receipt or invoice, and it returns the vendor, tax ID (CNPJ), date, total and expense category. At the end of the month you get a summary by category and a CSV that opens straight in Brazilian Excel.
The person who would pay for it is the freelancer who spends the last weekend of every month digging through receipts for their accountant. Those receipts hold client names, tax IDs and amounts, which is exactly what you don't want to upload to someone else's server.
Demo
Real output from my machine, with the model running locally:
curl -X POST localhost:3000/receipts -H 'content-type: application/json' \
-d '{"text":"PADARIA PAO QUENTE LTDA\nCNPJ: 11.222.333/0001-81\nEmissao: 03/10/2026\nTOTAL R$ 21,50"}'
{
"id": 1,
"vendor": "PADARIA PAO QUENTE LTDA",
"cnpj": "11222333000181",
"cnpjValid": true,
"issuedOn": "2026-10-03",
"totalCents": 2150,
"currency": "BRL",
"category": "alimentacao",
"needsReview": false
}
A month of receipts exports to a CSV with ; separators, decimal commas and a UTF-8 BOM, so it opens correctly in Excel without any import wizard:
data;fornecedor;cnpj;valor;moeda;categoria;revisar
2026-10-03;PADARIA PAO QUENTE LTDA;11222333000181;21,50;BRL;alimentacao;não
2026-10-08;Notion Labs Inc.;;10,00;USD;software_e_assinaturas;sim
2026-10-10;POSTO ESTRELA COMBUSTIVEIS;44555666000100;198,40;BRL;transporte;sim
The last two rows are flagged for review on purpose: the Notion invoice is in dollars, and the gas station's tax ID fails validation (I made it up for the test).
Code
🧾 Local Receipts
Organize os recibos do seu MEI ou freela sem mandar um centavo (nem um CNPJ) para a nuvem.
Você envia o texto ou a foto de um recibo ou nota fiscal. Um modelo open-weight rodando na sua máquina (Gemma 3 4B via Ollama) lê o documento e extrai fornecedor, CNPJ, data, valor e categoria. O código confere o CNPJ (dÃgitos verificadores) e marca para revisão tudo que não consegue confirmar. No fim do mês, você tem o resumo por categoria e um CSV que abre direto no Excel brasileiro.
Feito para o Hacktoberfest 2026 Open-Source AI Challenge, tema Side Hustle.
Por que não confiar só no modelo
Um modelo de 4B parâmetros lê bem, mas erra. Por isso o modelo só sugere e o código decide:
-
CNPJ: validado com os dÃgitos verificadores. Inválido ou ausente →
needsReview. - Moeda: o valor nunca é convertido. Documento…
How I Built It
The open-weight model. Gemma 3 4B, served by Ollama on my own machine. It reads both text and images, so one model handles pasted text and photos of receipts. Ollama's format field takes a JSON schema (generated from a zod schema), so the model has to answer in a fixed shape. The reply is validated with the same schema, and an invalid one becomes a clean 503 instead of garbage in the database.
The model suggests, the code decides. A 4B model reads well but is wrong sometimes, and it is bad news when it is wrong about money. So everything checkable is checked outside the model:
- The CNPJ is validated with its two check digits. If it is missing or invalid, the receipt is flagged.
- Amounts are stored as integer cents.
- The same document is never entered twice (content hash).
- If the date, tax ID or total can't be confirmed, the receipt gets
needsReview: true, and the model is told to answernullrather than guess.
Two bugs my own tests found. Mocked tests passed, so I ran real receipts through Gemma and found two problems:
- A gas station receipt came back as food (
alimentacao). Fix: I added a short category guide to the prompt, and it now comes back astransporte. - A US$ 10.00 invoice was saved as R$ 10,00 with no warning. That is the kind of silent error that costs real money. Fix: the model now extracts the currency, the amount is never converted, and any non-BRL document goes to review and stays out of the BRL total.
Both are the kind of failure a happy-path demo never shows. The final suite has 9 tests with the model mocked, and the real model was run against the cases above.
Stack: Node, TypeScript, Fastify, zod, SQLite (better-sqlite3), Ollama, Gemma 3 4B.
Why Does Open Innovation Matter?
- Privacy is the product. A receipt has a CNPJ, an amount and sometimes a client's name. With a local model, none of it leaves the laptop, and that is a feature I can honestly put on the box.
- The unit economics work. Processing 10 or 1,000 receipts costs the same: zero marginal cost. There is no per-token bill that forces a subscription price up.
- It works offline.
-
No lock-in.
OLLAMA_MODELswaps the model, and open weights mean a model could later be fine-tuned on Brazilian invoices, which a generic API handles unevenly.
I didn't compare against a closed API, so I won't claim it would extract worse. The point is where the data goes and what each document costs.
Limitations
- My photo test used a clean image I generated myself. A crooked, shadowed phone photo will fail more often, and I haven't measured by how much.
- No direct PDF support yet (convert to an image or send the text).
- Categories are the model's suggestion, so review what comes flagged.
- No login: it is designed as a single-user local tool.
What's Next
A tiny upload page, PDF support, and fine-tuning on Brazilian invoice formats.
Prize Categories
Best Use of Gemma: Gemma 3 4B runs locally through Ollama and does all the extraction, for both text and photos.
Top comments (0)