Forecasts
GET /{user_id}/forecasts builds a cashflow forecast from the user’s last 93 days of transactions on every request; nothing is cached. The response schema (Forecasts) is in API Specification.
Request Flow
-
Read the user’s
SAVINGS_MONTHrows for the current and previous two months (missing rows are not an error). -
Fetch active items with accounts from the Transactions Service; sum
available(elsecurrent) balances into checking and savings totals per account. Failure returns500. -
Fetch 93 days of transactions and run Recurring Detection.
-
Keep only expenses and income on currently active accounts.
-
Build the response: Cash Runway, Savings Progress, and Cash Calendar, each gated by an API env var (
INCLUDE_CASH_RUNWAY,INCLUDE_SAVINGS_PROGRESS,INCLUDE_CASH_CALENDAR, alltrue), thenincome,recurring, andritual(always empty).
Source: pkg/api/forecasts.go, pkg/api/forecasts_builder.go
Recurring Detection
Transactions are grouped per account, then split into debits (expenses) and credits (income) before name grouping.
| Step | Rule |
|---|---|
Group by name |
Cleaned transaction name; expense groups are further split by amount variance, income groups are not |
Minimum size |
More than 2 transactions per group, after merging closely dated transactions (retries) into one |
Cadence |
Average gap in days between consecutive transactions; every gap must fall within 0.8x to 1.2x the average (0.4x to 1.6x when retries were merged), else the group is not recurring |
Frequency and next date |
Average gap rounded and mapped to a frequency name ( |
Income check |
Credit groups must also pass a Plaid-category income check |
Source: pkg/api/fm_forecasts.go
Cash Runway
Starting from today’s checking and savings totals, walk forward one day at a time, subtracting each recurring expense on its due date and rescheduling it by its frequency, until either total reaches zero or two years pass. cash_runway is the walk reduced to 12 evenly spaced steps (date, checking, savings); cash_runway_days is the number of days covered. Income is not added. Source: pkg/api/runway.go
Cash Calendar
cash_calendar lists days with activity: past days in the current month (from the detected groups' transactions) and projected days up to six months out (each expense and income projected forward by its frequency). For projected days, income is added and expenses subtracted from a copy of today’s per-account balances; a day on which an account goes negative is marked is_overdraft with the affected overdraft_accounts. Source: pkg/api/calendar.go
Savings Progress
average_income and average_expenses are monthly totals of the detected upcoming income and expenses (frequency converted to occurrences per month). potential_savings is six monthly steps: one actual step per stored SAVINGS_MONTH row (up to three), each with potential equal to the previous actual plus max(income - expenses, 0), then projected steps one month apart until six exist. With no rows the first step is today with actual: 0. Source: pkg/api/savings_progress.go
Savings Snapshot
site-insight-savings-snapshot consumes user_new_txns_batch_completed (payload: the bare user ID) tapped from EventBridge — see Events. It lists the user’s active items with accounts, keeps checking and savings subtypes, sums available (else current) per account, and writes the SAVINGS_MONTH#<YYYY-MM> row (schema), overwriting the current month. A user with no checking or savings accounts gets no row. Metric insight.savings.snapshot tagged result:saved|no_accounts|failed. Source: pkg/savings-snapshot/handler.go