
SpendPulse
On-device insights into what your spending says about you
SpendPulse is an on-device spending-insights app that analyzes transactions to surface category, time-of-day, and mood correlations. All financial data is stored locally with SwiftData and never leaves the device. Insights are produced by statistical, deterministic engines (StatisticalInsightGenerator, KeywordCategoryClassifier, SubscriptionAuditor) with no LLM and no network, then visualized with Swift Charts across category, weekday, hour, and mood breakdowns plus a recurring/duplicate subscription auditor. Pro features unlock via a StoreKit 2 subscription, and a Face ID app lock protects the data.
Conventional budgeting apps ship data to servers, raising privacy concerns, and only show totals without explaining the 'why' behind spending. SpendPulse keeps data entirely on-device while analyzing mood and time patterns to deliver non-judgmental insights.
App preview
What it does
Mood-Spend Correlation
Tag each transaction with one of five moods (happy, neutral, stressed, tired, celebratory); PatternAnalyzer.moodAverages computes per-mood average spend. An insight surfaces only when the highest-to-lowest mood ratio is 1.5x or greater, filtering out noise.
Time & Weekday Patterns
Buckets weekday spend totals and a 24-hour transaction distribution to find peak days and times. Statistical thresholds gate the output: a weekday peak must exceed 1.8x the non-zero day average, and an hourly peak needs 4+ total transactions with one slot holding over 30% of them.
Subscription Auditor
SubscriptionAuditor groups transactions by merchant, currency, and amount, then auto-classifies a group as recurring when the average gap falls into weekly (6–10 days), monthly (26–35 days), or yearly (350–380 days) tolerances. Same-price, same-currency subscriptions across different merchants are bucketed separately to flag duplicate spend.
Keyword Auto-Categorization
KeywordCategoryClassifier lowercases the memo and matches a bilingual Korean/English keyword dictionary (e.g. Starbucks, Netflix, taxi) by longest-match to assign one of 13 spend categories. Overlapping rules are safe to add, and because it is deterministic it runs offline and is fully testable.
Weekly Deterministic Insights
StatisticalInsightGenerator derives a dominant category (50%+), mood skew, peak weekday, peak hour, and a weekly highlight from a week of transactions as pure functions, capping at five cards. Every rule runs on numbers alone — no LLM, no network — and doubles as the test oracle.
Native Chart Dashboard
Visualizes a category donut, weekly trend, mood heatmap, and weekday/time patterns with Swift Charts. The 13 categories map to a color-blind-safe palette that avoids red for spending, keeping the dashboard readable and accessible.
StoreKit 2 Subscription
A StoreKitPurchasing actor buys monthly/annual Pro plans through the real payment sheet, verifying VerificationResult, checking entitlements via currentEntitlements, and restoring with AppStore.sync(). It replaces the prior flag-flipping fake purchase path that triggered the 2.1(b) rejection with genuine StoreKit calls.
Face ID Privacy Lock
A LocalAuthentication-backed LocalAuthBiometricService gates app entry with Face ID to protect spending data. It checks biometric availability and distinguishes user/system cancellation from real errors, with a mock implementation used to test the auth flow.
How it's built
Language
UI
Data
Payments
Privacy & OS
Testing
Tooling
MVVM + Clean Architecture separating Domain/Data/Presentation, with all analysis built as deterministic pure functions so the app runs 100% on-device with no LLM and no server.
- 1
Repository pattern: the Domain layer depends only on TransactionRepository/GoalRepository protocols and the CategoryClassifier/InsightGenerator abstractions, isolating the SwiftData implementation (SwiftDataTransactionRepository) in the Data layer so storage is swappable and mockable.
- 2
Deterministic insight engine: StatisticalInsightGenerator, KeywordCategoryClassifier, SubscriptionAuditor, and PatternAnalyzer are I/O-free pure functions that run without LLM or network, and the same code doubles as the test oracle.
- 3
Immutable domain model: Transaction is a value type with only let properties, and its validated(...) factory rejects non-positive amounts, empty currencies, and future dates to block invalid data at the boundary.
- 4
StoreKit 2 actor design: StoreKitPurchasing is an actor that caches and reuses loaded Products (avoiding a refetch failure after the price is shown), verifies VerificationResult, checks entitlements via currentEntitlements, and syncs state through the Transaction.updates stream.
- 5
Reactive subscription state: an @Observable SubscriptionStore takes an injected payment engine, subscribes to the entitlementUpdates() stream, and refreshes currentTier on entitlement changes so the SwiftUI paywall and gated UI update automatically.
- 6
On-device privacy: financial data lives only in SwiftData (@Model TransactionModel/GoalModel) with no server dependency, protected further by a LocalAuthentication-based Face ID lock.
- 7
Siri integration: App Intents (LogTransactionIntent, TodaySpendingIntent) plus an AppShortcutsProvider let users log a transaction and check today's spending from Siri and Shortcuts.
146
Test cases (@Test)
26
Test suites (@Suite)
13
Spend categories
5
Mood tags
6
Supported currencies
100% on-device
Data storage



