DEV Community

Cover image for Spend Log: my family's credit card spending, sorted while I do nothing πŸ¦₯
Drishti Peshwani
Drishti Peshwani

Posted on

Spend Log: my family's credit card spending, sorted while I do nothing πŸ¦₯

Hacktoberfest Open-Source AI Challenge Week 3: Slow and Steady Submission πŸ¦₯

This is a submission for the Hacktoberfest Open-Source AI Challenge Week 3: Slow and Steady

What I Built

Every month, a pile of password-protected PDFs lands across our family's Gmail inboxes: my credit card statements, my mom's and my dad's. Each bank picks its own password rule ("first four letters of your name in capitals, then your date and month of birth", "your birth year and the last four digits of your card"), and each statement has its own layout. To answer a simple question like "what did we spend on food delivery this month?" I'd have to open every email, work out every password, read every PDF, and add things up in a spreadsheet.

So I mostly didn't. That's the honest answer, and it's the problem.

Spend Log is a local-first app that does all of that for you. You click Fetch new statements, and it:

  1. finds new statement emails in each family member's Gmail (read-only),
  2. reads the bank's password hint in the email and works out the password from that person's name and date of birth,
  3. has Gemma, running on your laptop, read every transaction out of the PDF,
  4. sorts each one into a category (Groceries, Food delivery, Fuel, Bills…) and tags it with the card and the person,
  5. has TabPFN forecast what each card's next bill will look like, with Gemma explaining why a category is going up and what to do about it.

Then you get one dashboard for the whole family: who spent what, where, on which card, and what's coming next month.

Nothing leaves the laptop. Not the statements, not the passwords, not the transactions. There's no server and no account, and the model has no API key.

Why this, when there are already apps for it?

I looked at what's already out there before building:

What exists What's missing
Card-management apps in India read your statement emails to show spends and due dates. They're built around one person's cards, and you hand your inbox to a company. That makes people uneasy: I found forum threads where people auto-forward statements to a throwaway inbox just to limit what such an app can see.
Bank apps show transactions for that bank's card. Seven cards means seven apps, each with its own categories, and nothing ties them together by person.
Expense trackers and spreadsheets can do anything. They need you to type or upload everything, which is exactly the work nobody keeps doing.
India's Account Aggregator and BBPS bill-fetch rails. They're for licensed businesses. BBPS gives only the amount due, not line items, and AA mostly covers bank accounts, not card statements.

The gap: there's no easy way to see spending per person, per category, across a whole family's cards, without giving your inbox to someone else. In a lot of Indian households one person pays several people's cards (their own, their parents', a spouse's), and that's exactly the view they need.

Why it fits "Slow and Steady"

This week's theme is rewarding laziness, and Spend Log is built around one lazy idea: the statements already come to you, so you shouldn't have to do anything with them.

  • No downloading, no uploading, no typing passwords. The password hint is already in the email; Gemma reads it.
  • No per-bank setup. Gemma reads any statement layout, instead of a separate parser for every bank.
  • No "remember to run the forecast". It updates on its own whenever new statements are read, and stays put when nothing's new.
  • No reading tables of numbers. Each chart says "β–² β‚Ή4.1K more than the 30 days before" in plain words, so my parents can read it too.

Click once, go make coffee, come back to a sorted month.

Demo

The demo uses my family’s real names (me, Mom and Dad), but every statement is made up: 7 cards from 7 fictional banks, with six months of statements each. That’s 42 password-protected PDFs, emailed to three real Gmail inboxes and fetched by the app exactly as real statements would be. No real card data appears anywhere in the video or screenshots.

Screenshots

Overview

Charts

Forecast View

Code

GitHub logo drishtipeshwani / spend-log

Your family's credit card statements, read from Gmail and sorted on your laptop with Gemma and TabPFN. Nothing leaves the machine.

Spend Log πŸ¦₯

Your family's credit card spends, sorted on your laptop while you do nothing.

Overview

Spend Log reads credit card statements from each family member's Gmail and shows the whole household where its money goes: by person, category, card and month, plus a forecast of next month's bills.

Click Fetch new statements and it:

  1. finds new statement emails in every connected inbox (read-only),
  2. works out each PDF's password: Gemma reads the bank's hint in the email, and Python fills it in from the person's name and date of birth,
  3. has Gemma read every transaction from the statement, whatever the bank's layout, while Python checks each amount and total against the PDF,
  4. categorizes each merchant (once, remembered, and correctable in the dashboard),
  5. has TabPFN forecast each card's next statement, with Gemma explaining the biggest rises.

Everything runs locally. Gemma runs through Ollama, TabPFN runs in local mode, and data…

How I Built It

        Gmail (each family member, read-only)
                       β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ your laptop ────────────────────┐
β”‚                                                      β”‚
β”‚  Fetch ─► new statement emails since the last fetch  β”‚
β”‚    β”‚                                                 β”‚
β”‚  Unlock ─► Gemma reads the password hint,            β”‚
β”‚    β”‚       Python fills in name / date of birth      β”‚
β”‚    β”‚                                                 β”‚
β”‚  Extract ─► Gemma reads transactions page by page,   β”‚
β”‚    β”‚        Python checks every amount and total     β”‚
β”‚    β”‚                                                 β”‚
β”‚  Categorize ─► Gemma, once per new merchant          β”‚
β”‚    β”‚                                                 β”‚
β”‚  Forecast ─► TabPFN predicts next bills;             β”‚
β”‚    β”‚         Gemma explains the biggest rises        β”‚
β”‚    β–Ό                                                 β”‚
β”‚  CSV files ─► Streamlit dashboard                    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
Enter fullscreen mode Exit fullscreen mode

Two open models do the heavy lifting, both on my laptop. Gemma 4 E4B, through Ollama, does the reading: it works out passwords from the bank's hint, pulls transactions out of any statement layout, categorizes merchants and explains the forecast. TabPFN does the predicting: next month's bill for every card. Python checks everything the models return against the PDFs. How each model is used, and what I liked about it, is under Prize Categories below.

Tech stack

Part What I used
Local model Gemma 4 E4B via Ollama (structured JSON outputs)
Forecasting TabPFN (local mode, telemetry off)
Email Gmail API (gmail.readonly), Google OAuth for desktop apps
PDFs pikepdf (unlock), pdfplumber (layout-preserving text)
Data pandas, CSV files
Dashboard Streamlit, Altair
Language Python

The dashboard

Built with Streamlit and Altair, in a calm "sloth moss" palette.

  • Your whole family, any number of inboxes. Add each person once (the name on their card and their date of birth), and connect as many Gmail accounts as you like. Statements are matched to whoever's details unlock them, so it doesn't matter whose inbox they land in.
  • Fetch whenever you want. One click checks every connected inbox for statements that arrived since the last fetch, and reads only those, with a live checklist as it goes. Nothing new means nothing changes.
  • Filters that follow you. Pick one person for a solo view or any group, choose a period (30 days, 2, 3 or 6 months, or custom dates), and turn individual cards on or off. Every chart and number updates to match.
  • Overview: what you spent and how it compares with the period before, where it went by category, week or month, card and person, with a plain note on what grew or shrank. Click any category or card to drill down to its statements and transactions, and fix a category once for every transaction from that merchant.
  • Forecast: next month's likely bills for the family, each person and each card, the categories most of it will go to, and Where to cut back cards explaining the biggest rises.

Everything is stored as plain CSV: easy to open, easy to fix by hand, and it goes straight into pandas for TabPFN.

Privacy, by design

  • Gmail access is gmail.readonly, with one token per family member, stored locally.
  • Names and dates of birth stay in a local file and are only used by Python to try passwords.
  • data/ and secrets/ are gitignored, and files are written readable by you only (on macOS and Linux).
  • Email bodies and card numbers are never logged.

Why Does Open Innovation Matter?

Credit card statements are about the most personal data there is: where you eat, what you buy, which doctor you saw, when you travelled. A closed API would have meant sending every one of those PDFs to someone else's server, plus the names and birth dates that unlock them. I wouldn't do that with my parents' statements, and I don't think anyone should have to.

Open models made a different design possible:

  • The data never moves. Gemma reads the statements where they already are. TabPFN learns from them on the same laptop. There's no "we take your privacy seriously" page to trust, because nothing is sent anywhere.
  • It costs nothing to run. No per-token bill for reading 42 statements, re-reading them when I fix a prompt, or forecasting every month.
  • It works offline. Once statements are fetched, everything else runs with Wi-Fi off.
  • The model is swappable. One setting (SPEND_LOG_MODEL) moves to a bigger Gemma, or to another model entirely, without touching the code.
  • Anyone can extend it. Because a model reads the statements instead of hand-written parsers, supporting a new bank is mostly "it already works". When it doesn't, the fix is a prompt or a check anyone can read and improve, not a black box.

Same idea as the card apps, without handing your inbox to a company.

My Agent Session

I built Spend Log with Claude Code as a pair programmer, from the first Gmail call to the last chart. I made the product calls: which tools to use; having Gemma read the password hints as well as the transactions, so nobody has to type a password; plain CSV instead of a database; TabPFN for forecasting rather than anomaly detection; how fetching and forecasting should work; and screens and charts designed so my parents could read them too.

I tested the UI constantly, and many of the best changes came from simply using it: fixing how fetching behaved, catching merchants that were labelled wrong, cutting views that only repeated what the charts already showed, and swapping charts my parents found confusing for plain notes like β€œβ–² β‚Ή4.1K more”. Claude wrote most of the code and tests, and validated its own changes after each iteration; that back-and-forth is what got it to something my family can actually use.

For the demo, I planned how it would work: what data we needed, how to spread transactions across the family, which recurring payments to include, and realistic price ranges for each statement. Claude then recorded the demo video by driving the dashboard with Playwright, with narration from a voice built into my Mac.

The Lazy Payoff

Before: for each statement, open the email, find the password rule, type it, open the PDF, and copy amounts into a sheet. That's about 10 minutes per statement if you're quick, so an hour or more a month across all our cards, plus the actual adding up. In practice, I just didn't do it.

After: one click. A statement takes about a minute for Gemma to read, unattended, and the forecast updates itself. My effort is the click.

I first ran it on my own real statements this week, which is where the password-hint idea and the "let Gemma read it" decision came from. Then I planned a demo family to show it across three inboxes and seven cards.

Prize Categories

I'm entering Overall, Best Use of Gemma and Best Use of TabPFN.

Best Use of Gemma

Gemma 4 E4B runs locally through Ollama and does all the reading in Spend Log. Every call returns JSON that matches a schema, and Python checks what comes back.

  • Password hints. Gemma turns the bank's sentence ("first four letters of your name in capitals, then DDMM") into a recipe, and Python fills it in from each family member's details. Whoever's details open the PDF is the cardholder. Gemma never sees anyone's name or date of birth.
  • Transactions from any bank. We started with a parser for one bank's layout, but every bank would need its own, so I decided Gemma should read the statements instead. Python checks every amount, date and total against the PDF and retries any page that doesn't add up. On a real statement, 20 of 20 rows matched a hand-written parser, and all 42 demo statements add up to their printed totals.
  • Categories. Each new merchant is named and categorized once, then remembered. Fix one in the dashboard and every transaction from that merchant follows.
  • Explaining the forecast. For the biggest rises, Gemma writes a why and a try from numbers Python computes. When TabPFN expected an β‚Ή859 Airtel charge from three statements ago to come back, Gemma explained it as a prepaid recharge falling due again. Nobody told either model about prepaid plans.

Best Use of TabPFN

TabPFN predicts each card's next statement. Six statements per card is too little for a classic forecasting model, so every card's history becomes one table: a row per card, statement and category, with recent spend and how often that category shows up. TabPFN learns from all of it at once and returns a likely amount, with a range, for every category.

What I liked: it doesn't chase one-offs (a β‚Ή32,500 jewellery purchase didn't inflate next month's forecast), and it runs on its own whenever a fetch brings new statements, locally and offline, feeding the Forecast page and Gemma's cut-back tips.

What's next

  • Monthly auto-fetch, as a durable workflow (Temporal is a natural fit), so even the one click goes away.
  • Longer-lived Gmail access. Google expires test-mode tokens weekly. Verifying the app, or a single forwarded inbox, would fix that.
  • Ask questions in plain English ("How much did Mom spend on groceries in September?"), answered locally.
  • A fully on-device mobile version, so the phone that receives the statements can read them too.

Top comments (0)