How the repurchase algorithm decides
This article explains why the algorithm recommends the products it does. For the practical side (the variable, builtin, preset, input/output contract, and errors), see Integrate & call the repurchase algorithm.
The core intuition
For each product in a user's purchase history, the algorithm:
Estimates D, the product's repurchase cadence in days.
Computes
days_since_last_purchase, how long since the user last bought it.Defines a repurchase window around D.
If the user is inside the window, the product is eligible and gets an urgency score; otherwise it's discarded.
Eligible products are returned sorted by descending urgency. The idea: don't recommend a product right after it was bought (too early), nor indefinitely long after (the need is probably already covered), but around the moment the observed cadence says the user is due to buy again.
Estimating the cadence D
How D is estimated depends on the user's history for that specific product.
Two or more purchases: use the user's own cadence
Sort the purchase dates, take the intervals between consecutive purchases, and set D to their mean.
Example. Purchases on Jan 1, Mar 1, May 1 → intervals of 59 and 61 days → D = 60 days.
The user's own cadence is used as-is, with no smoothing and no blending with the site-wide average.
A single purchase: fall back to the site-wide average
One purchase can't measure an interval, so the algorithm falls back to the product's site-wide average cadence (avg_repurchase_interval_days).
If a site-wide average exists → it becomes D.
If none exists (nobody has ever repurchased the product) → the product is excluded. There is no second fallback.
The repurchase_frequency column always reflects the value actually used: the user's own cadence, or the site-wide average when it's the fallback.
The site-wide average
The site-wide average is the safety net for users who bought a product only once. For each product:
Consider purchases over the trailing 12 months.
Keep only users who bought that product at least twice on distinct dates. A one-time buyer says nothing about cadence.
For each such user, compute their average cadence (mean of the intervals between their successive purchases).
The product's site cadence is the mean of those per-user cadences, where every multi-buyer weighs equally regardless of how many times they bought.
Two product-level indicators come out of this:
avg_repurchase_interval_days
Average repurchase cadence across all multi-buyers. Null if no multi-buyer exists → the product can't serve as a fallback.
multi_buyer_count
Number of users who bought the product ≥ 2 times (a confidence indicator).
These refresh with the catalog; a daily cadence is enough, since the 12-month aggregate barely moves day to day.
The repurchase window
Around the estimated cadence D, an eligibility window is defined as:
With the default ratio of 1/3, the window is [2/3·D, 4/3·D].
The original brief said "±30%", but the implemented default is one third (1/3 ≈ 33.3%). 1/3 is the authoritative value.
A product is eligible only when the user sits inside the window:
Before the window (too early) → excluded. After it (overdue) → excluded. The ratio is configured once per site (repurchase_window_ratio in Redis) and is shared by every repurchase rule on that site, so it's not a per-rule parameter. Per-rule tuning happens through where / sort on days_since_last_purchase and urgency_score.
Example. D = 30 days → window
[20, 40]days after the last purchase.
The urgency score
The urgency score is a symmetric triangular peak centered on D: 1.0 exactly at the forecasted date, falling linearly to 0 at the window edges.
The compact form:
With D = 30 and ratio = 1/3 (window [20, 40]):
20
0.0
25
0.5
30
1.0
35
0.5
40
0.0
Sorting urgency_score DESC therefore surfaces the products for which the user is closest to their repurchase date.
End-to-end example
Request excerpt:
Walkthrough as of 2026-04-02, default ratio 1/3:
COFFEE-1KG: 3 purchases → intervals of 30 and 31 days → D ≈ 30.5. Last purchase 03/06 →
days_since_last ≈ 27. Window ≈[20.3, 40.7]. 27 is inside → eligible,urgency_score ≈ 0.65.FILTERS-100: 1 purchase → fall back to the site-wide average. If
avg_repurchase_interval_days ≈ 45→ D = 45. Last purchase 02/20 →days_since_last ≈ 41. Window ≈[30, 60]. 41 is inside → eligible. Had there been no site-wide average, it would be excluded.
The response returns the eligible products with their catalog columns plus repurchase_frequency, days_since_last_purchase, and urgency_score, sorted by urgency_score DESC and limited to 12.
Reminder: the API doesn't fetch history on its own. The caller supplies purchase_history on every request. See Integrate & call the repurchase algorithm for how to wire that up.
Defaults at a glance
Input variable
purchase_history (ITEMS_HISTORY)
Declared automatically on analytics unlock
Cap on products in history
50 (most recent kept)
Not configurable
Window width ratio
1/3 (≈ ±33%) → [2/3·D, 4/3·D]
Per site (repurchase_window_ratio), not per rule
Site-wide average lookback
Trailing 12 months
Not configurable
Multi-buyer filter
Users with ≥ 2 purchases of the product
Not configurable
Default sort
urgency_score DESC
Editable in the rule
Default volume
12
Editable in the rule
Last updated
Was this helpful?

