How sarim2000/pennywiseai-tracker works
1. Repository shape
- This is a multi-module Kotlin/Swift monorepo, not a single app.
- The main products are: an Android app (app), a shared Kotlin Multiplatform library (shared), a bank/SMS parser engine (parser-core), a companion web/server project (pennywise-web), and an iOS app (iosApp).
2. Primary product: Android expense tracker
- The Android app is the most complete runtime: it owns SMS ingestion, local persistence, analytics, widgets, billing, onboarding, and the on-device AI chat experience.
- UI is Jetpack Compose with a screen/view-model structure and a bottom-navigation shell.
3. Android presentation boundary
- presentation/, ui/screens/, ui/components/, and navigation/ hold feature screens and shared composables.
- Feature areas map cleanly to architecture diagram nodes: home, transactions, analytics, accounts, budgets, subscriptions, rules, settings, chat, statement import, paywall, and unrecognized SMS handling.
- ViewModels sit between UI and repositories/use-cases; they are the main state boundary.
4. Android data layer and persistence
- The app uses Room as its local source of truth via PennyWiseDatabase, DAOs, entities, and schema snapshots under app/schemas.
- Repositories wrap DAOs and expose feature-specific data access for transactions, balances, budgets, cards, loans, subscriptions, exchange rates, merchant mappings, profiles, rules, chats, and unrecognized SMS.
- Backup/export support is built into the data layer with CSV export and a structured backup/restore format.
5. SMS ingestion pipeline
- Incoming bank messages enter through platform receivers/services such as SmsBroadcastReceiver, BankNotificationListenerService, and NotificationActionReceiver.
- Background workers (OptimizedSmsReaderWorker, BankNotificationRetryWorker) and managers (SmsScanManager, SmsTransactionProcessor, TransactionDeduplication) turn raw messages into normalized transaction records.
- This pipeline is the core data-flow into the local database and is one of the most important diagram paths.
6. Parsing and normalization
- Bank-specific parsing is split out into parser-core, a large standalone Kotlin library with one parser per bank/service and a registry/factory layer.
- The Android app also has statement-import parsing for PDFs (PdfParserFactory, PdfStatementParser, bank-specific PDF parsers, text extraction, and enrichment).
- Parsed transactions are mapped into app entities, deduplicated, enriched, and then persisted.
7. Rules, categorization, and enrichment
- Transaction automation is handled by a rules subsystem: RuleEngine, rule templates, repositories, and use cases for applying rules to past or new transactions.
- Enrichment layers include merchant/contact lookup, currency conversion, category mapping, subscription detection, and account-balance tracking.
- These are all local, deterministic transformations sitting between ingestion and storage.
8. On-device AI chat
- The app has a local AI assistant (ChatScreen, ChatViewModel, LlmRepository, LlmServiceImpl) intended to answer questions over the user’s own data.
- The README indicates the model runs fully on-device via MediaPipe/Qwen 2.5, which makes this a privacy-preserving local inference path rather than a cloud API flow.
- AiContextRepository suggests the assistant builds context from local finance data before generating answers.
9. Pro features and entitlement
- Free/pro gating is handled through EntitlementGate, FreeTierLimits, purchase launchers, and product definitions.
- The app is variant-aware: standard builds use Play Billing (PlayBillingGateway), while F-Droid builds substitute a no-op/local billing implementation (FdroidBillingGateway).
- This is a compile-time/runtime boundary that affects feature access rather than core data flow.
10. Widgets and background presentation
- Home-screen widgets for budgets and recent transactions are backed by their own receivers, data stores, and update workers.
- Widget data is derived from the same local repositories/database, but serialized into lightweight widget-specific models.
- This is a read-only projection layer, distinct from the main Compose UI.
11. Cross-platform shared module
- shared is the KMP layer that centralizes reusable data models, repositories, statement parsing, bootstrapping, and use cases across Android/iOS.
- It includes its own Room-based shared database abstraction, shared entities/DAOs, and domain use cases for transactions, budgets, accounts, rules, subscriptions, exchange rates, splits, and unrecognized SMS.
- This module is the natural architecture boundary for logic intended to survive platform differences.
12. iOS app
- The iOS app is a separate SwiftUI client with its own screens, view models, and theme/components.
- It mirrors the product surface of the Android app—home, accounts, analytics, budgets, transactions, settings, onboarding, subscriptions, and rules—but uses Apple-native UI and app lifecycle code.
- In an architecture diagram, it should be shown as a platform-specific front end consuming the shared domain/data layer rather than reusing Android UI code.
13. Companion web/server
- pennywise-web is a separate Kotlin server application with routing, templating, feedback handling, parsing documentation, and SMS report collection.
- It appears to support community-facing flows such as feedback submission, parse reporting, and roadmap/docs publishing, not the main consumer app runtime.
- It has its own deployment/runtime boundary and database migrations.
14. Operational and tooling boundaries
- The repo includes release automation, Fastlane Play metadata, Gradle build logic, tests, and extensive parser regression coverage.
- Database migration snapshots and parser tests are especially important because they stabilize the SMS parsing and local schema evolution that the app depends on.
Main entry points: MainViewModel.kt ViewModels · PennyWiseDatabase.kt Data layer · HomeViewModel.swift ViewModels · SharedDatabase.kt Shared database
Components 25
Android app
Primary Android runtime with UI, ingestion, persistence, widgets, billing, and AI assistant.
- Android app · Main Android expense-tracking runtime and product shell.
app/src/main/java/com/pennywiseai/tracker/MainActivity.kt - Compose UI · Bottom-nav shell, screens, shared composables, and presentation utilities.
app/src/main/java/com/pennywiseai/tracker/ui/MainScreen.kt - ViewModels · Screen state and orchestration layer between UI and data/domain code.
app/src/main/java/com/pennywiseai/tracker/ui/viewmodel/MainViewModel.kt - Data layer · Room database, repositories, backup/export, enrichment, and local models.
app/src/main/java/com/pennywiseai/tracker/data/database/PennyWiseDatabase.kt - SMS ingestion · Receivers, workers, and processors that normalize incoming bank messages.
app/src/main/java/com/pennywiseai/tracker/receiver/SmsBroadcastReceiver.kt - Statement import · PDF statement parsing, text extraction, enrichment, and import orchestration.
app/src/main/java/com/pennywiseai/tracker/data/statement/PdfParserFactory.kt - Rules engine · Deterministic transaction rules, templates, and historical rule application.
app/src/main/java/com/pennywiseai/tracker/domain/service/RuleEngine.kt - Local AI · On-device finance assistant context building and inference service.
app/src/main/java/com/pennywiseai/tracker/data/service/LlmServiceImpl.kt - Entitlements · Pro/free gating and variant-specific billing implementations.
app/src/main/java/com/pennywiseai/tracker/billing/EntitlementGate.kt - Widgets · Budget and recent-transaction projections backed by local repositories.
app/src/main/java/com/pennywiseai/tracker/widget/BudgetWidget.kt - Settings · Appearance, lock, and configuration screens plus preference state.
app/src/main/java/com/pennywiseai/tracker/ui/screens/settings/SettingsScreen.kt
Shared KMP
Cross-platform domain, persistence, and import logic reused by Android and iOS.
- Shared facade · KMP entrypoint exposing shared app logic across platforms.
shared/src/commonMain/kotlin/com/pennywiseai/shared/PennyWiseSharedFacade.kt - Shared database · Cross-platform Room-style database abstraction, entities, and DAOs.
shared/src/commonMain/kotlin/com/pennywiseai/shared/data/local/SharedDatabase.kt - Shared repos · Platform-neutral repositories for finance domains and unrecognized SMS.
shared/src/commonMain/kotlin/com/pennywiseai/shared/data/repository/RoomSharedTransactionRepository.kt - Shared use-cases · Cross-platform transaction, budget, account, rule, subscription, and import use-cases.
shared/src/commonMain/kotlin/com/pennywiseai/shared/domain/usecase/ManageBudgetUseCase.kt - Shared parsing · Shared statement parsers and PDF text extraction for KMP clients.
shared/src/commonMain/kotlin/com/pennywiseai/shared/data/statement/SharedStatementParserFactory.kt - Bootstrap · Default categories and shared data initialization.
shared/src/commonMain/kotlin/com/pennywiseai/shared/data/bootstrap/SharedDataInitializer.kt
iOS app
SwiftUI client layered over shared finance logic.
- iOS app · SwiftUI client for the shared expense-tracking product surface.
iosApp/iosApp/iosAppApp.swift - SwiftUI UI · Platform-native screens, components, and tab navigation.
iosApp/iosApp/App/MainTabView.swift - ViewModels · iOS state and screen orchestration layer.
iosApp/iosApp/ViewModels/HomeViewModel.swift
Parser core
Standalone Kotlin SMS parser engine for bank and payment messages.
- Parser registry · Central factory and registry for resolving the correct bank parser.
parser-core/src/main/kotlin/com/pennywiseai/parser/core/bank/BankParserFactory.kt - Bank parsers · Large set of bank and payment-specific SMS parsers.
parser-core/src/main/kotlin/com/pennywiseai/parser/core/bank/BankParser.kt
Web/server
Companion Kotlin server for feedback, parse reporting, and docs.
- Web server · Kotlin server runtime for feedback, parse reporting, and docs.
pennywise-web/server/src/main/kotlin/Application.kt - Feedback · Feedback submission, fingerprinting, and related routing/views.
pennywise-web/server/src/main/kotlin/feedback/FeedbackService.kt - SMS reports · SMS report collection and parse-report support service.
pennywise-web/server/src/main/kotlin/SmsReportService.kt
Connections 24
- Android app → Compose UI: hosts · Android activity and app shell present the Compose UI.
- Compose UI → ViewModels: drives · Screens delegate state and actions to ViewModels.
- ViewModels → Data layer: reads/writes · ViewModels coordinate repositories and persistence-backed flows.
- ViewModels → Rules engine: applies · Rule-related screens and flows use deterministic automation logic.
- ViewModels → Local AI: queries · Chat ViewModel builds assistant flows over local data.
- SMS ingestion → Parser registry: parses via · Inbound messages are normalized through parser-core.
- Parser registry → Bank parsers: selects · Registry resolves the appropriate bank or service parser.
- SMS ingestion → Data layer: persists · Normalized SMS events become local transactions and related records.
- Statement import → Parser registry: reuses logic · Statement import shares parsing concepts with the bank parser layer.
- Statement import → Data layer: imports to · Parsed statements are enriched and stored locally.
- Data layer → Widgets: feeds · Widgets derive lightweight projections from local repositories.
- Entitlements → ViewModels: gates · Entitlements constrain feature access in the UI flow.
- Entitlements → Android app: variants · Standard and F-Droid builds swap billing implementations.
- Shared facade → Shared database: bootstraps · Shared facade exposes the database-backed domain layer.
- Shared database → Shared repos: backs · Database DAOs and entities power shared repositories.
- Shared repos → Shared use-cases: serves · Repositories are consumed by shared domain use-cases.
- Shared parsing → Shared use-cases: imports · Statement parsing feeds shared import workflows.
- Bootstrap → Shared database: initializes · Default data seeds the shared local model.
- iOS app → SwiftUI UI: hosts · SwiftUI app entrypoint renders the native UI shell.
- SwiftUI UI → ViewModels: drives · Screens bind to platform ViewModels.
- ViewModels → Shared use-cases: uses · iOS delegates core finance behavior to the shared domain layer.
- ViewModels → Shared repos: reads/writes · iOS persists and reads via shared repositories.
- Web server → Feedback: routes · Server exposes feedback submission and related views.
- Web server → SMS reports: collects · Server handles SMS report intake and storage.