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
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?
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")
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
localhostmeans 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)