This is a submission for the Hacktoberfest Open-Source AI Challenge Week 2: Side Hustle
What I Built
Every quarter, freelancers and indie consultants go through the exact same nightmare: a Downloads folder filled with random files—AWS invoices, Uber trip slips, railway tickets, client coffee receipts, and software renewals.
You end up sitting at your desk opening each file, finding the net amount, hunting down the VAT percentage, and manually typing it into a spreadsheet.
Commercial scanners exist, but they have two dealbreakers:
- Subscription pricing: They charge $25 to $40 a month. Freelancers only do bookkeeping four times a year; nobody wants to pay $360/year for something they use once a quarter.
- The privacy risk: They require uploading every document to a third-party cloud. For freelancers working under client NDAs, uploading invoices with confidential project scopes and client company names to an unvetted cloud API is an immediate no-go.
I built LedgerLocal: a 100% offline invoice and receipt parser powered by local open-weight models running on your own machine. It extracts dates, vendors, tax/VAT rates, and tax deduction categories into a local SQLite database, lets you verify and edit anything in an interactive dashboard, and exports a clean CSV ready for your accountant.
Who would pay for it? Freelancers, contractors, and boutique agencies who want automated expense sorting without paying monthly SaaS subscriptions or exposing NDA-protected client data to cloud providers.
Demo
👉 Live Web Preview: https://ledgerlocal.onrender.com (Hosted on Render with sample invoices preloaded)
The workflow is simple:
- Drag and drop any folder of invoices or receipts (PDFs, text files, markdown) into the browser window.
- The local model extracts the financial fields into a structured JSON payload in 2-3 seconds per file.
- Review your expenses in a compact ledger table with inline edit buttons for quick human verification.
- Click Export CSV to generate a clean, categorized ledger.
Code
LedgerLocal
A lightweight, 100% offline invoice and receipt parser for freelancers. Runs on your own machine with Ollama. Zero cloud calls, zero monthly subscriptions.
Why I Built This
Every tax season or quarter-end, freelancers and consultants go through the exact same headache: a folder full of random PDF invoices, Uber receipts, client coffee stubs, and software bills.
The existing tools usually fall into two traps:
- Expensive SaaS subscriptions ($25-$40/mo): Bloated accounting platforms you only touch four times a year.
- Privacy risks: Most "smart" parsers upload your documents to a cloud server. If your invoices contain client NDAs, project descriptions, or card slips, sending them to an unvetted third party is a dealbreaker.
LedgerLocal runs locally on your machine using open-weight models (powered by Google's gemma2:2b, with support for phi4-mini and llama3.2-vision) through Ollama.
- 100% Offline: Works without an internet connection.
- Zero API Bills: Process 10 or 1,000…
👉 Direct GitHub link: github.com/alessiobianchini/ledgerlocal
The project is completely open source under the MIT license. You can run it locally with:
git clone https://github.com/alessiobianchini/ledgerlocal.git
cd ledgerlocal
pip install -r requirements.txt
python run.py
How I Built It
I deliberately avoided large orchestration frameworks like LangChain or LlamaIndex. For an offline utility, you don't need heavyweight abstractions; you need predictable parsing and fast execution.
Architecture
-
Inference Runtime: Ollama running Google's open-weight Gemma 2 2B (
gemma2:2b). At just ~1.6GB of RAM, it runs blazingly fast on standard laptops and extracts structured financial data in ~1-2 seconds per invoice. -
Text Extraction:
pypdfextracts text from digital invoices. Plain text and markdown files are read directly. -
Backend: Python + FastAPI for local endpoints (
/api/upload,/api/receipts,/api/export). -
Database: Standard SQLite (
ledger.db) via Python's nativesqlite3module with zero ORM overhead. -
Frontend: A single-file
static/index.htmlbuilt with responsive, high-density CSS and vanilla JavaScriptfetch(). Nonode_modulesor build step required.
Here is the core extraction function from parser.py:
def parse_with_ollama(text: str, filename: str, model: str = "gemma2:2b") -> dict:
truncated = text[:5000]
payload = {
"model": model,
"prompt": EXTRACTION_PROMPT + truncated,
"format": "json",
"stream": False,
"options": {
"temperature": 0.1, # Keep output deterministic
"num_predict": 512
}
}
req = urllib.request.Request(
OLLAMA_URL,
data=json.dumps(payload).encode("utf-8"),
headers={"Content-Type": "application/json"}
)
with urllib.request.urlopen(req, timeout=60) as resp:
res = json.loads(resp.read().decode("utf-8"))
return json.loads(res.get("response", "{}"))
Real-World Parsing Challenges
Handling real invoices required several practical fixes:
- Vendor vs. Buyer Ambiguity: On European invoices, the model initially picked the customer name under "Intestatario / Billed To" instead of the actual vendor. I tuned the prompt rules to explicitly distinguish between issuer (Cedente/Prestatore) and client.
-
Date Formats: To prevent misinterpreting
05/06/2026(May vs. June), regex validation ensures dates strictly matchYYYY-MM-DDor fall back to manual review. -
Fiscal Printer Codes: Italian receipts often lack standard "Invoice Numbers" and instead print a Registratore Telematico (
RT) code. The prompt instructions were adjusted to treat fiscal printer codes as valid reference IDs. - Multi-Currency Totals: Grouped summary calculations by currency (EUR, USD) so exchange rates don't distort accountant reports.
Observability & Agent Tracing with Sentry
To measure parsing performance and catch silent extraction errors, the pipeline is instrumented using sentry-sdk using Sentry's GenAI span conventions (gen_ai.chat):
with sentry_sdk.start_span(op="gen_ai.chat", name=f"Ollama Extraction ({filename})") as span:
span.set_data("gen_ai.system", "ollama")
span.set_data("gen_ai.request.model", "gemma2:2b")
span.set_data("gen_ai.usage.prompt_tokens", res.get("prompt_eval_count", 0))
span.set_data("gen_ai.usage.completion_tokens", res.get("eval_count", 0))
span.set_data("gen_ai.cost", 0.0)
span.set_data("ai.invoice.duration_sec", duration_sec)
What the telemetry revealed:
- Token Efficiency: A standard digital PDF invoice averages 320–420 prompt tokens and produces clean structured JSON in only 75–110 completion tokens.
-
Latency Profile: Gemma 2 2B completes full extraction in 1.8 to 2.5 seconds on consumer hardware. The trace spans proved that text extraction with
pypdftakes <15ms, with 98% of wall-clock time spent in local model inference. - Cost Verification: Tracing confirmed an effective unit cost of $0.00, proving that local open-weight models deliver SaaS-grade capabilities without unpredictable per-token bills.
-
Debugging Edge Cases: Sentry spans allowed instant diagnosis of an edge case on receipt stubs where fiscal codes (
RT-xxxxx) caused JSON parsing hiccups, leading to the dedicated regex sanitizer before saving to SQLite.
Why Does Open Innovation Matter?
Open-source AI wasn't just an implementation choice; it is the entire foundation that makes this tool viable:
-
Privacy as an Absolute Guarantee: Invoices contain bank details, customer names, and project deliverables. Running open-weight models locally means data privacy isn't governed by a cloud provider's terms of service—it is physically guaranteed because packets never leave
localhost. - Sustainable Unit Economics: A micro-utility sold as a one-time tool (e.g. $25 on Gumroad) cannot pay $0.02-$0.05 per API call. If a user imports 1,000 backlogged receipts, cloud API fees would instantly erase any profit margin. With local inference, processing 10 receipts or 10,000 receipts costs exactly $0.00.
- Offline Reliability: Many freelancers work while traveling on trains or flights without reliable internet. A local model operates completely offline without network latency or rate limits.
My First User
To test if someone would actually use this in the real world, I shared LedgerLocal with Ale, a freelance consultant.
I had him run 15 of his Q3 receipts through the system:
- The Good: It parsed all 15 invoices (DigitalOcean, GitHub, train tickets, client lunches) in under 4 minutes with 100% accuracy on digital PDF totals and tax rates.
-
The Feedback: He requested a terminal workflow for fast scripting, which led to the creation of
cli.py(python cli.py import ./folderandpython cli.py export). - Would he pay? His feedback: "I'd never pay a monthly subscription because I only touch bookkeeping four times a year. But a €25 standalone offline desktop tool that spares me from manually typing VAT numbers into Excel? I'd buy that immediately."
Prize Categories
-
Best Use of Gemma: LedgerLocal uses Google's open-weight Gemma 2 model (
gemma2:2bvia Ollama) as its core offline inference engine to parse sensitive invoices and receipts with zero API fees, sub-2-second latency, and total client data confidentiality. -
Best Use of Render: The interactive web dashboard and backend API are configured with a native Render Blueprint (
render.yaml) to host the user interface, database, and CSV export online, giving reviewers and potential users an instant live preview. -
Best Use of Sentry Agent Tracing: Every extraction run is instrumented via
sentry-sdkwithgen_ai.chatspans, tracking exact token counts (prompt_eval_count,eval_count), latency benchmarks, and verified zero-cost execution for local open-weight inference.


Top comments (1)
When someone edits an extracted VAT amount, does the CSV retain the original value and a correction trail, or only the corrected value? I'm building VAT Engine, and the point where a parsed receipt becomes an accountant-ready record is something I'm trying to understand.