Case study · iOSPrivate build, in daily use
Budget Tracker: money tracking that fills itself in.
A personal finance app built for how money works in India. Bank SMS alerts become transactions automatically, accounts, cards, loans and budgets stay in sync, and an on-device AI assistant answers questions about your spending without being able to make up a number.
// A model-extracted card offer is kept only if // every figure in it appears in the source text. static func isGrounded(_ offer: ExtractedOffer, in source: String) -> Bool { let figures = title.matches(of: /\d+(?:\.\d+)?/) guard figures.allSatisfy(haystack.contains) else { return false } ... }
Counted from the repository in October 2026.
The problem
Expense apps fail at the first step: entering expenses.
Most budgeting apps assume you'll type in every purchase, or they connect to your bank through an aggregator, which means handing your financial data to a third party. In India, though, every UPI payment, card swipe and transfer already produces an SMS from the bank. The data is on the phone. It just isn't structured.
Budget Tracker turns those messages into a ledger automatically, on the device, and builds budgets, loans, card bills and insights on top, with no account, no server and no aggregator.
Rules I set
- Financial data never leaves the phone.
- The AI must never invent an amount, date, merchant or offer.
- Card scans keep only the last four digits: never the full number, CVV or photo.
- Logging an SMS must work in the background, within Siri's time limit.
What it does
- Bank accounts, credit and debit cards, cash and wallets, with transfers between them.
- Category budgets, savings goals, and loans with repayment schedules and reminders.
- Credit cards with bill-date and due-date tracking.
- Spending widgets on the home screen and lock screen.
- Face ID app lock.
How it works
From a bank SMS to a categorised transaction, hands-free.
-
Capture
An iOS Shortcuts automation fires when a message notification arrives and passes the text to the app's Log Bank Message App Intent, which runs in the background without opening the app. It can also attach the current location.
-
Parse
A regex parser reads debit, credit, UPI and card messages: amount, account or card last four digits, merchant and reference number. For free-text entries, Apple's on-device Foundation Models extract a structured transaction. That call is capped at 6 seconds because Siri only allows an intent a few seconds, and it falls back to the regex parser.
-
De-duplicate and match
The same payment often triggers two messages. Duplicates are caught by reference number within a three-day window, or by amount and direction within three hours. A debit in one of your accounts and a matching credit in another are linked as a transfer, not counted as spending.
-
Update everything
A balance ledger keeps account balances consistent with transactions, and the widget data is refreshed on every save so the home-screen totals are always current.
On-device AI
An assistant that answers from your data, or not at all.
Everything runs on Apple's on-device Foundation Models: no API key, no cost per query, and nothing sent off the phone. The hard part was making a small model trustworthy with money.
The chat assistant can't answer from memory. It must call one of seven tools (spending summary, transaction search, accounts, budgets and bills, subscriptions, card offers, goals) for every figure.
The monthly "money story" is checked so that every number in it appears in the computed facts, or the story is rejected.
Card offers are read from bank offer messages and pages, and each is kept only if its figures and merchant appear in the source. There's no offers database for the model to invent from.
Vision OCR plus the on-device model turn a photo of a receipt into a structured transaction.
Subscription detection, unusual-spend alerts, a weekly digest, budget suggestions, a bills plan, and natural-language transaction search.
The home chart projects month-end spend from the 30-day everyday average plus scheduled EMIs, which are shown separately from everyday spending.
The pattern: the statistics are computed in plain Swift, and the model only phrases them. That split keeps the numbers exact while the words stay natural, the same idea behind the grounding checks in Dinapatra.
Engineering decisions
Trade-offs worth explaining.
Why SMS parsing instead of a bank aggregator?
Aggregators need credentials or consent flows and send your data to a third party. Bank SMS is already on the device, covers every bank, and keeps the whole app offline. The cost is parser maintenance, which the test suite exists to catch.
Why on-device models only?
Personal finance is the most sensitive data most people have. On-device inference means no API keys, no per-query cost, and nothing to leak. Because the model is small, the app doesn't trust it with arithmetic: Swift computes, and the model only writes the sentences.
How does the app survive a corrupted database?
The SwiftData store is shared through an App Group with the widget. If it can't be read, the app moves it aside and starts fresh instead of crashing on launch, which keeps the old store available for recovery.
Why did the Face ID lock loop, and how was it fixed?
iOS marks the app "active" again after the Face ID sheet, Control Center or a notification closes, so locking on every activation re-locked the app right after each unlock. The fix: the lock lives in its own window, and the app re-locks only after a real trip to the background longer than a chosen grace period. The lock also can't be turned on until one successful check, so you can't lock yourself out, and the behaviour is covered by tests.
Stack
Built with
- Swift
- SwiftUI
- SwiftData
- Foundation Models
- App Intents · Shortcuts
- WidgetKit
- Vision (OCR)
- Swift Charts
- Core Location · MapKit
- LocalAuthentication
- Swift Testing, 101 tests
Want a walkthrough?
It's a private build that I use every day, so the code isn't public. I can demo it or walk through the parsing and the AI guardrails.