The formulas behind the tools, and four queries for the scorecard.
| For | Formula | Notes |
|---|---|---|
| Blended repeat rate | R = u × ru + (1 − u) × rn | u: share used early. ru, rn: repeat rates of users and non-users. |
| Lift from moving m to used | ΔR = m × (ru − rn) × c | m: share of all buyers moved. c: share of the gap caused by use; estimate it with a holdout. |
| Break-even first-use spend | ΔR × V per first-time buyer | V: contribution per repeat customer over 12 months. Yearly value is N × 12 × ΔR × V. |
| Corrected share used by day t | pt × (q + (1 − q)(1 − k)) | q: share of the cohort you heard from. k: how much less likely the rest are to have used it. |
| Median time to first use | Linear interpolation between the two days where the corrected share crosses 50% | From (0, 0) through days 1, 3, 7, 14 and 30. |
| Fair captivity | (H + S + W) / 18 × (1 − R / 12) | Each source 0 to 6 from three statements; R is resentment, 0 to 6. A judgment weighting, not a published scale. |
-- first-time buyers, whether they used it within 14 days of delivery,
-- and whether they ordered again within 180 days of the first order.
-- usage_events: one row per signal of use (survey answer, activation,
-- QR scan), with customer_id, used_at, source
WITH first_orders AS (
SELECT o.customer_id, o.id AS order_id, o.created_at,
MIN(f.delivered_at) AS delivered_at
FROM orders o
JOIN fulfillments f ON f.order_id = o.id
WHERE o.customer_order_index = 1 -- or ROW_NUMBER() over created_at
AND o.created_at < CURRENT_DATE - INTERVAL '180 days'
GROUP BY 1, 2, 3
),
use AS (
SELECT fo.customer_id,
MIN(u.used_at) AS first_used_at
FROM first_orders fo
JOIN usage_events u ON u.customer_id = fo.customer_id
AND u.used_at >= fo.delivered_at
GROUP BY 1
),
repeaters AS (
SELECT DISTINCT fo.customer_id
FROM first_orders fo
JOIN orders o2 ON o2.customer_id = fo.customer_id
AND o2.created_at > fo.created_at
AND o2.created_at <= fo.created_at + INTERVAL '180 days'
)
SELECT CASE WHEN u.first_used_at <= fo.delivered_at + INTERVAL '14 days'
THEN 'used by day 14' ELSE 'not used / unknown' END AS grp,
COUNT(*) AS buyers,
AVG(CASE WHEN r.customer_id IS NOT NULL THEN 1.0 ELSE 0 END) AS repeat_rate_180d
FROM first_orders fo
LEFT JOIN use u ON u.customer_id = fo.customer_id
LEFT JOIN repeaters r ON r.customer_id = fo.customer_id
GROUP BY 1;
Buyers with no signal fall into the second group, which mixes non-users with users you didn’t hear from. Run it again restricted to buyers who answered the survey, and use that version in the tool; the unrestricted one understates the gap.
-- cumulative share used by day 1, 3, 7, 14, 30 after delivery,
-- among first-time buyers who answered the use question
-- (reuses the first_orders and use CTEs from the query above)
SELECT DATE_TRUNC('month', fo.delivered_at) AS cohort,
COUNT(*) AS respondents,
AVG(CASE WHEN u.first_used_at <= fo.delivered_at + INTERVAL '1 day' THEN 1.0 ELSE 0 END) AS by_d1,
AVG(CASE WHEN u.first_used_at <= fo.delivered_at + INTERVAL '3 days' THEN 1.0 ELSE 0 END) AS by_d3,
AVG(CASE WHEN u.first_used_at <= fo.delivered_at + INTERVAL '7 days' THEN 1.0 ELSE 0 END) AS by_d7,
AVG(CASE WHEN u.first_used_at <= fo.delivered_at + INTERVAL '14 days' THEN 1.0 ELSE 0 END) AS by_d14,
AVG(CASE WHEN u.first_used_at <= fo.delivered_at + INTERVAL '30 days' THEN 1.0 ELSE 0 END) AS by_d30
FROM first_orders fo
JOIN survey_responses s ON s.customer_id = fo.customer_id
AND s.question = 'first_use'
LEFT JOIN use u ON u.customer_id = fo.customer_id
GROUP BY 1
ORDER BY 1;
Map survey answers to a date before this runs: “today” is the response date, “1 to 3 days after it came” is delivery plus 2 days, and so on; “not yet” leaves first_used_at empty. Divide respondents by the cohort’s delivered first orders to get coverage for the tool in chapter 3.
-- share of repeat buyers whose latest gap is within 25% of their median gap
WITH gaps AS (
SELECT customer_id, created_at,
created_at - LAG(created_at) OVER (PARTITION BY customer_id ORDER BY created_at) AS gap
FROM orders
),
per_customer AS (
SELECT customer_id,
PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY EXTRACT(EPOCH FROM gap)) AS median_gap,
(ARRAY_AGG(EXTRACT(EPOCH FROM gap) ORDER BY created_at DESC))[1] AS last_gap,
COUNT(gap) AS n_gaps
FROM gaps WHERE gap IS NOT NULL
GROUP BY 1
)
SELECT AVG(CASE WHEN ABS(last_gap - median_gap) <= 0.25 * median_gap THEN 1.0 ELSE 0 END) AS regular_share
FROM per_customer
WHERE n_gaps >= 3;
-- reorders from a saved cart, reorder link or subscription
SELECT DATE_TRUNC('week', created_at) AS week,
AVG(CASE WHEN landing_site LIKE '%utm_content=reorder%'
OR source_name = 'subscription'
THEN 1.0 ELSE 0 END) AS one_tap_share
FROM orders WHERE customer_order_index > 1
GROUP BY 1 ORDER BY 1;
This is one chapter of Used, Not Bought, which is free and readable in full on a single page with no form in front of it.