This is a submission for the Hacktoberfest Open-Source AI Challenge: Week 2 — Side Hustle.
What I Built
Strukku: upload a photo of a receipt, and an open-weight vision model reads it — vendor, date, amount, currency, and expense category — straight into a running bookkeeping ledger you can export as a CSV.
The target user is a freelancer or small shop owner who currently does one of two things every month: pays a bookkeeper to manually retype a shoebox of receipts, or loses an evening doing it themselves. Strukku turns that into "take a photo, done."
It's specifically tuned for a case most bookkeeping tools handle badly: messy, mixed-language, informal local receipts — a handwritten note from a small Indonesian warung, a Gojek/Grab ride receipt, a market vendor's scrawled total. General-purpose OCR and bookkeeping SaaS are trained mostly on tidy, printed, English-first receipts, and fumble exactly this case.
Who would pay for this
Anyone currently paying a bookkeeper, or burning an afternoon a month doing their own books by hand. A tool that's faster than both and cheap enough to run for free is worth a small monthly fee on its own merits.
Demo
Not deployed yet — I built and verified this entirely within the challenge window (typecheck and production build both pass), but haven't yet run it against real photographed receipts with a live Supabase + OpenRouter setup, so I'm not going to claim a polished demo I haven't actually seen work. The honest next step, before I'd trust this with real bookkeeping, is exactly that: clone it, wire up a free Supabase project and OpenRouter key (both free), and feed it a stack of real receipts.
Code
github.com/fatik07/strukku — MIT-style setup instructions in the README, including the SQL to set up Supabase.
How I Built It
Stack: TanStack Start, TypeScript, TanStack Router, TanStack Query, TanStack AI (@tanstack/ai + @tanstack/ai-openrouter), Supabase (Postgres + Storage), OpenRouter, Tailwind CSS.
The flow:
Receipt photo → base64 → Supabase Storage (saved) → vision model (OpenRouter) → structured JSON → Zod validation → receipts table → ledger + CSV export
The AI call sends the photo as an image content part alongside a prompt tuned for the messy-receipt case:
function imageMessage(base64: string, mimeType: string) {
return {
role: 'user' as const,
content: [
{ type: 'text' as const, content: RECEIPT_EXTRACTION_PROMPT },
{ type: 'image' as const, source: { type: 'data' as const, value: base64, mimeType } },
],
}
}
The prompt itself explicitly tells the model what it's looking at:
"The receipt may be in Indonesian, English, or a mix of both, and may be a neat printed receipt, a crumpled handwritten note from a small local shop (warung), or a ride-hailing receipt (e.g. Gojek, Grab)."
The model's output is validated against a Zod schema before it ever touches the database — if the model doesn't support strict structured output, there's a plain-JSON fallback that re-parses its response, same defensive pattern I'd use for any untrusted model output.
The entire AI call is isolated in one module (src/server/ai/adapter.ts), so swapping models — or providers — is a one-line change, not a rewrite.
Why Does Open Innovation Matter?
Three concrete reasons I picked an explicit open-weight model (Google's Gemma, via OpenRouter's free tier — google/gemma-4-31b-it:free, not the openrouter/free router, which could silently pick a non-open model for a given request) over a closed API:
- Cost. The whole pitch is "cheaper than a bookkeeper." An AI step that costs nothing to run is what makes that arithmetic actually work at a small monthly price.
-
No lock-in. If a different open-weight model turns out to read Indonesian handwritten receipts better, swapping it in is a one-line change in
adapter.ts, not a renegotiated API contract with a single vendor. - Financial data privacy. Receipts are financial records. Keeping the AI layer open-weight keeps the door open to eventually pointing this at a self-hosted model instead of permanently renting inference from one closed provider — a real option for a tool that handles someone's money.
My First User
Not yet — this is fresh code built during the challenge window, not yet handed to a real freelancer. The honest next step is exactly that: get it in front of someone who actually has a shoebox of receipts, and find out where the extraction breaks on their specific mess of paper.
AI Disclosure
Built with heavy assistance from an AI coding agent (Claude Code) — architecture, implementation, and this write-up. I reviewed the code and the claims above myself; nothing here claims to be tested beyond what's stated (typecheck + production build pass; live extraction accuracy against real receipts is the next thing to verify, not something I'm claiming already works).
Top comments (0)