Skip to Content
FeaturesFinanceOverview

Finance Feature Guide

Comprehensive guide to the Finance module: accounts, transactions, budgets, savings goals, scheduled transactions, and the balance prediction engine.

Overview

The Finance module is the most complex feature in Oter. It provides full personal finance management — tracking income, expenses, and transfers across multiple accounts — plus a sophisticated balance prediction engine that projects future account balances using historical data, scheduled transactions, periodic spends, and configurable noise/scenarios. The module follows Clean Architecture on Android (three Gradle modules) and shares its HTTP client and models via the KMP shared layer.

Architecture

The finance feature spans five layers: Android presentation, Android domain, Android data, KMP shared services, and the Ktor server.

Data flows unidirectionally: UI intent → ViewModel → UseCase → Repository → DataSource → HTTP → Server.

ViewModel Structure

The presentation layer uses one ViewModel per domain concept, coordinated by a shared wrapper.

Each ViewModel is responsible for a single domain. FinanceActionsWrapper delegates actions to the right ViewModel. FinanceStateConverter merges all individual states into the single DesktopFinanceState consumed by the UI.

Data Models

Key Enums

TransactionType: INCOME, EXPENSE, TRANSFER

AccountType: CHECKING, SAVINGS, CREDIT_CARD, INVESTMENT, CASH, LOAN, OTHER

Category: 30+ values grouped as income (SALARY, INVESTMENT, FREELANCE, GIFT) and expense (FOOD, HOUSING, TRANSPORTATION, SHOPPING, HEALTH, SUBSCRIPTION, …)

CategoryVolatilityType (used by prediction engine):

TypeMeaningExample categories
FIXEDStable, predictableHousing, Bills, Subscription, Debt
SEMI_FIXEDMostly stable with some varianceGroceries, Transportation, Health
VARIABLEHighly unpredictableEntertainment, Shopping, Travel

API Endpoints

Accounts

See accounts.md for balance derivation, soft-delete, client mapping, and tests.

MethodPathDescription
GET/finance/accountsList all accounts
GET/finance/accounts/total-balanceSum balance across all accounts ({ "totalBalance": number }) — registered before /{id}
GET/finance/accounts/{id}Get account by ID
POST/finance/accountsCreate account
PATCH/finance/accounts/{id}Update account
DELETE/finance/accounts/{id}Soft-delete account

Transactions

MethodPathDescription
GET/finance/transactionsList transactions (paginated, filterable)
GET/finance/transactions/{id}Get transaction by ID
POST/finance/transactionsCreate transaction
PATCH/finance/transactions/{id}Update transaction
DELETE/finance/transactions/{id}Delete transaction
GET/finance/transactions/byAccount/{accountId}Transactions for an account
POST/finance/transactions/importImport from text/CSV
POST/finance/transactions/import/previewPreview import before confirming
POST/finance/transactions/balance-predictionRun balance prediction
POST/finance/transactions/balance-prediction/affordabilityWhat-if purchase affordability check
GET/finance/balance-scenariosList saved balance scenarios
POST/finance/balance-scenariosCreate balance scenario
PUT/finance/balance-scenarios/{id}Update balance scenario
DELETE/finance/balance-scenarios/{id}Delete balance scenario (soft)

Budgets

MethodPathDescription
GET/finance/budgetsList budgets (paginated)
GET/finance/budgets/withProgressBudgets with spending progress
GET/finance/budgets/byDateRangeBudgets in a date range
GET/finance/budgets/{id}/progressProgress for a budget
POST/finance/budgetsCreate budget
PATCH/finance/budgets/{id}Update budget
DELETE/finance/budgets/{id}Delete budget
GET/finance/budgets/{id}/transactionsTransactions counted against budget
GET/finance/budgets/unbudgeted/transactionsTransactions with no budget
POST/finance/budgets/unbudgeted/categorizeAuto-categorize unbudgeted
POST/finance/budgets/transactions/categorizeAuto-categorize all transactions

Savings Goals

MethodPathDescription
GET/finance/savings-goalsList goals
GET/finance/savings-goals/{id}/progressGoal progress
GET/finance/savings-goals/{id}/remainingRemaining amount
POST/finance/savings-goalsCreate goal
PATCH/finance/savings-goals/{id}Update goal
DELETE/finance/savings-goals/{id}Delete goal
POST/finance/savings-goals/{id}/progressUpdate goal progress

Scheduled Transactions

MethodPathDescription
GET/finance/scheduled-transactionsList (paginated)
GET/finance/scheduled-transactions/{id}Get by ID
POST/finance/scheduled-transactionsCreate
PATCH/finance/scheduled-transactions/{id}Update
DELETE/finance/scheduled-transactions/{id}Delete
GET/finance/scheduled-transactions/byAccount/{accountId}By account

Category Keywords & Analytics

MethodPathDescription
GET/finance/category-keywordsList all keywords
GET/finance/category-keywords/by-category/{category}Keywords for a category
POST/finance/category-keywordsAdd keyword
PATCH/finance/category-keywords/{id}Update keyword
DELETE/finance/category-keywords/{id}Delete keyword
GET/finance/statisticsFinancial statistics

Balance Prediction Engine

The prediction engine is the core differentiator of the Finance module. It generates time-series balance projections across three scenarios.

Inputs and Scenarios

Each prediction point in the response represents one date and includes its balance, individual transactions, and per-scenario balances for that date.

Scenario lab (what-if)

Persisted balance scenarios and session inline adjustments layer on top of the core engine. See balance-prediction-scenario-lab.md for adjustment types, affordability verdicts, desktop UI, and platform coverage.

CapabilitySummary
Saved scenariosCRUD via /finance/balance-scenarios; adjustments stored as JSON
Inline adjustmentsNEW_TRANSACTION, MODIFY_CATEGORY, MODIFY_SCHEDULED in BalancePredictionFilters.inlineAdjustments
Baseline comparisoncompareToBaseline returns baselinePoints without scenario/inline layer
Budget impactPer-budget and unbudgeted bucket: baseline vs scenario spend + verdict
AffordabilityPOST .../affordability — one-time expense what-if with verdict precedence

Desktop UI (composeApp/.../balanceprediction/): scenarios drawer, adjustment dialog, affordability dialog, budget impact on point details, baseline dashed line on chart. Category fields use CategoryDropdown (enum-backed).

Safety Features

FeatureDescription
safetyFloorAlert when projected balance drops below this threshold
overdraftThresholdWarn when balance goes negative (default 0.0)
dangerEventsList of dates/amounts where thresholds are breached
safeToSpendTodayMaximum spendable today without triggering safety floor
goalAnalysisProbability of reaching a savings goal; required extra savings per week/month

Pagination

Prediction results are paginated (default 100 points/page) to handle long date ranges efficiently. Clients specify page and pageSize in BalancePredictionFilters.

Client Behavior

For long date ranges, the UI may request additional pages by incrementing filters.page.

Key Files

FilePurpose
finance/finance_domain/.../repository/FinanceRepository.ktDomain repository interface — the contract for all finance operations
finance/finance_data/.../repository/FinanceRepositoryImpl.ktRepository implementation, delegates to data source
finance/finance_data/.../datasource/FinanceRemoteDataSource.ktHTTP data source with null-safe response validation
finance/finance_presentation/.../viewmodel/TransactionViewModel.ktTransaction CRUD, filtering, import
finance/finance_presentation/.../viewmodel/AccountViewModel.ktAccount management
finance/finance_presentation/.../viewmodel/BudgetViewModel.ktBudget tracking and auto-categorization
finance/finance_presentation/.../viewmodel/BalancePredictionViewModel.ktPrediction engine state, scenario selection
finance/finance_presentation/.../viewmodel/FinanceActionsWrapper.ktUnified actions interface delegating to focused ViewModels
finance/finance_presentation/converter/FinanceStateConverter.ktMerges all ViewModel states into DesktopFinanceState
server/.../routing/FinanceRouting.ktAll 40+ HTTP endpoints with auth and pagination
server/.../models/finance/FinanceDTOs.ktRequest/response DTOs
shared/.../services/finance/FinanceService.ktKMP Ktor HTTP client for all finance API calls
shared/.../models/finance/BalancePrediction.ktBalancePredictionFilters, BalancePredictionResponse, PeriodicSpend, DangerEvent
shared/.../models/finance/ScenarioAdjustment.ktScenarioAdjustment, BalanceScenario, BudgetImpact, AffordabilityRequest / Result
server/.../service/ScenarioAdjustmentMerger.ktMerges scenarios + inline + periodic spends before engine
server/.../service/BudgetImpactService.ktPer-budget scenario vs baseline spend
server/.../service/BalancePredictionAffordabilityService.ktAffordability what-if endpoint
shared/.../models/finance/Category.ktCategory enum + CategoryVolatilityType classification

Testing

  • Unit tests should cover BalancePredictionViewModel scenario switching and state transitions.
  • Domain tests should cover use case edge cases: empty account list, zero balance, date range spanning month boundaries.
  • Repository tests should verify Result.failure propagation when the HTTP layer returns an error.
  • Server tests should cover the prediction engine with mocked scheduled/historical transactions; verify danger events are emitted when safetyFloor is breached.

Troubleshooting

Prediction returns empty points

  • Verify fromDate < toDate in BalancePredictionFilters.
  • Check that at least one accountId is valid and belongs to the authenticated user.
  • Confirm pageSize > 0 and page >= 0.

Budget progress not updating

  • Ensure the transaction category matches the budget’s category exactly.
  • Check startDate/endDate overlap between the transaction date and the budget period.

Transaction import assigns wrong categories

  • Check category-keywords for the relevant category — add or update keywords as needed.
  • Use the preview endpoint before confirming an import to inspect auto-categorization results.

Scheduled transactions not appearing in prediction

  • Verify includeScheduledTransactions: true in BalancePredictionFilters.
  • Confirm the scheduled transaction’s startDate falls within the prediction date range.