DEV Community

Mohamed Adel
Mohamed Adel

Posted on AI-assisted

Invoico - yet another invoice and receipts assistant

Hacktoberfest Open-Source AI Challenge Week 2: Side Hustle Submission πŸ’Έ

This is my participation in the Hacktoberfest 2026 Open-Source AI Challenge β€” Week 2 (Side Hustle).

What I built

I'm mohamedation, and I built Invoico for the challenge: a local invoice and receipt helper (another one, yes i know). The theme this week is side hustle, so the project had to be something you could imagine turning into a small product β€” with open weights in the loop, and some real measurement to back it up.

The problem I invisioned is pretty ordinary and common. Every few months there is a folder of invoice PDFs and phone photos of receipts, and then someone has to type vendor, date, net, VAT, gross into a spreadsheet. Those files are private. Pushing them through a closed vision API just to skip a boring afternoon of data entry never sat right with me. So for this challenge I built something that stays local. It can be turned into a self hosted or a packaged software and with minor automations, it can work as just taking a photo of the reciept and it automatically extract the data.

What it does

You drop in a PDF, JPG, or PNG. A local open-weight vision model through Ollama (Gemma) reads the page and pulls out the fields β€” including line items, cash tendered, and change, because till slips love to print a big Cash number that is not the bill. Plain Python then checks whether net + VAT roughly equals gross, whether the line items add up, and whether someone mixed up cash with the total. You review, confirm (notes stick around if you confirm a mismatch as-is), edit or delete later, and export a CSV.

There is also an Overview with charts, and an Analyze step where the same local model looks at the spend snapshot and writes a short briefing. Mixed currencies get an EUR estimate from a static ECB rate file (demo only β€” more on that below).

Nothing goes out to a cloud API for inference. That was the design from the start for the challenge.

How the side hustle angle fits the challenge

Week 2 asks you to think about the product shape, not only the demo. Here is the path I would take if this ever left the challenge folder.

Not SaaS. User accounts and a shared database would put invoices on someone else's servers, which fights the whole local-first idea.

Not a heavy self-hosted enterprise thing either. The compose file is in the repo if you want to run it that way, but that is not the story.

For a side hustle, packaged software fits better: install it (or one compose up), Ollama locally, SQLite locally. One-time license, or yearly if updates matter when models change. Inference stays on the user's hardware, so there is no per-document cloud bill to pass on. The repo is MIT either way β€” this challenge entry is open source.

Demo

In the video I walk through uploading a clean invoice, the intentional bad-math sample, confirming a mismatch so the note stays, a real till receipt where Total β‰  Cash, then Overview and Analyze.

A few screenshots:

Overview β€” spend at a glance

Invoices β€” filter, edit, CSV export

Analyze β€” local model briefing

Code

GitHub logo mohamedation / invoico

Local invoices. local AI

Invoico

Local invoice & receipt assistant. Drop in a PDF or a phone photo, a vision model on your machine pulls out the fields, Python checks the math, you confirm, and you get a CSV for the accountant.

Nothing is sent to a cloud API. That is the product.

I built this for the Hacktoberfest 2026 Open-Source AI Challenge β€” Week 2 (Side Hustle).

Demo: https://youtu.be/Cl_LL_XIZYI

Overview

Invoices

Analyze

Side hustle (where this actually goes)

This is not a SaaS. I am not spinning up user accounts and a shared database so your invoices sit on my servers β€” that would throw away the whole privacy angle.

It is also not an enterprise self-hosted platform. Freelancers do not want to babysit Postgres for a receipt folder.

The real product shape is packaged software: you install it (or docker compose up), Ollama runs on your machine, SQLite stays local. Pay once for…


ollama pull gemma3:4b
docker compose up --build
# or uvicorn + streamlit β€” see the README
Enter fullscreen mode Exit fullscreen mode

Stack is FastAPI + Streamlit + Ollama + SQLite. Compose runs the API and UI; Ollama stays on the host. pytest -q is green (15 tests).

How it came together

The flow looks roughly like this:

Upload a receipt or invoice (PDF or image)
       - Prepare the file for processing
       - Use Ollama Vision to extract invoice details and line items
       - Check and correct totals, change, and item sums when needed
       - Validate net amount, VAT, line items, and gross total
       - Save to SQLite, updating existing records with the same hash or invoice number
       - Review everything in the Invoices section
       - Optionally, generate an analysis summary
Enter fullscreen mode Exit fullscreen mode

A few things that i noticed:

Dumping the full Pydantic JSON Schema into Ollama's format made the small model just… drop fields. A hand-written (and AI cleaned) compact schema fixed that.

Gemma 4's "thinking" mode burned the token budget on chain-of-thought and came back with empty content so i used think=False.

On real till receipts from the data set https://huggingface.co/datasets/jsdnrs/ICDAR2019-SROIE small models kept putting Cash tendered or even a store ID like 1950 into the gross amount. I ended up with three layers: clearer prompt, extract cash / change / line items as their own fields, then Python recovery (cash βˆ’ change or sum of product lines).

Extraction was the obvious model call. Analyze is the second one it builds a spend snapshot. Felt more useful and somewht unique instead of just another sorter or AI OCR."

Numbers

I labelled a small set under eval/ground_truth.csv: four synthetic invoices in samples/ plus three real scanned till slips (Malaysian cash bills from the huggingface dataset.

python eval/run_eval.py --compare gemma4:e2b gemma3:4b
Enter fullscreen mode Exit fullscreen mode

Head-to-head (same images, same prompt)

Model Core exact* Gross accuracy Cash vs total correct† Avg s/doc
gemma3:4b 0.857 (6/7) 1.000 3/3 2.69
gemma4:e2b 0.571 (4/7) 0.571 0/3 1.22

*Core exact = vendor + gross + currency all right on the same document.

†On the three real slips that print both a bill total and a cash/change block.

Field accuracy (gemma3:4b)

Field Accuracy
currency 1.00
gross_amount 1.00
cash_tendered 1.00
change_amount 1.00
net_amount 1.00
vat_amount 1.00
vat_rate 1.00
issue_date 0.86
invoice_no 0.86
vendor 0.86
doc_type 0.71

Per document (gemma3:4b)

File Source Gross pred Cash pred Core Seconds
invoice_cloudtools.png synthetic 119.00 β€” yes 2.19
invoice_us_hosting.png synthetic 49.00 β€” yes 1.98
invoice_bad_math.png synthetic 250.00 β€” yes 2.50
receipt_cafe.png synthetic 15.41 β€” yes 2.93
image.jpg (BOOK TALK) real scan 9.00 10.00 yes 2.35
image (1).jpg (McD) real scan 26.60 100.00 no‑ 3.73
image (3).jpg (Bar Wang) real scan 8.20 8.20 yes 3.17

‑Amounts are right (total 26.60, cash 100.00, change 73.40). Core failed because the vendor came back as the legal entity (Gerbang Alaf Restaurants Sdn Bhd) plus a store number, not the short name McDonald I had in the labels. Wrong label match, not a wrong total.

Same real slips on gemma4:e2b

File Truth gross e2b gross e2b cash
BOOK TALK 9.00 18.00 10.00
McDonald's 26.60 53.20 (wrong / missing)
Bar Wang Rice 8.20 3.30 β€”

Faster, sure (about 1.2s per doc), but on till receipts it was a mess. So the default in the repo is gemma3:4b.

Line items are a useful second opinion when the model grabs a store ID or doubles something. And validation still matters; the synthetic bad-math invoice (net 200 + VAT 40 β‰  gross 250) is supposed to stay in review.

Why open weights

Privacy first β€” invoices do not need a cloud vision hop.

Cost is next after you have the hardware, another document is basically free which i think the future will lean more towards this.

And control: could swap gemma4:e2b for gemma3:4b, re-run the same labelled set or improve your review time and so on.

  • Overall
  • Best Use of Gemma β€” extraction and analyze both go through Gemma via Ollama (gemma3:4b by default; e2b still around for comparison) and i might test more open weights models later.

Quick FX note

Overview and Analyze convert mixed currencies to EUR using fx/eurofxref.csv which is a static ECB snapshot (demo, dated 13 October 2026). Not live FX, not auto-updated, not for real bookkeeping or tax. Native currency totals stay visible next to the estimate.

Still rough

The labelled set is small, and real phone photos will keep being messier than the clean samples.


That is my Week 2 entry. Repo and demo are linked above. As always, any feedback is much appreciated.

Top comments (0)