DEV Community

Cover image for "VaultLedger: an offline invoice and bank statement auditor built on Ollama"
Akshat Sharma
Akshat Sharma

Posted on AI-assisted

"VaultLedger: an offline invoice and bank statement auditor built on Ollama"

Hacktoberfest Open-Source AI Challenge Week 2: Side Hustle Submission 💸

This is a submission for the Hacktoberfest Open-Source AI Challenge Week 2: Side Hustle

What I Built

VaultLedger is an offline invoice and bank statement auditor for small shops and the bookkeepers who handle their records.

I've been into a lot of local stores, and many of them already do their auditing on a PC, but none of it is automatic. Someone sits there matching invoices to bank statement lines one at a time. Internet isn't something you can count on either, so a cloud tool isn't an option. VaultLedger does the matching on the shop's own computer, with no internet needed.

You upload the invoice PDFs and the bank statement CSV. A local open-weight model reads each invoice, and the app flags three things:

  • Duplicate invoices, so the same bill isn't paid twice
  • Invoices with no matching bank charge, so you can see what hasn't been paid
  • Bank charges with no invoice, the missing receipts that usually show up at tax time

On my sample files the whole audit takes about a minute and a half to two minutes on my laptop, and the result downloads as an Excel report that can be handed to the owner or the client.

Who would pay for this: a small shop owner or bookkeeper who spends hours matching invoices to bank statements by hand. One idea I'd want to test is a one-time license, or a small fee per batch of invoices. I haven't validated any price yet.

Demo

The video runs the audit with my wifi turned off. It reads three invoices, flags the duplicate, and catches the one bank charge (QUICKPRINT STATIONERY, 86.50) that has no receipt.

Code

🔒 VaultLedger: 100% Offline PDF Invoice & Bank Auditor

VaultLedger is a local-first AI financial reconciliation tool built for freelancers, independent consultants, and small agencies. It uses open-weight models via Ollama (qwen2.5:7b / llama3.2:3b) to parse unstructured PDF invoices, cross-check them against bank statement CSVs, flag duplicate bills, and catch missing tax receipts—all running 100% offline on localhost with zero cloud APIs and zero data leaks.

Built for the DEV Open-Source AI Challenge (Week 2: Side Hustle).


✨ Key Features

  • 100% Air-Gapped Privacy: Sensitive financial PDFs, client names, tax IDs, and bank statements never leave your machine. Works completely without an internet connection.
  • Deterministic JSON Extraction: Pairs Pydantic with Ollama's native structured output API (temperature=0.0) to extract vendor_name, invoice_number, invoice_date, subtotal, tax_amount, total_amount, currency, and expense category without hallucinations.
  • Duplicate Invoice Detection: Automatically flags duplicate bills (same…

The repo includes a sample_data folder with 3 fake invoices (one is a deliberate duplicate) and a bank CSV with one unreceipted charge, so you can test it in a couple of minutes.

How I Built It

  • Model: qwen2.5:7b, an open-weight model, running locally through Ollama
  • PDF reading: PyMuPDF pulls the text out of each invoice
  • Structured output: a Pydantic schema is passed to Ollama's format option, so the model has to return valid JSON with exactly the fields I need instead of free text I'd have to parse
  • Reconciliation: pandas compares the extracted invoices to the bank CSV
  • UI: Streamlit, with an Excel export of the audit

The flow is simple on purpose: PDF text goes to the local model, the model returns structured JSON, and pandas does the matching and flagging.

Diagram: invoice PDFs go through PyMuPDF to a local Ollama model, then pandas compares them with the bank CSV, and Streamlit shows the results and an Excel report

Things that broke along the way, and what I learned:

  1. The category field was useless at first. My schema asked for a category like Software or Travel, but I had typed the field as a plain string, so the model answered "Invoice" for everything. Ollama only enforces what is in the schema. Changing the field to a Literal with the allowed values fixed it.
  2. The results vanished when I clicked download. Streamlit reruns the whole script on every click, so the tables disappeared the moment I downloaded the Excel file. Storing the results in st.session_state fixed it. I found it while recording my own demo, and I re-recorded the video after fixing it.
  3. "Failed to connect to Ollama." The first time I clicked the audit button, every invoice failed. The model was downloaded, but the Ollama server wasn't running yet. The fix is to start Ollama first (it runs in the background once installed). The app shows this error clearly, and the README tells the user to check that Ollama is running before starting the app. Running the AI locally means you also run the thing that serves it. That's the trade-off I accept in exchange for privacy and no per-document cost.

Why Does Open Innovation Matter?

Privacy is the product. Shop owners and bookkeepers hold bank statements and invoices that other people trust them with. With an open-weight model running on the same machine, the documents never go to a server they don't control, and I could show it by recording the demo with wifi off.

It works without internet. In the shops I've visited, you can't count on a connection. A cloud API simply stops working when the internet does. A local model doesn't care.

It costs nothing per invoice. After the one-time model download, there is no API bill. A cloud API charges per document, which would eat into a small business's margin as volume grows.

I can swap models. The model is a setting in the app, and any model Ollama can run can be dropped in. I only tested qwen2.5:7b for this submission, so I can't claim how other models compare.

Where a closed model would still win: scanned or handwritten invoices. My version only reads PDFs that have a text layer, and a closed vision model would handle scans better today.

Limitations (what I'd fix next)

  • Matching is by amount only. Two different vendors charging the same amount could falsely match. The next step is amount plus a date window plus fuzzy vendor-name matching.
  • Duplicates are detected by vendor, total and date. Real duplicates often show up with the same invoice number, which I'd add next.
  • The model's numbers aren't verified. A 7B model can get arithmetic wrong, so I'd add a check that subtotal plus tax equals total, and flag the row for review if it doesn't.
  • Scanned PDFs aren't supported yet.
  • I tested on clean, synthetic invoices with one model. I can't claim accuracy on messy real-world invoices yet.
  • GST support for Indian invoices is the next feature I'd build.
  • It needs a computer that can run a 7B model comfortably.

This is a prototype built in a week. The part I trust is the idea: the documents stay on the machine, and the audit still works with no internet.

Top comments (0)