Forecasts & Payday
Overview
The prod-insight-api Lambda exposes two related capabilities via its REST API:
-
Forecasts (
GET /{user_id}/forecasts) — a combined view of a user’s recurring income, recurring expenses, and ritual expenses, assembled either from Pave-cached data or from real-time FloatMe-generated analysis depending on GrowthBook flags. -
Payday prediction (called internally during float creation and by
GET /{user_id}/insights/employment/payday) — predicts the user’s next payday date using up to three algorithms run in parallel and compared before returning a result.
Both capabilities depend on the same upstream data sources: Pave-cached insight entities in DynamoDB, employment records in RDS, and recent transactions from the Transactions Service.
Forecasts (GET /{user_id}/forecasts)
The forecasts endpoint assembles a Forecasts response from income sources, recurring expenses, and ritual expenses. Two data paths exist and are controlled by GrowthBook flags:
| Path | Flag(s) | Behaviour |
|---|---|---|
Pave path (default) |
|
Reads |
FloatMe path (FM-generated) |
|
Fetches the user’s last 90 days of transactions from the Transactions Service and runs the FloatMe recurring detection algorithm directly. Does not use Pave-cached data. |
When insights.fm_forcasts.api.send_fm_generated = true for the user and insight.pave.decom.enabled = false, both forecasts are computed: the FloatMe result is saved to DynamoDB as a CASHFLOW_ANALYTICS data-capture record and the FloatMe response is returned to the caller. When neither flag is set, only the Pave forecast is computed and returned.
On the Pave path, a missing Pave meta record (the user has not been mined yet) returns 404; other repository or build failures return 500. If the FloatMe path fails but a Pave forecast was already computed, the Pave forecast is still returned with 200.
FloatMe Recurring Detection
The FloatMe forecasts algorithm analyses raw transaction history directly, without Pave:
-
Fetches up to 90 days of transactions from the Transactions Service.
-
Groups transactions by account, then by transaction name.
-
Within each group, separates credits (income) from debits (expenses) before analysis — this prevents income transactions with the same merchant name as an expense from being mixed.
-
Applies a Jaro-Winkler similarity threshold (≥ 0.90) to group similarly-named transactions together.
-
For expense groups: confirms recurrence by checking that transactions in the group span multiple calendar dates; builds a
RecurringExpenseentity if recurring. -
For income groups: confirms recurrence similarly and validates against Plaid category data.
Data-Capture Writes
When the FloatMe path runs alongside the Pave path, the result is written to the fmdatacapture DynamoDB table with:
| Field | Value |
|---|---|
Event name |
|
Sort key |
|
TTL |
2 days (172,800 seconds) |
This is a shared cross-service data-capture table — the Insight Service is a producer, not the owner.
Payday Prediction
When It Runs
Payday prediction (EvaluateNextPayday) is called in two contexts:
-
During float creation — called by the Float Service with a non-empty
loan_id. In this case, all three prediction algorithms run and results are saved to DynamoDB + data-capture. -
Direct API query —
GET /{user_id}/insights/employment/paydaycalls it with an emptyloan_id. In this case, results are computed but not persisted (no DynamoDB write, no data-capture).
GET /{user_id}/insights/employment/payday takes an optional JSON request body (earliest_date, loan_id). To use the current date as the earliest payday, send an empty JSON object {} or a completely empty body — the NormalizeEmptyPaydayBody middleware rewrites an empty body to {} before decoding, so the mobile app’s no-body call and an internal caller’s {} behave identically. A non-empty but malformed body or an invalid earliest_date returns 400; EvaluateNextPayday failures return 500.
|
Three Prediction Algorithms
All three run when loan_id is provided and insight.payday_prediction.v2.enabled = true for the user; when that flag is false (or not enabled), only Pave and FM Legacy run even if loan_id is provided. Only Pave and FM Legacy run when loan_id is empty.
| Algorithm | Key | Description |
|---|---|---|
Pave |
(always runs) |
Fetches the user’s |
FloatMe Legacy (FM) |
(always runs) |
Uses the user’s RDS employment record ( |
FloatMe V2 |
|
Fetches up to 93 days of transactions from the Transactions Service, filters them to payroll-like candidates using four passes (non-integer amounts, recurring names, override keywords, blacklist exclusions), then runs a 5-phase DOW/DOM analysis pipeline to predict the cadence and next payday date. Does not use employment records or Pave. See FloatMe V2 Payday Analyzer for the full algorithm walkthrough. |
Result Selection
After all algorithms run, the returned prediction is chosen in this order:
insight.payday_prediction.v2.rollout = true for user AND v2 produced a result
└─▶ return V2 prediction
Pave produced a non-empty payday AND no Pave error
└─▶ return Pave prediction
(fallback)
└─▶ return FM Legacy prediction
All three results (and any errors) are logged for offline comparison via a Datadog metric (insight.payday.comparison, insight.payday.v2.comparison).
Persistence (when loan_id is provided)
| Store | What is written |
|---|---|
|
The winning prediction (Pave or FM Legacy) along with both predictions' instant, standard, and extended collection payday dates. Written via |
|
Three entries per evaluation:
|
Collection Date Calculation
The FM Legacy predictor calculates four collection dates beyond the raw payday:
| Field | Buffer from request date |
|---|---|
|
Next payday after |
|
Next payday after |
|
Next payday after |
|
Same as |
GrowthBook Flags
| Flag Key | Default | Effect |
|---|---|---|
|
|
Disables the Pave data path for forecasts entirely; always uses the FloatMe-generated path |
|
|
Per-user flag: when |
|
|
Per-user flag: enables the V2 (transaction-history-based) payday algorithm during float creation |
|
|
Per-user flag: when true, returns V2 results to the caller instead of Pave/FM Legacy results |
Related Pages
-
Architecture — System context and Lambda inventory
-
FloatMe V2 Payday Analyzer — Full walkthrough of the V2 algorithm (filter pipeline, DOW/DOM grids, 5-phase prediction)
-
Pave Mining — How recurring, ritual, and income entities are written to DynamoDB by the miner
-
DynamoDB Tables —
payday,recurring,ritual,incomeentity schemas -
PostgreSQL Schema — Employment table used by FM Legacy predictors
-
Feature Flags — Full GrowthBook flag reference