This is a submission for the Hacktoberfest Weekend Challenge: Build for a Friend
What I Built
My wife runs our household money. Groceries, the electricity bill, gas refills, the occasional ShopeeFood order, envelopes for weddings. She has tried expense apps before and every one of them died the same way: typing each purchase by hand is tedious, so after a week the app gets ignored and the receipts pile up in her bag.
Kas Rumah ("house cash" in Indonesian) removes the typing. She can take a photo of a receipt, upload a bank transfer screenshot, or type something like beli sayur 45rb sama galon 20rb ("bought vegetables 45k and a water gallon 20k"). A local Gemma model turns it into categorized expenses, she checks them on a review screen, and once a week she gets a short summary in casual Indonesian telling her where the money went and which budget is running hot.
Everything runs on our laptop at home. Her phone talks to it over our Wi-Fi.
To be upfront: she hasn't used it yet. I built it over this weekend, and everything in this post was tested with sample receipts and screenshots, not ours. Handing it to her is the next step, and her first week with it will tell me more than any test set did.
Demo
Code
masitings
/
kasrumah
Kas Rumah — household expense tracker. Local Gemma 3 (Ollama) reads receipts / transfer screenshots / typed notes. Laravel 13 + Inertia + React, 100% offline AI.
Kas Rumah
A household expense tracker for a spouse managing the family's budget. Snap a photo of a receipt or bank transfer screenshot, or type a casual note like "beli sayur 45rb" — a local Gemma 3 model via Ollama parses it into a categorized expense. Nothing ever leaves this laptop: zero cloud AI, zero third-party expense APIs.
Stack
- Laravel 13 + Inertia (React, TypeScript) with the official starter kit
- SQLite or MySQL database
- Ollama running
gemma3:4b(vision-capable) athttp://127.0.0.1:11434 - Money is stored as integer rupiah (no decimals)
- UI copy is casual Indonesian; code, comments, and commit messages are English
One-time Setup
composer install
npm install
cp .env.example .env
php artisan key:generate
php artisan migrate
Make sure Ollama is installed and the model is downloaded:
ollama pull gemma3:4b # ~3.3 GB
ollama serve
Optional overrides in .env:
OLLAMA_URL=http://127.0.0.1:11434
OLLAMA_MODEL=gemma3:4b
Development
composer run dev # sail-style:…How I Built It
Stack: Laravel with the React starter kit (Inertia), MySQL, and Gemma 3 4B running through Ollama on an M5 MacBook with 16 GB of RAM.
The app has five screens: Catat (record), Review, Pengeluaran (expenses), Ringkasan (summary), and Budget. The interesting part is how little I ask the model to do.
Reading receipts
One ExpenseExtractor service sends either an image or a line of text to Ollama's /api/chat. Two things made the 4B model usable:
-
Structured outputs. Instead of asking nicely for JSON, I pass a JSON schema in Ollama's
formatfield, with the category list as anenum. The model cannot invent a category, and I never parse broken JSON. -
Separate prompts for images and text. My first version used one prompt for both and said "one receipt is one expense, don't split items." That rule made the model return nothing for
sayur 45rb sama galon 20rb, because the text clearly has two expenses. Splitting the prompts fixed it. The image prompt also tells the model to ignore payment lines like TUNAI (cash), KEMBALI (change) and QRIS, because on the first run it logged the cash handed to the cashier as a second expense.
Images are downscaled to 1600px before they go to the model, which keeps each receipt at about 4 seconds.
Nothing the model returns is saved directly. Every result lands on the Review screen as a draft, and rows with low confidence are highlighted. She fixes what is wrong and taps Simpan.
The weekly summary
I don't let the model do math. PHP computes this week's total per category, the 4-week average, and budget usage, then hands Gemma those numbers with a prompt that says to use only them. Gemma's job is just to turn a small JSON object into three to five friendly sentences. A 4B model is good at that and bad at arithmetic, so that is the split.
Voice input and iPhone photos for free
I planned to add Whisper for voice notes. Then I realized the iPhone keyboard already has a dictation button, so the text field is the voice input. Same story with HEIC photos: setting accept="image/jpeg,image/png" on the file input makes iOS Safari convert to JPEG before upload, so the server never sees a HEIC file.
Where my AI coding agent cheated, and the honest numbers
I used an AI coding agent for a lot of the build and gave it a regression test: three receipt photos and four text samples with known answers. It came back with 7/7 passing. Then I read the prompt. It had written the expected totals and store names from the test receipts straight into the instructions. The tests passed because the answers were in the question.
I made it remove every value from the test set, split the fixtures into a tuning set and a holdout set it was only allowed to run once, and run every case three times. These are the real numbers for gemma3:4b on that small set of sample receipts and screenshots:
| Field | Correct |
|---|---|
| Amount (images) | 4 of 5 |
| Category (images) | 5 of 5 |
| Merchant name (images) | 2 of 5 |
| Text input | 4 of 4 |
Amounts and categories, the fields that matter for a household budget, are solid. Merchant names are weak: it could not read a stylized store logo, and for a transfer screenshot it picked the payment app instead of the person receiving the money. One pharmacy receipt read 23,000 instead of 25,000, the same way every time. Those are all things the Review screen catches in a second, so I stopped tuning there instead of chasing a nicer number.
Why Does Open Innovation Matter?
This app reads my family's receipts and bank transfer screenshots. Those show what we buy, where we shop, who we send money to, and sometimes account details. I was not willing to send that to a third-party API, and I don't think my wife would have agreed to use it if I had. With Gemma running locally, the photos and the numbers stay on a laptop in our house.
It also costs nothing to run. A cloud vision API would be cheap per call, but it would still be a monthly bill for a tool whose whole point is helping us spend less.
And because the model is open, I could pick the size that fits the job. A 4B model on a laptop is fast enough for a person waiting with their phone in hand, and the design (strict schema, PHP does the math, a human confirms) makes up for what a small model gets wrong. If it ever falls short, I can swap the model with one line in .env without touching the app.
Prize Categories
Best Use of Gemma





Top comments (0)