[KongchangAI]
· 2 min read· 1,497 words

Strukku: A Receipt Bookkeeping Tool for Freelancers Powered by Open-Weight Models

Strukku: A Receipt Bookkeeping Tool for Freelancers Powered by Open-Weight Models

Strukku uses the Gemma open-weight vision model to turn messy receipt photos into validated, exportable ledger data.

Strukku is a receipt bookkeeping tool for freelancers and small merchants, submitted during the Hacktoberfest Open Source AI Challenge. It targets long-tail scenarios ignored by generic OCR and bookkeeping SaaS — mixed Indonesian/English, handwritten notes, ride-hailing receipts — using the Gemma open-weight vision model to extract data, validating with Zod before writing to Supabase, with CSV export. AI calls are isolated in a single adapter module, so switching models is a one-line change. The author chose open weights for three reasons: zero call cost, no vendor lock-in, and the ability to self-host financial data. The code is complete and builds pass, but accuracy hasn't been validated on real receipts, a boundary the author honestly states.

From Shoebox to CSV: Automating Receipt Bookkeeping

Freelancers and small shop owners face a pile of trivial receipts every month — either paying an accountant to enter them by hand, or spending an entire evening typing them up themselves. Strukku tries to solve this with a single-line workflow: take a photo, and let AI handle the rest.

This is a receipt bookkeeping tool built on the Gemma open-weight vision model, submitted during the second week of the Hacktoberfest Open Source AI Challenge. It isn't yet another generic OCR wrapper — it targets a scenario that mainstream tools have long overlooked.

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

Gemma is a series of open-weight vision-language models that Google has been releasing since 2024, spanning parameter sizes like 2B, 7B, 9B, and 27B. It follows the same technical lineage as the Gemini series but is released with open weights, allowing commercial use and local deployment. Unlike pure-text LLMs, Gemma's multimodal versions (such as the Gemma 3 series) can directly accept image input and output text, making them suitable for "look-and-describe" tasks like receipt recognition. "Open-weight" means the model parameter files can be publicly downloaded and self-hosted for inference, which is fundamentally different from closed-source models that only expose APIs (such as GPT-4 and Claude). The google/gemma-4-31b-it:free mentioned in the article is a free inference endpoint provided through OpenRouter; it stands for instruction-tuned, suitable for structured extraction tasks driven by prompts.

What It's Actually Solving

Generic OCR and bookkeeping SaaS are almost all trained on tidy, printed, English-first receipts. But in the real world, a large number of freelancers and small merchants deal with: handwritten notes from Indonesian food stalls (warung), Gojek/Grab ride receipts, and scribbled totals from market vendors. These informal, multilingual receipts are precisely the blind spot of mainstream tools.

Strukku's prompt directly states this goal: receipts may be in Indonesian, English, or a mix of both; they may be printed, crumpled handwritten notes, or ride-hailing receipts. Model output only gets written to the database after Zod validation — if the model doesn't support strict structured output, there's a pure-JSON fallback parser, and no unvalidated model output is ever trusted.

Zod is a TypeScript-first runtime data validation library. It allows developers to declare the expected structure of data using schemas (for example, "amount must be a number, date must be an ISO string, merchant name must be a non-empty string"), and then validate the model's returned JSON field by field at runtime, throwing catchable errors on mismatch. In LLM applications, Zod is often used as an "output contract": the prompt requires the model to output according to a schema, and the same schema is used to validate it, effectively catching model hallucinations or format drift. The article mentions that "if the model doesn't support strict structured output, there's a pure-JSON fallback parser" — this refers to the fact that some models support native JSON Schema constraints (structured output), which can directly guarantee format compliance, while others rely only on prompt constraints, requiring a fallback to manually parsing the JSON string and validating it with Zod. Together, both paths ensure that unvalidated model output never pollutes the database.

The Technical Path: A Clear Pipeline

On the tech stack side, the project chose a complete solution from the TanStack ecosystem: TanStack Start, TypeScript, TanStack Router, TanStack Query, TanStack AI (@tanstack/ai + @tanstack/ai-openrouter), with Supabase providing Postgres and storage on the backend, model calls routed through OpenRouter to the Gemma free tier, and styling with Tailwind CSS.

The processing flow is quite straightforward: receipt photo → base64 encoding → stored in Supabase Storage → call the vision model → structured JSON output → Zod validation → write to the receipts table → generate the ledger and support CSV export.

What's interesting is that the entire AI call is isolated in a single module, src/server/ai/adapter.ts. This means switching models or providers only requires changing one line of code, rather than refactoring the whole system. For indie developers iterating quickly, this kind of architectural restraint is a pragmatic choice.

TanStack is a set of frontend/full-stack tools known for type safety and framework agnosticism, initially famous for TanStack Query (data fetching and caching) and TanStack Router (type-safe routing). TanStack Start, mentioned in the article, is its newer full-stack framework, offering an integrated solution for SSR, server functions, and routing; @tanstack/ai and @tanstack/ai-openrouter are wrappers for AI calls, abstracting model requests into a call pattern similar to Query. The motivation for choosing this ecosystem is that Router, Query, and AI calls share the same type system and caching semantics, eliminating the need to synchronize state across multiple libraries. For indie developers, this means less glue code and a more predictable debugging path, but it also means betting on a relatively young framework.

Why Stick with Open-Weight Models

The author gives three specific reasons, all favoring an open-weight model (Google's Gemma, called through OpenRouter's free tier as google/gemma-4-31b-it:free) over a closed-source API.

Cost is the primary factor. The entire business logic rests on "cheaper than hiring an accountant." A zero cost for the AI portion is the precondition for a small monthly-fee model to work. If model calls themselves incurred ongoing costs, the price advantage would be eroded.

No lock-in risk. If another open-weight model later turns out to recognize Indonesian handwritten receipts more accurately, switching is just one line in the adapter. By contrast, switching a closed-source provider means renegotiating an API contract.

Financial data privacy. Receipts are essentially financial records. Sticking with open weights means that in the future the model can be migrated to self-hosting, rather than permanently renting inference services from a closed-source provider. For a tool that handles other people's financial data, this is the responsible choice.

It's worth noting that the author deliberately chose a specific Gemma endpoint rather than the generic openrouter/free route — the latter could silently route to a non-open-weight model at request time, making the "open source" promise ring hollow. This kind of attention to detail and honesty deserves recognition.

OpenRouter is a routing service that aggregates inference from multiple model providers, letting developers access models from OpenAI, Anthropic, Google, Meta, and others through a unified API format, paying per usage or using some free quota. The article specifically notes that the author did not use the generic free route openrouter/free, because that route may dynamically choose any free model at request time, including models with non-open weights, thereby undermining the commitment to "using open weights." Specifying a concrete Gemma endpoint keeps the decision of "which model to use" firmly in one's own hands, and also makes the three arguments about cost, privacy, and replaceability hold up. This detail is a useful reference for any AI application that relies on third-party routing: the convenience of the default route often comes at the cost of predictability.

An Honest Status: Code Complete, Validation Pending

The author doesn't claim a demo that hasn't been verified. The project was completed within the challenge window, passing type checks and production builds, but it has not yet been fully run end-to-end on real photographed receipts in a live Supabase + OpenRouter environment.

In other words, the code structure is ready and the defensive programming is in place, but extraction accuracy in real scenarios remains unverified. The author clearly states that the next step is to hand the tool to a freelancer who actually has a shoebox of receipts, to see where it fails on a specific pile of paper.

On AI disclosure, the author acknowledges heavy use of Claude Code in the architecture, implementation, and this write-up, but reviewed the code and all claims, stating only what has actually been verified.

Takeaways for Indie Developers

Strukku's value may lie not in how polished it already is, but in the pragmatic approach to building AI applications that it demonstrates: narrow scenario + open weights + defensive integration + honest delivery.

It doesn't try to be a general-purpose bookkeeping tool, but specifically optimizes a neglected long-tail scenario. It doesn't chase the newest and largest closed-source models, but chooses an open-weight solution that is cost-controllable, replaceable, and self-hostable. It doesn't write model output straight to the database, but strictly validates with Zod. And it doesn't publish a demo link before validation, but honestly explains its boundaries.

The code is hosted at github.com/fatik07/strukku; the README includes SQL setup instructions for Supabase, under an MIT-style license. If you want to test whether this approach holds up, the barrier isn't high: clone the repo, connect a free Supabase project and an OpenRouter key, and feed it a stack of real receipts — which is exactly the next step the author plans to take.

For AI applications handling sensitive data, open-weight models offer more than just a cost advantage — they offer architectural freedom: you can switch models without rebuilding the system, and you can self-host for privacy when needed. Strukku is a concrete footnote to that logic.

#Strukku#open-weight model#Gemma#receipt bookkeeping#freelancers#OCR#Zod#OpenRouter#Supabase#indie developers
Share:

Related articles