Payday Prediction
Payday prediction returns a user’s next payday and the collection dates derived from it. It runs on GET /{user_id}/insights/employment/payday (the Float Service passes loan_id during float creation; the mobile app sends no body) and inside GET /{user_id}/insights/employment. Request and response shapes are in API Specification.
Entry Points
| Caller | Behaviour |
|---|---|
|
Cacheable path; |
|
Bypasses the cache in both directions ( |
|
Cacheable path, no |
The payday endpoint also fills estimated_next_paydays: the next N paydays projected from the chosen cadence, where N is the insights.payday_prediction.next_paydays_to_estimate flag (default 6).
A user with no employment record gets 500 from every entry point — the chain reads the record before running any algorithm.
|
Same-Day Cache
A prediction is computed at most once per user per UTC day and stored in the payday entity (schema).
-
Read
PAYDAY#<user_id>/PREDICTION. Ifprediction_dateequals today, return the stored chosen prediction (result:hit) and write aPAYDAY_PREDICTIONcapture row withfrom_cache: true. -
Otherwise run the Prediction Chain (
result:miss), store the winner plus every algorithm’s output, and return the winner.
The row is deleted (result:invalidated) whenever the employment record changes: employment create, update, and delete, income verification save, and the institution-change handler. Deletion failures are logged, never surfaced.
Prediction Chain
| Order | Algorithm | Runs when |
|---|---|---|
1 |
Pave (Pave) |
The pay-frequency gate allows the user |
2 |
FloatMe V2 (FloatMe V2) |
Pave produced no payday |
3 |
FloatMe V1 (FloatMe V1) |
Always computed; chosen when neither of the above produced a payday |
Source: pkg/payday/predictor.go (computeNextPayday)
Every run logs Payback prediction comparison with all four outputs (Pave V1, Pave V2, FloatMe V1, FloatMe V2), emits insight.payday.predictor_used tagged predictor:pave|fm_v2|fm_v1, pay_frequency, gated, and is_loan, and writes a PAYDAY_PREDICTION row (Persistence).
Pave
One call to Pave GetIncomeSource returns the user’s recurring income sources; two selectors run over the same list. insight.payday_prediction.pave_v2.rollout (per user) picks V2 when it produced a payday, otherwise V1. The losing selection is still logged and captured (used_pave_v2).
| Selector | Selection |
|---|---|
V1 |
Sources with |
V2 |
Filter to |
Source: pkg/payday/helpers.go (V1), pkg/payday/pave_predictor.go (V2)
Dates from the selected source: next_date when it is not before the earliest date, else the first of Pave’s next_dates that is not, else next_date plus avg_period_days. The earliest date for payday is floored at tomorrow, so a source paying today resolves to its following payday and sets is_today. The extended date is the first next_dates entry inside the extended window, else next_date stepped by avg_period_days until it passes the window start.
FloatMe V1
Arithmetic over the employment record: step payday forward by pay_frequency (weekly 7 days, bi-weekly 14 days, monthly one month; bi-monthly and semi-monthly snap to the 15th or the 1st of next month) until it is not before the earliest date, then move a bank holiday back one day and a Saturday or Sunday back to Friday. Source: pkg/employment/payday.go
FloatMe V2
Predicts cadence and next payday from the primary account’s credit history alone; no employment record or Pave data.
| Stage | What happens |
|---|---|
1. Fetch |
93 days of transactions from the Transactions Service; keep credits on the primary account; amounts become positive cents |
2. Filter |
Four passes (Filter Passes) over transactions dated before today and under $5,000; union the include passes, subtract the blacklist, keep the highest amount per calendar day |
3. Gate |
Total at least $3,000 and at least 3 transactions, else |
4. DOW grid |
Weeks x 7 amounts from the Sunday before the first transaction; per-column z-scores; per weekday |
5. DOM grid |
Months x 31 amounts; per day |
6. Predict |
Phases in Prediction Phases; result captured as |
Source: pkg/payday/floatme_predictor.go, pkg/payday/floatme_analyzer.go
Filter Passes
| Pass | Rule |
|---|---|
Non-integer amount |
Amount at least $300 and not a whole dollar |
Recurring name |
Amount at least $800 and the cleaned name appears on at least 2 distinct dates at $800 or more |
Override keyword |
Cleaned name contains |
Blacklist (exclude) |
Cleaned name contains a gambling, earned-wage-access, reversal/refund, or brokerage keyword ( |
Source: pkg/payday/floatme_predictor.go
Prediction Phases
| Phase | Rule |
|---|---|
0 Gate |
Total volume under $3,000: no prediction |
1 Anchors |
DOM days with |
2 Signals |
|
3 Calendar mode |
|
4 DOW waterfall |
Skipped in calendar mode. Take the best recent weekday by |
5 DOM fallback |
Requires |
Source: pkg/payday/floatme_analyzer.go
Output cadence is one of WEEKLY, BIWEEKLY, SEMIMONTHLY, MONTHLY (UNKNOWN is defined but never returned); predictor FLOATME_V2_PAYDAY_CALCULATION. FloatMe V2 sets only payday, frequency, and payday_cadence; the collection fields below are empty for it.
Collection Dates
Pave and FloatMe V1 fill four collection fields alongside payday, each by re-running the same algorithm with a different earliest date.
| Field | Earliest date used |
|---|---|
|
Request |
|
Now + 24h, plus one day when that lands after 06:00 UTC of its calendar day |
|
Now + 96h, with the same 06:00 UTC adjustment |
|
First payday after |
Source: pkg/payday/helpers.go, pkg/payday/predictor.go
Persistence
| Store | Written |
|---|---|
|
Winner plus each algorithm’s output, on a cache miss — DynamoDB Tables |
|
|