Skill · Sales and Marketing · Finance and Operations

BFCM Cohort Quality

Find out if last Black Friday's new customers were worth it: contribution, repeats and payback vs October and January buyers, by discount, source and product.

Created by type.com

What it does

  • Compares customers first acquired in last year's BFCM week with October and January–February buyers: repeat rate and contribution at 30 to 365 days, unmatured horizons marked
  • Shows what each group cost to acquire (all ad spend ÷ new customers, not platform attribution), when it paid back, how much it refunded, and how often repeats needed a discount
  • Cuts BFCM buyers by first-order discount depth, source and entry product with 95% ranges; deep discounts are judged on repeat behaviour, not the first order's built-in lower margin
  • Recommends this year's discount depth, the products to prospect with, post-purchase email timing, and the return rate and new-customer cost to carry into the Budget Planner
  • Read-only and runs on local exports, with a tested sample store and 17 end-to-end checks; run it again in January for an early read on this year's BFCM

Before you start

  • A Shopify order export covering every order since the store opened: order, customer, date, net sales, discount and refunds, plus codes, UTM source and products if available
  • Your gross margin, shipping and fulfillment cost per order, payment fees and return rate (the same economics file as the BFCM Holiday Budget Planner)
  • Optional: daily ad spend by channel for last October, BFCM week and January–February, for cost per new customer and payback
  • At least one finished BFCM in the order history; the first 30-day read is possible a month after Cyber Monday
  • Python 3.8 or newer. Nothing to install and no API keys needed

See an example

Example output from a sample run. Company names and figures are sample data.

Fictional sample store: Fernhill Goods. Offline demonstration only.

BFCM Cohort Quality: Fernhill Goods, BFCM 2025 (as of 2026-10-06)

BFCM 2025 = customers whose first-ever order fell on Nov 24–Dec 2, 2025 (the Monday of Thanksgiving week through the Tuesday after Cyber Monday). Compared with the customers first acquired in October 2025 (Oct 1–31, 2025) and Jan–Feb 2026 (Jan 1–Feb 28, 2026). Contribution margin (CM) = net sales kept after refunds − COGS − payment fees − shipping and fulfillment, before ad spend. A horizon is shown only when every customer in the cohort has reached it. Amounts in USD.

BFCM 2025 new customers were worth $31.49 in contribution each at 180 days, vs $52.54 for October 2025 (−40%) and $52.83 for Jan–Feb 2026 (−40%).

180 days is the longest horizon BFCM 2025, October 2025 and Jan–Feb 2026 have all fully reached. BFCM 2025's 365-day horizon matures on Dec 2, 2026.

What the data shows (detected)

  1. Cheaper to acquire, worth less. Blended new-customer cost $35.59 (October 2025 $79.06, Jan–Feb 2026 $64.43). The average BFCM 2025 customer covered it on day 295. October 2025 had recovered 81% by day 340 and Jan–Feb 2026 had recovered 88% by day 220.
  2. Fewer came back. 12.3% ordered again within 180 days (104 of 843), vs 24.0% (October 2025) and 19.6% (Jan–Feb 2026).
  3. More discount-dependent. 48% of their repeat orders within 180 days used a discount (63 of 131), vs 26% (October 2025) and 22% (Jan–Feb 2026).
  4. Customers acquired at deeper discounts repeated less. 186 customers (22.1%) took more than 25% off their first order: 3.8% ordered again within 180 days (2–8%) vs 15.2% (13–18%) at 16–25%, and CM from repeat orders was $1.88 vs $6.18 per customer. First-order CM was $13.09 vs $27.24, lower by construction at a deeper discount. This may reflect who the discount attracts; confirm with a holdout test before setting a cap. Above 35% off (114): 2.6% ordered again, $1.79 of repeat-order CM.
  5. Best entry product: ceramic-pour-over-set. 23.9% ordered again within 180 days (18–31%) vs 12.3% for the cohort; 51% of those repeaters' next orders included stoneware-mug-set.
  6. Weakest source: TikTok. 3.8% ordered again within 180 days (1–9%) vs 12.3% for the cohort, worth $23.80 each. 49% of them took more than 25% off their first order (cohort: 22%), so the two effects overlap.
  7. Refunds. 9.2% of BFCM 2025 first-order net sales were refunded (84 of 843 orders), vs 5.2% for October 2025, 3.7% for Jan–Feb 2026 and the 7% return rate in economics.json.
1. The cohorts side by side
BFCM 2025October 2025Jan–Feb 2026
WindowNov 24–Dec 2, 2025Oct 1–31, 2025Jan 1–Feb 28, 2026
New customers843359514
New customers per day93.711.68.7
First-order AOV (net sales)$84.87$88.61$92.18
First-order discount, average depth26.4%5.4%5.1%
First orders with a discount97.3%43.2%41.4%
First-order net sales refunded9.2%5.2%3.7%
Ad spend in the window (all channels)$30,000$28,384$33,116
Blended new-customer cost$35.59$79.06$64.43
Ordered again within 30 days2.0%5.0%2.1%
Ordered again within 60 days3.8%12.3%6.0%
Ordered again within 90 days6.8%17.3%10.1%
Ordered again within 180 days12.3%24.0%19.6%
Ordered again within 365 daysnot maturenot maturenot mature
CM per customer at 30 days$26.59$40.91$42.38
CM per customer at 60 days$27.25$43.46$43.83
CM per customer at 90 days$28.38$47.04$45.66
CM per customer at 180 days$31.49$52.54$52.83
CM per customer at 365 daysnot maturenot maturenot mature
Days to 2nd order, median (repeaters within 180 days)795985
Repeat orders with a discount (within 180 days)48% of 13126% of 12522% of 148
Payback (CM per customer ≥ blended cost)day 295not by day 340 (81% recovered)not by day 220 (88% recovered)

Not mature yet: 365 days on Dec 2, 2026 (BFCM 2025), Oct 31, 2026 (October 2025) and Feb 28, 2027 (Jan–Feb 2026). Blended new-customer cost = all ad spend in the window ÷ new customers in the window. It is not attribution: it charges retention and brand spend to new customers and gives repeat orders none. Payback = first day the average customer's cumulative CM reaches that cost, read only over days every customer has reached.

2. BFCM 2025 by first-order discount (180 days)
First-order discountCustomersShareFirst-order AOVOrdered again within 180 days (95% range)CM per customer at 180 daysof which first orderof which repeat ordersRepeat orders with a discount
0%232.7%$119.57fewer than 30 customers————
1–15%354.2%$123.4811.4% (5–26%)$57.88$53.22$4.662 of 4
16–25% (planned depth)59971.1%$84.3115.2% (13–18%)$33.42$27.24$6.1855 of 113
26–35%728.5%$80.145.6% (2–13%)$19.45$17.42$2.033 of 4
>35%11413.5%$71.992.6% (1–7%)$12.13$10.35$1.791 of 4
All BFCM 2025843100%$84.8712.3%$31.49$26.04$5.45—

Discount codes on first orders above 25% off: MAYA20 106, WELCOME10 67, MAYA20+WELCOME10 8, VIPEARLY+WELCOME10 5. An order deeper than any single code may mean a code stacked on an automatic sale discount. Depth = discount ÷ (net sales + discount) on the first order, rounded to a whole percent. First-order CM includes any other order placed the same day; a deeper discount lowers it by construction, so compare the repeat rate and repeat-order CM to judge the customers. Compare-at markdowns (clearance) aren't in the order's discount, so they don't count here.

3. BFCM 2025 by acquisition source (180 days)
Source (first order's utm_source)CustomersSpellings mergedFirst order >25% offOrdered again within 180 days (95% range)vs cohort (12.3%)CM per customer at 180 days
Meta452facebook 269, Facebook 57, fb 56, ig 42, instagram 2820%11.1% (8–14%)within noise$31.36
Google221google 189, Google 21, adwords 1115%18.6% (14–24%)clearly higher$36.12
TikTok106tiktok 89, TikTok 1749%3.8% (1–9%)clearly lower$23.80
Klaviyo15klaviyo 1513%fewer than 30 customers——
Pinterest5pinterest 520%fewer than 30 customers——
No UTM (direct/unknown)44—16%15.9% (8–29%)within noise$31.55

utm_source was spelled 10 ways for 3 channels; spellings were merged before cutting. Source is the first order's utm_source: where the first order came from, last click, not what convinced the customer.

4. BFCM 2025 by entry product (180 days)
Product in the first orderCustomersOrdered again within 180 days (95% range)vs cohort (12.3%)CM per customer at 180 daysMost common next product (share of repeaters)
merino-wool-throw21212.3% (9–17%)within noise$57.09ceramic-pour-over-set (23%)
linen-apron2089.6% (6–14%)within noise$29.36stoneware-mug-set (35%)
stoneware-mug-set20013.5% (9–19%)within noise$34.89stoneware-mug-set (33%)
waxed-canvas-tote20012.0% (8–17%)within noise$38.70stoneware-mug-set (42%)
walnut-serving-board19312.4% (9–18%)within noise$33.39walnut-serving-board (33%)
ceramic-pour-over-set18023.9% (18–31%)clearly higher$43.32stoneware-mug-set (51%)

A first order with two products counts under both, so rows overlap. Next product = what the repeaters' second order included.

5. Recommendations for BFCM 2026 (recommended; nothing has been changed)
  1. New customers: test a first-order discount cap at 25% with a holdout before setting it. BFCM 2025 customers acquired above 25% off (186 customers, 22.1% of the cohort) repeated 3.8% within 180 days (2–8%) vs 15.2% (13–18%) at 16–25%, and added $1.88 vs $6.18 of CM per customer after the first order. This may reflect who the discount attracts; confirm with a holdout test before setting a cap. First-order CM was $13.09 vs $27.24; a deeper discount lowers that by itself, so it isn't read as customer quality. Codes on those first orders: MAYA20 (106), WELCOME10 (67) and MAYA20+WELCOME10 (8). Check that welcome and other codes can't combine with the BFCM 2026 sale (BFCM Offer & Discount Config QA checks combinations).
  2. Existing customers: early access, not a deeper code. Give them the same depth as new customers with an early-access window, and don't open the post-purchase flow with another discount: 48% of BFCM 2025 customers' repeat orders already used one, vs 26% for October 2025.
  3. Prospect with ceramic-pour-over-set. BFCM 2025 customers whose first order included it ordered again 23.9% of the time within 180 days (18–31%) vs 12.3% for the whole cohort, and were worth $43.32 each. 51% of those repeaters' next orders included stoneware-mug-set: feature ceramic-pour-over-set in BFCM prospecting creative and gift-guide placements, and cross-sell stoneware-mug-set after it.
  4. Time the post-purchase flow to day 51–79. Of the BFCM 2025 customers who ordered again within 180 days (104 customers), a quarter came back by day 51, half by day 79 and three quarters by day 122. Send the first cross-sell (the entry product's natural next buy, at full price) by day 51, a second touch before day 79, and move non-buyers to the regular newsletter after day 122. 16 of those second orders came before Dec 25: add a gift reminder before your ground-shipping cutoff (Holiday Shipping Cutoff Checker).
  5. Budget math: plan BFCM on first-order contribution, not on lifetime value. By 180 days a BFCM 2025 customer had added $5.45 of contribution after the first day (October 2025: $13.69). In the BFCM Holiday Budget Planner, keep the target MER on first-order contribution and don't lower it for expected repeat purchases; set return_rate_pct to 9.2 (what BFCM 2025 first orders actually refunded) instead of 7; and treat $31.49 (contribution per BFCM 2025 customer at 180 days) as the most a BFCM new customer can cost if it has to pay back within 180 days. BFCM 2025's blended cost was $35.59. If the plan's blended new-customer cost is above $31.49, expect BFCM 2026 customers not to pay back within 180 days at BFCM 2025's quality.
  6. Re-run twice: on Dec 2, 2026, when BFCM 2025's 365-day horizon matures; and in January for an early read on BFCM 2026 (first horizons: 30 days on Dec 31, 2026 and 60 days on Jan 30, 2027). For the January read, compare like with like: --year 2026 --compare "BFCM 2025=2025-11-24:2025-12-02" --compare "October 2026=2026-10-01:2026-10-31". Horizons that haven't matured print as not mature.
6. Caveats and data checks
  • Data: 2,434 orders from 1,716 customers, Oct 1, 2025–Oct 6, 2026. Every customer in the file is in an analysed cohort.
  • New customers: a customer counts as new only if the file holds no earlier order. The file is taken to hold every order since Mar 1, 2021 (--history-since).
  • Costs: COGS estimated at 36% of the pre-discount price (gross margin 64% in economics.json) for every order (no cogs column); shipping and fulfillment $11.00 per order; payment fees 2.9% + $0.30 on net sales (fees on tax and shipping charged aren't counted, so CM is slightly overstated); refunded goods restocked, shipping and fees not recovered (the same assumptions as the BFCM Holiday Budget Planner).
  • Same-day orders: customers who placed another order on their first day: 20 (BFCM 2025), 4 (October 2025), 9 (Jan–Feb 2026). Those orders count toward value, not as a repeat.
  • Seasons differ: 16 of October 2025's 125 repeat orders within 180 days were placed during BFCM 2025 itself; 16 of BFCM 2025's 104 second orders within 180 days came before Dec 25 (gift shopping). Each cohort's first months fall in a different part of the year, so compare them as what they are, not as a controlled test.
  • Small cells: cuts with fewer than 30 customers show no rates (--min-cell). Ranges are 95% Wilson intervals; "clearly higher/lower" means the range excludes the cohort-wide rate. A rough screen for noise, not proof.
  • Correlation, not causation: the cuts show who came back, not why. Customers who chose a deep code or came from a given channel may differ in ways the data can't see. Test a change (a holdout, or one channel at a time) before treating a cut as the cause.
  • Time zone: timestamps converted to America/New_York before taking the order date.

States: findings are detected; section 5 is recommended. Nothing was applied: read-only, no store, ad account or email flow was changed.

AI for marketing teams: a practical guide

Browse the technical files
---
name: bfcm-cohort-quality
description: Find out whether the new customers a Shopify DTC brand bought during Black Friday / Cyber Monday were worth it. Compares them with the customers bought the October before and the January–February after, at equal ages (repeat rate and contribution margin per customer at 30–365 days, immature horizons marked), plus refunds, discount dependence and payback on blended new-customer cost. Cuts BFCM buyers by discount depth, source and entry product, and sets this year's offer and budget. Read-only.
---

# BFCM Cohort Quality

Black Friday 2026 is **Nov 27** and Cyber Monday is **Nov 30**. BFCM is often the cheapest week of the year to buy a new customer: conversion rates jump, so ad spend per new customer falls. But a customer bought at 25–40% off may not behave like one bought in October at full price. A working hypothesis worth testing on your own data: some came for the deal, wait for the next code, and send more back. A budget that values every BFCM customer at the store's average lifetime value overpays for them.

This skill answers one question from the order history: **were the customers we bought during BFCM actually worth it?** Run it now on last year's BFCM to shape this year's offer and budget, and again in January for an early read on this year's. A horizon is only reported once every customer in the cohort has reached it; anything younger prints as "not mature" with the date it matures.

It specializes the library's **Cohort LTV Analyzer**, which builds calendar-month cohorts, so BFCM buyers are blended into November and December with everyone else. Here the BFCM window (Monday of Thanksgiving week through the Tuesday after Cyber Monday) is its own cohort, measured against the October before and the January–February after at the same ages, then cut by first-order discount depth, acquisition source and entry product. Use Cohort LTV Analyzer for the month-by-month LTV triangle; use this skill for the BFCM decision. It hands its numbers to two collection skills: **BFCM Holiday Budget Planner** (same `economics.json`; this report says which return rate and new-customer cost to plan with) and **BFCM Offer & Discount Config QA** (checks that codes can't stack on the sale in ways the plan didn't intend).

## Before you run

This skill ships scripts and sample data alongside this SKILL.md. Before running any command:

1. **Get the files.** Make sure the skill's other files (`scripts/`, `examples/`, `tests/`, `references/` and `DATA_CONTRACT.md`) are in your working folder at the same relative paths. Some environments load only SKILL.md. If yours did, fetch each file from this skill's published files and write it to the matching path. In type.com, read them with the skill-file tools. Anywhere else, the type.com Skills Library API lists every file with its path, content and `sha256`: GET `https://api.type.com/api/public/library/skills` and take the entry with slug `bfcm-cohort-quality`.
2. **Check the copies are exact.** Compare each file's size in bytes, not characters (and its hash, where your tools report one), with the published version before running. A copy written out from the published file is fine once its byte size and hash match. Never run a script you summarised or reconstructed from memory.
3. **Run from the skill's folder**, calling interpreters explicitly: `python3 scripts/…` and `bash examples/run.sh`. Python 3.8+ and the standard library only; there is nothing to install.
4. **Try the sample first.** Run `bash examples/run.sh`. Its output must match `examples/expected_output.txt` exactly. If it doesn't, stop and report the first differing line rather than running on real data. `bash tests/run_tests.sh` runs the full check suite.

## When to use this

- October, before the BFCM offer and budget are locked: run it on last year's BFCM.
- January, for an early read on this year's BFCM (30 days matures about a month after Cyber Monday, 60 days a month later). Re-run at 90 and 180 days.
- Someone asks "were our Black Friday customers any good?", "should we go deeper than 25%?", "what lifetime value should we assume for BFCM customers?" or "which products bring people back?"
- An agency reviewing each brand's last BFCM before planning the next one.

## Operating rules

1. **Read-only.** The script reads local files and prints a report. Never change a discount, flow, campaign or budget unless the user approves that specific change. When they do, make it, read the setting back, and re-run this report when the next horizon matures.
2. **Equal ages only.** Compare cohorts at the same horizon, never a 180-day number with a 365-day one. Never present a "not mature" horizon as a result; quote the date it matures instead.
3. **Contribution, not revenue; blended, not attributed.** Value is contribution margin after COGS, discount, refunds, payment fees and shipping, before ads. Cost per new customer is all ad spend in the window ÷ new customers in the window. Don't swap in platform-reported CAC or ROAS.
4. **Correlation, not causation.** The cuts show who came back, not why. Respect the small-cell suppression and the 95% ranges, say "within noise" when a difference is, and propose a holdout test before calling a cut the cause.
5. **Never invent economics.** Gross margin, shipping, fees and return rate come from the user or the Budget Planner's `economics.json`. If one is missing, ask.
6. **Keep exports local.** The orders file holds customer IDs or emails. The report never prints them; don't paste the file into chat, and delete it when done.
7. Say which state each item is in: **detected**, **recommended**, **applied**, or **verified**.

## Gathering the inputs

Exact columns, aliases and validation are in `DATA_CONTRACT.md`.

**1. `orders.csv`, required: one row per order, full history for every customer.** The script finds each customer's first-ever order itself, so the export must reach back before the cohorts (ideally to the store's first order); `--history-since` states where complete history starts if you pre-filter. Columns: `order_id, customer_id, created_at, net_sales, discount_amount`, plus `discount_codes, refunded_amount, cogs, utm_source` (or `landing_site`) and `products` (handles separated by `;`) when you have them.
- **In type.com**, pull orders through the connected Shopify integration with read-only scopes (`read_orders`, plus `read_all_orders` for anything older than 60 days). The GraphQL fields are listed in `DATA_CONTRACT.md` §1. Never ask the user to paste a key into chat.
- **Shopify admin export** (Orders → Export → All orders, CSV) loads as it is: `Name`, `Email`, `Created at`, `Subtotal`, `Discount Code`, `Discount Amount`, `Refunded Amount`, `Lineitem name` and `Cancelled at` are recognised, extra line-item rows are merged and cancelled orders dropped. It has no UTM data, so the source cut is skipped.
- A **Cohort LTV Analyzer** order export also loads (`order_date`, `net_revenue`, `discount`, `refund_amount`). If its `net_revenue` is already net of refunds, drop or rename `refund_amount` (and set `return_rate_pct` to 0) so refunds aren't subtracted twice; see `DATA_CONTRACT.md` §1.

**2. `economics.json`, required.** The same file as the BFCM Holiday Budget Planner: `gross_margin_pct`, `shipping_fulfillment_per_order`, `payment_fee_pct`, `payment_fee_fixed`, `return_rate_pct`, and `discount_pct` (this year's planned depth, used as the reference band). Every other key is ignored, so the planner's file works unchanged.

**3. `spend.csv`, optional.** Daily ad spend: `date` plus one `*_spend` column per channel (`meta_spend`, `google_spend`, `tiktok_spend`, `other_spend`, …), store time zone, covering every day of each cohort window. The Budget Planner's `history.csv` has the same columns and works too. Without it the report skips cost and payback.

**4. Settings** (flags, all optional): `--year` (default: the latest BFCM finished by the as-of date), `--as-of` (default: the last order date), `--bfcm-window START:END`, `--compare "LABEL=START:END"` (repeatable; replaces the October and January–February defaults), `--horizons` (default `30,60,90,180,365`), `--min-cell` (default 30), `--timezone` (the store's, for timestamps written in UTC) and `--history-since`.

## Running it

```bash
python3 scripts/cohort_quality.py --orders orders.csv --economics economics.json [--spend spend.csv] \
  [--year 2025] [--as-of YYYY-MM-DD] [--timezone America/New_York] [--json analysis.json]
```

`bash examples/run.sh` runs it on the bundled sample store (Fernhill Goods, fictional): BFCM 2025 against October 2025 and January–February 2026, as of Oct 6, 2026.

For the January read on this year's BFCM, compare like with like:

```bash
python3 scripts/cohort_quality.py --orders orders.csv --economics economics.json --spend spend.csv \
  --year 2026 --compare "BFCM 2025=2025-11-24:2025-12-02" --compare "October 2026=2026-10-01:2026-10-31"
```

What it does:

1. **Finds new customers.** A customer is new in a cohort when their first-ever order falls in its window. Anyone with an earlier order is not new, however big their BFCM order.
2. **Measures each cohort at equal ages**: new customers, first-order AOV and discount depth, refunds, blended new-customer cost, then repeat rate and cumulative CM per customer at each horizon. A horizon is computed only when the cohort's last day has reached it (right-censoring); otherwise it prints "not mature" and the date. A second order on the same day as the first counts toward value, not as a repeat.
3. **Compares at the longest horizon the cohorts share**, and adds days to second order (median and quartiles), the share of repeat orders that used a discount, and the payback day: the first day the average customer's cumulative CM reaches the blended cost, read only over days every customer has reached.
4. **Cuts the BFCM cohort** by first-order discount depth (0, 1–15, 16–25, 26–35, >35% of the pre-discount price, with the codes behind the deep bands), by acquisition source (`utm_source` spellings such as `fb` / `Facebook` / `ig` merged) and by entry product (repeat rate and the next product bought). Cells under `--min-cell` customers show no rates; repeat rates carry a 95% range, and "clearly higher/lower" is used only when the range excludes the cohort-wide rate.
5. **Judges deep discounts on later behaviour only.** A deeper first-order discount lowers first-order CM by construction, so first-order CM is reported separately and the "customers acquired at deeper discounts repeated less" finding appears only when the deep group's repeat rate (95% Wilson range) or CM from repeat orders (95% interval) is clearly lower than the planned-depth band's. It is worded as an association to confirm with a holdout, not a cause.
6. **Recommends** this year's discount depth for new and existing customers, entry products to prospect with, post-purchase flow timing, the numbers to carry into the BFCM Holiday Budget Planner, and when to re-run.

Exit codes: 0 means the report was written, 2 means invalid input (with the file, line, column and reason). Numbers are parsed strictly: comma decimals (`75,00`), European-formatted columns (`1.234,56`), `nan`, `inf`, exponents and negative amounts are rejected, not guessed; `$1,234.56`, `€1,234.56` and `1234.56` are fine, and a refund written `(50.00)` or `-50.00` is read as a 50.00 refund. A `*_pct` key in economics.json between 0 and 1 (`gross_margin_pct: 0.64`) exits 2 for margin and planned discount and is flagged for fees and returns. **Net sales** means gross sales − discounts, before returns/refunds, excluding tax and shipping.

## Acting on the report

Present the headline and the numbered findings first, then walk through the recommendations. Each change needs the user's approval. Settings, flow layouts and templates are in `references/playbook.md`.

1. **Offer depth for new customers.** If customers acquired above the planned depth repeated clearly less (or added clearly less CM after their first order), propose testing a cap at the planned depth with a holdout before setting it; the gap may reflect who the discount attracts rather than the discount itself. Lower first-order CM alone is not evidence. Either way, check that codes can't stack on the sale unintentionally. Run BFCM Offer & Discount Config QA to check combinations and per-customer limits, and Promo Margin Guard if the depth itself is still open.
2. **Existing customers.** The report cites returning customers' BFCM orders only when there are at least 50, with the share discounted and a 95% range; test whether they'd buy without a discount. Consider early access at the same depth rather than a deeper code, and keep discounts out of the first post-purchase emails to BFCM buyers.
3. **Entry products.** Lead BFCM prospecting creative and gift-guide placements with the product whose buyers came back most, and cross-sell its usual next product.
4. **Post-purchase timing.** Set the cross-sell and follow-up delays in the post-purchase flow from the report's quartile days, and add a gift reminder before the ground-shipping cutoff (Holiday Shipping Cutoff Checker).
5. **Budget math.** In the BFCM Holiday Budget Planner, set `return_rate_pct` to the BFCM first-order refund rate if it is higher, keep the target MER on first-order contribution, and compare the plan's blended new-customer cost with the report's CM per BFCM customer at the comparison horizon.
6. **Re-run.** When the next horizon matures, and in January for the early read on this year's BFCM. Mark a recommendation **verified** only when a later run shows the change worked.

## Files

- `scripts/cohort_quality.py`: the analyzer. Local files only, deterministic output, optional `--json`.
- `DATA_CONTRACT.md`: the three input formats, column aliases, settings, every definition and formula, and the output.
- `references/playbook.md`: pulling orders from Shopify, reading the report, setting the BFCM offer, post-purchase flow timing, budget hand-off, the January read, and a summary template.
- `examples/run.sh`, `examples/expected_output.txt`, `examples/data/`: sample run. `examples/make_fixtures.py` regenerates the sample data.
- `tests/run_tests.sh`, `tests/make_cases.py`: 17 end-to-end checks on hand-checkable inputs. No network needed.