DEV Community

一郎 支
一郎 支

Posted on

I Built LocalLedger: An Offline Receipt Organizer That Runs Gemma on Your Own Laptop

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

LocalLedger is an offline invoice and receipt organizer for freelancers. You point it at a folder of receipts; it reads them on your own laptop, sorts every line into a category, writes an accounting-ready CSV plus a plain-language quarterly review, and ends with a list of questions to ask your accountant.

Who would pay for it, and why: freelancers and one-person businesses. Every freelancer I know does their books the same way — a shoebox of PDFs in July, an evening of typing numbers into a spreadsheet, and a nagging worry that they missed a deduction. The existing tools solve this by taking your data: connect your bank feed, upload your receipts to their cloud OCR, pay $15–40 a month forever. For someone billing three clients from a kitchen table, that is real money and a real privacy cost — client names, addresses and rates all sitting in someone else's database.

LocalLedger is the opposite trade. The entire thing runs locally on an open-weight model, so the numbers never move and the cost per receipt is zero.

Demo

No install, no API key, no internet:

python -m localledger scan examples/receipts.txt --no-llm
Enter fullscreen mode Exit fullscreen mode
LocalLedger - scanning examples/receipts.txt
engine: deterministic-offline

  DATE        VENDOR                          AMOUNT  CATEGORY       NOTE
  ------------------------------------------------------------------------
  2026-07-03  ADOBE SYSTEMS INCORPORATED   22.99 USD  Software       Creative Cloud - monthly subscription
  2026-07-07  UBER TRIP                    18.40 USD  Travel         Ride to client meeting downtown
  2026-07-09  STARBUCKS STORE 1042          6.75 USD  Meals          Team coffee meeting
  2026-07-14  AMAZON MARKETPLACE           31.20 USD  Office         Printer paper and toner
  2026-07-21  LUFTHANSA                   210.50 EUR  Travel         Flight FRA-LHR return
  2026-07-28  MYSTERY VENDOR LTD            9.99 USD  Uncategorized  Misc supplies

  Totals: 210.50 EUR, 14.99 GBP, 224.33 USD
  Documents: 9   Uncategorised: 1

Questions to ask your accountant
  - Meals & entertainment total 6.75 - confirm the deductible percentage in your jurisdiction.
  - 1 uncategorised item(s) (MYSTERY VENDOR LTD) - can they be assigned to a deductible category?
  - Largest single expense: LUFTHANSA at 210.50 EUR - is this a capital asset rather than a one-off cost?
  - Expenses span EUR, GBP, USD - which exchange rate applies on the tax return?
Enter fullscreen mode Exit fullscreen mode

There is also a local web console (python -m localledger serve) that renders the same result as a printable review page, and it reads PORT, so it drops onto any host unchanged.

Two outputs land in out/: a ledger.csv you can import straight into your accounting software, and a quarterly-review.html with totals per currency, a category breakdown, every document read, and the accountant questions.

Code

The whole project is on GitHub and is MIT-licensed:

https://github.com/Zhiyilang/localledger

Zero third-party dependencies — the core, the CLI, the web console and the test suite all run on the Python standard library alone.

How I Built It

LocalLedger has two interchangeable engines, and that split is the whole design:

Engine When it runs What it does
deterministic-offline Always Regex parser + keyword categories. Zero setup, no model, no network.
local-open-weight-model When Ollama or any OpenAI-compatible server is reachable on localhost Writes the quarterly narrative in plain English from the parsed rows.

The parsing and categorising are plain code, not the model. The regex layer pulls the vendor, date and amount out of each receipt block and normalises dates to ISO (03/07/2026, 25/12/2026 and 2026-02-09 all land correctly). The keyword layer assigns a category. Only after the numbers are fixed does the local model get asked to write the narrative — it is handed the already-parsed rows and told not to invent figures. A bookkeeping tool that hallucinates a total is worse than useless, so the model never touches the arithmetic.

Every row also carries an evidence trail back to the file and line it came from (examples/receipts.txt:1 vendor=… / date=… / amount=…), so any number can be traced and audited.

from localledger import parse_file, summarize, html_report

receipts = parse_file("examples/receipts.txt")
summary = summarize(receipts)              # offline engine; pass model=get_model() to use Gemma
html_report(summary, receipts, "out/quarterly-review.html")
Enter fullscreen mode Exit fullscreen mode

It is tested: python eval.py runs 46 assertions covering parsing, date normalisation, multi-currency totals, categorisation, the accountant-question logic, CSV and HTML output, and — importantly — that an unreachable model falls back cleanly instead of crashing. GitHub Actions runs that suite on Python 3.8, 3.10 and 3.12.

Why Does Open Innovation Matter?

This is the part I care about, because for a freelancer the closed version is actively worse:

  • It runs on a laptop with no internet. The default engine is fully offline, so the tool works on a plane, in a café with bad wifi, or on a machine that never gets an outbound connection. A cloud OCR service cannot do that by definition.
  • The data stays off a server you don't control. Client invoices contain names, addresses and amounts. Running the model locally on localhost means there is no data-processing agreement to sign, no third-party breach surface, and no vendor that can change its privacy policy on you.
  • The cost per document is zero. Cloud document-AI bills per page. Local inference has no marginal cost, and that is what makes a €5–10/month price for a freelancer actually work instead of being another SaaS line item.
  • You can swap the model. Because the bridge speaks both Ollama's API and the OpenAI-compatible API, the user can run Gemma, Llama or a fine-tuned model of their own — and can fine-tune it on their vendor names and categories, something a general-purpose hosted API can never learn.

A closed API would have made all four of those impossible. The open pieces are not a feature bolted on top; they are the reason the product can exist at this price and with this privacy story.

Prize Categories

Best Use of Gemma — LocalLedger is built around running Google's open-weight Gemma locally. gemma2:2b is the default narrative engine, served through Ollama on localhost; pulling it is one command (ollama pull gemma2:2b) and the project's whole privacy-and-cost argument depends on the model running on the user's own machine rather than behind an API.


Anything a freelancer would actually pay for has to earn trust about their money. Running the model locally is how LocalLedger earns it.

Top comments (0)