This is a submission for the Hacktoberfest Open-Source AI Challenge Week 3: Slow and Steady
What I Built
SlothSpend is a desktop app that checks your bank statement for subscriptions you forgot about. It runs entirely on your own computer.
You give it the CSV your bank already lets you download. A few seconds later it tells you:
- which charges repeat (and which ones only look like they do),
- which prices went up without you noticing,
- which merchant charged you twice,
- which services overlap (two streaming apps doing the same job),
- how much the next 30 days will probably cost, as a range rather than one made-up number.
Then it does the part I always skip: it follows up. It reminds you a few days before a renewal. If you say "I cancelled it", it waits for your next statement and checks that the charge actually stopped. If the merchant charged you anyway, it tells you so and already has a refund email written, with the exact dates and amounts.
So who gets to be lazy? Anyone who has ever said "I should really go through my statement" and then didn't. That includes me. Reading 400 rows of ACH DEBIT IRONTRAP FITNESS MEMB 7731 is boring enough that most of us never do it, and that's how a $39.99 gym membership turns into $49.99 without anyone noticing.
The sloth rules
I gave the app a personality to fit the theme, and some hard limits that come with it:
- It informs, it never acts. SlothSpend never cancels anything, never logs into anything and never emails a merchant. It writes the draft and you send it.
- It never makes up a link or a phone number. Every answer and draft goes through a guard that removes them. A model inventing a "cancel here" URL is a phishing risk.
- It never claims to have done something it didn't. If the model says "I've set a reminder" and no tool call confirmed it, the reply gets replaced with what actually happened.
- Every number says where it came from. Each figure is labelled Gemma 4 Β· local, TabPFN v2, or a clearly marked Template / Heuristic fallback if a model isn't available.
Demo
π Website: slothspend.netlify.app
The app has a Demo data β Sample statement button that loads 12 months of made-up charges, so you can try everything without using your own bank data. The screenshots below come from that sample. Here's the whole flow, step by step.
1. Getting ready (one time only)
On first launch, SlothSpend checks three things: its own local engine, a Gemma model in Ollama, and TabPFN. If you don't have Gemma yet, it offers to download it and shows a progress bar. TabPFN v2's weights (~70 MB) download on their own. After that, nothing leaves your computer.
2. Import a statement, even an awkward one
Every bank exports CSVs differently. This one is from an Indian bank: 13 junk lines before the header, dates written day-first, separate Debit and Credit columns, amounts in βΉ. SlothSpend worked out most of that by itself and only asks about the one thing it wasn't sure of, which column holds the money. It remembers the layout for next time.
3. The report
The pink card at the top is Sloth's summary in plain English. Below it:
- Monthly recurring: what you actually pay every month.
- Next 30 days: TabPFN's forecast with a likely range (q10βq90), not one fake-precise number.
- Potential savings: how much you'd save by cancelling the "zombie" subscriptions yourself, plus any double charge worth disputing.
- Flags: duplicates, price rises, overlaps and zombie suspects.
The chart shows 12 months of subscription spend. The pink July bar is the month the gym raised its price and charged twice.
4. "Why was this flagged?"
Click any merchant to see its full history and the reasons in plain words: "Billed every ~30 days, Β±1 day, near the 5th of the month. The same $39.99 eight times in a row, then $49.99 for the last four." If the model got it wrong, press Not a subscription. TabPFN takes your correction into account and re-scores everything in about 2 seconds, with no retraining.
5. Ask Sloth, a chat agent that shows its work
Ask Sloth is a local agent: Gemma 4 with 8 typed tools. You can ask things like "What will subscriptions cost next month if I cancel StreamFlix and WatchNest?" and watch it call get_merchant twice and then forecast_spend. That last one runs a live TabPFN forecast with those two merchants removed. Every tool call shows up in the chat, so you can see where each number came from.
6. "Cancel it for me" β "No, but here's everything you need"
This was my favourite thing to test. If you ask "Cancel IronTrap for me", Sloth explains that it can't, writes a cancellation email using your real renewal date and amount, and offers to remind you 3 days before the next charge. The email contains no links and no phone numbers, and it ends with "Check the dates and amounts, then send it yourself."
7. Reminders that actually follow through
Each reminder goes through four steps: Watching β Reminded β Kept / You cancelled β Verified. You can keep it, snooze it (up to 3 times), or say you cancelled it. You also get desktop notifications. If the app was closed when a reminder was due, it shows up the next time you open it.
8. Verified⦠or still charged
When you import the next statement, SlothSpend checks whether a charge appeared after your cancellation date. If none did, the reminder becomes Verified and shows what you're saving.
If the merchant charged you anyway, you get the screen at the very top of this post: Still charged, plus a refund request that lists both the charge after you cancelled and the old July double charge.
9. Settings and privacy
Here you can pick which Gemma model to chat with, set your default currency and reminder timing, and switch on a demo mode that shrinks every wait to a few seconds. There's also a privacy table listing every possible network call, and a Delete all local data button.
Code
souptik4572
/
sloth-spend
SlothSpend audits a 12-month bank-statement CSV entirely on the user's machine, finds recurring charges, price creep and duplicate charges, forecasts the next 30 days of subscription spend, lets the user interrogate the audit through a local chatbot.
SlothSpend
Slow and steady subscription audits β on your own computer.
Find the subscriptions you forgot, the prices that crept up and the charges that came twice with open models (Gemma + TabPFN) running locally. SlothSpend informs, never acts.
Website Β· Download Β· How it works Β· Privacy Β· Develop
Contents
- Why SlothSpend
- Features
- How it works
- Screenshots
- Install
- First run
- Using SlothSpend
- Privacy: where your data goes
- Architecture
- Performance
- Development
- Configuration
- Engine API
- Troubleshooting
- Roadmap
- Hacktoberfest notes
- License and attribution
Why SlothSpend
A year of bank transactions says where you live, what you eat and who you love. Cloud "subscription trackers" want that data β and often a login to your bank. SlothSpend takes a CSV export, keeps it in a local database, and runs every model on your own machine. After a one-time model download it makes no network calls at all.
The theme is slowβ¦
To run it from source you need Node 24, Python 3.11, uv and Ollama 0.20+ with any Gemma model:
git clone https://github.com/souptik4572/sloth-spend.git
cd sloth-spend
npm install
npm run engine:venv # builds engine/.venv from the pinned lockfile
npm run dev
Then click Demo data β Sample statement.
How I Built It
SlothSpend has two halves: an Electron + React desktop app and a Python FastAPI engine that runs next to it as a local sidecar process.
Electron (React 19, Tailwind v4, zustand)
β HTTP + server-sent events, 127.0.0.1 only, per-launch token
βΌ
Python engine (FastAPI, pandas, SQLite)
ingest β normalise (Gemma) β detectors β TabPFN score + forecast β summary (Gemma)
Ask Sloth agent + 8 tools Β· reminder scheduler
β 127.0.0.1:11434
βΌ
Ollama β Gemma 4
Electron picks a free port and a random token on every launch, then starts the engine. If Electron crashes, the engine notices its stdin closing and shuts itself down, so you never end up with a stray Python process.
The interesting part is that two very different open models each do the job they're good at.
Gemma 4 (via Ollama): the language part
Gemma handles everything that involves words:
-
Cleaning up merchant names.
ACH DEBIT IRONTRAP FITNESS MEMB 7731becomes IronTrap Fitness with the category Fitness. Ollama handles one request at a time, so this runs in priority order under a time budget. Whatever doesn't fit in the budget falls back to rules and gets cleaned up in the background later. If you rename a merchant yourself, your name always wins. - The summary. The code writes the facts and Gemma only rephrases them into friendly English. The output is then checked: if Gemma drops or changes any number, the code-written version is used instead.
-
Ask Sloth. Gemma does tool calling over 8 typed tools (
get_merchant,find_duplicate_charges,spend_by_category,forecast_spend,score_merchant,run_readonly_sql,set_renewal_reminder,get_report_overview), with up to 4 tool rounds per answer. - Emails. Refund, cancellation and negotiation drafts start from a code-written template with the exact facts in it. Gemma makes them read naturally, and then a guard checks the result.
TabPFN v2: the numbers part
This was the bit that surprised me most. TabPFN is a tabular foundation model. You show it example rows when you call it (in-context learning) and it predicts straight away, with no training step.
- Model A (classifier): "Is this a subscription?" Every merchant becomes a row of features: median gap between charges, how regular that gap is, how stable the amount is, day-of-month spread, and so on. The context is a set of simulated billing histories that ship with the app, plus your own corrections, each repeated 3 times. That's why "Not a subscription" changes the score in ~2 seconds: there's no retraining, the new label is just part of the next call.
- Model B (regressor): "What will the next charge be?" It predicts the next/last price ratio as 19 quantiles. That's where the q10βq90 range on the forecast card comes from, and it's what Ask Sloth's live what-if forecast runs on.
TabPFN refuses more than 1,000 training rows on CPU by default (I found that out the hard way), so the context is capped at 950 rows.
Making it trustworthy
- Deterministic detectors handle duplicates, price creep, overlaps and possible annual plans. I don't want a language model deciding whether $49.99 on Jul 5 and Jul 6 counts as a double charge.
- Reminders are rows in SQLite plus a single loop that ticks at startup and every 30 seconds. There's no workflow engine. Every state change is compare-and-set, so a click and a tick can't overwrite each other.
-
An egress test.
engine/tests/test_egress.pyruns the whole app (audit, every chat tool, drafts, a full reminder cycle, Delete all local data) with every non-loopback network connection refused, and fails if anything tries to reach the internet.
Numbers
Measured on the packaged macOS build (Apple Silicon, TabPFN v2, gemma4:latest):
| Check | Measured |
|---|---|
| Launch β window shown | 0.18 s |
| Launch β engine healthy | 7.1 s |
| Full audit of the sample (412 rows, 12 months) | 27.7 s |
| Re-score after a correction | 2.4 s |
| Engine health check p95 during an audit | 19 ms |
One real bug from the week: launch first took 22.5 s. It turned out that reading the saved Prior Labs key synchronously from the macOS keychain made the OS verify the whole app bundle (921 MB including the Python engine) before handing the key over, and it froze the window while doing so. Switching to safeStorage.decryptStringAsync brought the window to 0.18 s and the engine to 7.1 s.
Why Does Open Innovation Matter?
For this app, open models aren't a nice extra. Without them the app doesn't really work.
A year of bank transactions shows where you live, what you eat, who you see and what you're struggling with. Most "subscription tracker" apps want that data on their servers, and many want your bank login too. I didn't want to build another one of those, and honestly I wouldn't use one.
Open weights changed what I could build:
-
Nothing leaves the machine. Gemma runs through Ollama on
127.0.0.1and TabPFN runs on local PyTorch. After the one-time model download, SlothSpend makes no network calls, and there's a test that proves it. A closed API would mean sending every transaction to someone else's server. - It costs nothing to run. No API key, no per-token bill, no usage limit. An app whose job is saving you money shouldn't charge you every time it reads your statement.
- Swapping models is easy. Any Gemma you already have in Ollama works, and Settings lets you pick one. TabPFN goes v2 (open weights, the default) β 3.5 (if you add a free Prior Labs key) β a labelled heuristic (if no model is available). Being able to fall back like this only works because I control the model layer.
- TabPFN made the "learns from you" part possible. Because it learns in context, a user correction becomes part of the prompt rather than a training job, so the app adapts to you, on your own device, in two seconds. I couldn't have built that loop with a hosted black box.
- It's auditable. The engine is open source, the models are open, and the privacy table in Settings lists every byte that could ever leave your computer. You don't have to take my word for any of it.
The Lazy Payoff
On the 12-month sample statement, a single 28-second audit found:
- $87.46/month of potential savings across 4 "zombie" subscriptions, about $1,050 a year,
- a $49.99 double charge worth disputing,
- two price rises: a gym at +25% and a music app at +9.1%,
- two streaming services doing the same job.
Finding that by hand means reading 412 rows, matching messy merchant names across 12 months, and noticing a $10 price change in the middle of the year. For me that's an evening I'd keep putting off. Then there's the follow-up, which is where most of the money actually gets lost: remembering a renewal date, checking next month's statement, and writing the refund email. SlothSpend turns all of that into one import, one click on "I cancelled it", and a second import a month later.
The sloth does the slow and steady part. You just stay lazy.
Prize Categories
- Best Use of Gemma: Gemma 4 runs locally through Ollama and does every language task in the app: merchant normalisation, the summary (with a number-preservation check), the tool-calling Ask Sloth agent, and refund/cancellation/negotiation drafts.
- Best Use of TabPFN: TabPFN v2 is the scoring and forecasting core. The classifier decides what recurs, using simulated priors plus user corrections as in-context examples, and the quantile regressor powers the q10βq90 forecast and the live "what if I cancel X" tool. Built with PriorLabs-TabPFN.
AI disclosure: I built SlothSpend with help from Claude Code (Claude Opus) as a coding assistant, and this post was drafted and edited by me. The product decisions, the "inform, never act" rules and the testing are mine.
SlothSpend is MIT-licensed. TabPFN v2 weights are under the Prior Labs License 1.1; Gemma is used under Google's Gemma terms.










Top comments (0)