This is a submission for the Hacktoberfest Open-Source AI Challenge Week 2: Side Hustle
What I Built
LedgerLens — a receipt sorter for freelancers that runs entirely on their laptop. Drag a folder of receipt photos and PDFs into the app; get back a tax-ready Schedule C CSV, per-receipt audit JSON, and a printable quarterly summary. Nothing leaves the machine. Ever.
The person it's for: Rachel, a freelance photographer doing ~40 client projects a year. Every quarter she spends 3–5 hours sorting receipt photos, PDF email attachments, and crumpled paper into a spreadsheet for her accountant — or pays him $400+ to do it. She already refused QuickBooks because "why does Intuit need my bank login?"
Her receipts contain client names, project codes, and card last-4s. That data should not be sitting on someone else's server so a cloud OCR service can read it.
Price: $39 one-time for the review queue + multi-client mode. MIT-licensed core.
The part that makes it work long-term: when Rachel corrects "Sweetgreen → Client Meal" once, LedgerLens remembers — locally, as a few-shot example that gets prepended to the extraction prompt on the next run. Not a general API's guess at her categories. Her categories. No GPU, no training job, no per-call fee.
Demo
Real pipeline, WiFi off (90 seconds):
https://youtu.be/m_DB3SEOhn8
What the video shows:
- WiFi off (visible in the menu bar) — stays off the entire time
- Drag
samples/receipts/into the local Streamlit UI - Pipeline runs: OCR → local Qwen2.5-VL extraction → validation → categorization
- One live correction (Starbucks → Client Meal) — then the correction loop is explained
- CSV export opened in Excel
- WiFi still off at the end
Live UI demo (hosted, sample data only):
https://ledgerlens-app.streamlit.app/
I deliberately did not deploy the real pipeline to Streamlit Cloud. A hosted demo would mean receipt data goes to someone else's server, which contradicts the entire pitch. The hosted version shows the UI with hardcoded sample data and a yellow "DEMO MODE" banner explaining exactly that. Streamlit Cloud can't run Ollama — which is precisely the point.
Code
https://github.com/Ashutoshranjan195/ledgerlens
Repo structure:
-
app/core/— the pipeline (extract → normalize → model → validate → output) -
app/update/— 5-channel auto-update subsystem with an eval gate -
app/ui/— Streamlit frontend -
prompts/— versioned, eval-gated prompt files -
evals/— golden set (39 cases) + accuracy runner -
tests/— layering tests that enforce the offline guarantee in CI
How I Built It
Open-weight models: Qwen2.5-VL 7B via Ollama (default profile), with llama3.2:3b as the 8GB-RAM floor and qwen2.5:32b-vl for studio users. Embeddings via nomic-embed-text, also local. Three profiles in a single models.yaml — swapping models means editing one line.
Local inference: Everything runs through Ollama. No cloud API in the core loop. The architecture deliberately separates app/core/ (the pipeline) from app/update/ (the only place allowed to touch the network) — and tests/unit/test_layering.py enforces this at CI time. Nothing under app/** may import requests, httpx, or openai outside app/update/.
Agent harness: A hand-rolled ReAct loop in app/agent/harness.py that stays fully inspectable — every prompt is a versioned file in prompts/, never a string literal. No framework magic you can't read.
Auto-update with an eval gate: This is the part that makes open-source AI safe to ship. Five channels — app, models, prompts, deps, schema — each independently updatable. The important one:
[Channel 3/5] Evaluating prompts against golden baseline...
-> Staged but NOT PROMOTED extract_fields (v2).
Score 87.2% did not beat 87.2% by margin 1.00pp.
Most "AI apps" silently get worse when the vendor swaps a model under them. LedgerLens can't — the eval gate blocks promotion unless the new prompt beats the current one on the golden set by a documented margin. The gate is a directory boundary (app/update/ is the only thing allowed to read evals/run.py), not a discipline.
Honest accuracy numbers (on 39 golden cases, qwen2.5:7b-vl, 16GB RAM):
| Field | Accuracy |
|---|---|
| Vendor | 94.9% |
| Date | 100% |
| Total | 97.4% |
| Tax | 100% |
| Category | 92.3% |
| Overall exact match | 87.2% |
Good, not great. docs/ACCURACY.md publishes the misses — crumpled thermal paper, multi-currency, handwritten tips, Amex statement PDFs. Category accuracy is the number to watch; the correction loop is the path to 97%+ on a specific user's data.
Why Does Open Innovation Matter?
Three reasons — and each one is a reason a closed API could not have shipped this product at this price.
1. Cost math only closes with local inference.
1,000 receipts through a cloud VLM API ≈ $15–25. Through LedgerLens ≈ $0 marginal — the user's laptop does the work. At $39 one-time, cloud inference would eat the margin by month three. The $39 price is only possible because the model runs locally. A closed-API version of LedgerLens would have to be $8/mo forever, which is exactly the pricing Rachel already rejected.
2. Privacy isn't a feature here — it's the whole reason the product exists.
There is no server for me to leak, subpoena, or get breached. Not "we don't log your data" — there is no server. This isn't a marketing claim; it's enforced by tests/unit/test_layering.py which fails CI if anything under app/** imports requests, httpx, or openai. The offline guarantee is a test, not a promise.
3. Model-swappable, forever.
app/models.yaml has three profiles. When Qwen ships a better 7B vision model, my users get it by editing one line. When a closed vendor deprecates a model or triples its price, my users notice nothing. docs/WHY_OPEN.md has the honest comparison — closed APIs still win on raw accuracy for hard edge cases like crumpled receipts. But for this specific task (structured field extraction from a receipt), a 7B open VLM is within 1–2 percentage points of a frontier model, at $0 marginal cost. That 1–2% gap is worth far less than the $15–25 per 1,000 runs the closed API would cost.
What open made possible that closed would have blocked:
- The $39 one-time price
- The complete absence of a data server
- The ability to fine-tune later on a specific user's corrections without a per-call cost
- An update system that can't silently degrade the product
My First User
I haven't put this in front of a real freelancer yet. That's the honest version. The plan is in docs/FIRST_USER.md: post to r/freelance and r/photography this week, offer free installs in exchange for honest feedback, and record what breaks. The three questions I'll ask on the call:
- "What did it get wrong?"
- "Would you pay $39 for this?"
- "What would you need before you'd switch?"
If I get a real quote, I'll edit this post.
What's Broken / What's Next
Judges deserve honesty:
- Amex statement PDFs — multipage, mixed format, messy. #1 request from testers. Roadmap item 1.
- Mileage tracking — merging receipts + mileage into one report is the wedge into "bookkeeping app" territory.
- Category accuracy (92.3%) — good, not great. The correction loop gets you to 97%+ on a specific user's data, but the first-run experience needs to be smarter.
-
First-run hardware detection — the app should auto-select
fastvsbalancedbased on available RAM. Currently it asks.
Try It
git clone https://github.com/Ashutoshranjan195/ledgerlens
cd ledgerlens
pip install -e .
python -m app.main
Hardware floor: 8GB RAM on the fast profile. 16GB recommended for balanced. 32GB for best. Windows / macOS / Linux.
MIT-licensed core. $39 perpetual for the review queue and multi-client mode.
Repo: https://github.com/Ashutoshranjan195/ledgerlens
Hosted UI demo: https://ledgerlens-app.streamlit.app/
Video (WiFi off): https://youtu.be/m_DB3SEOhn8
Top comments (0)