DEV Community

fatik07
fatik07

Posted on

Strukku: Receipt Bookkeeping for Freelancers, Built on an Open-Weight Model

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

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
Enter fullscreen mode Exit fullscreen mode

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 } },
    ],
  }
}
Enter fullscreen mode Exit fullscreen mode

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)