Skill · Sales and Marketing · Finance and Operations

BFCM Holiday Budget Planner

Plan your Black Friday and Cyber Monday ad budget from MER and contribution margin, not platform ROAS, with a daily, per-channel plan and guardrails.

Created by type.com

What it does

  • Works out the MER you need to break even at your BFCM discount and at full price, and the MER that keeps your margin floor, after COGS, shipping, fees and returns
  • Shapes a daily budget for Nov 1 – Dec 6 from last year's BFCM curve, split across Meta, Google, TikTok and other paid channels, within your channel limits and total cap
  • Ramps up no more than 30% a day, peaks on Black Friday and steps down no more than 50% a day; Thanksgiving, the weekend and Cyber Monday follow last year's curve
  • Projects low, base and high net sales, MER and contribution per day, with per-day pull-back and scale lines that always sit below and above that day's plan
  • Read-only: no platform ROAS; rejects comma decimals, nan and unknown spend or numeric columns; flags stale history, paused ads and assumed curves; 22 end-to-end checks

Before you start

  • Daily Shopify net sales, orders and new-customer orders for last October to December and this year's last 90 days
  • Daily ad spend for the same days from Meta, Google, TikTok and any other paid channel you run
  • Your gross margin, shipping and fulfillment cost per order, payment fees, expected return rate, and your planned BFCM discount with the dates it runs
  • A goal: the minimum margin to keep after ads, or a target MER, plus an optional total budget cap
  • Python 3.8+; nothing to install. Works on type.com or anywhere you can run Python.

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 Budget Plan: Fernhill Goods, Nov 1–Dec 6 2026

History through 2026-10-06. Black Friday Nov 27, Cyber Monday Nov 30, cyber week Nov 26–Dec 1, sale Nov 24–Dec 1 (25% off). Amounts in USD.

Plan: $61,059 ad spend over 36 days. Base case $237,995 net sales, MER 3.90, $28,562 contribution after ads (12.0% of net sales).

  • Target: keep 12% of net sales after ads across the window (MER ≥ 4.83 on sale days, ≥ 3.15 at full price). Breakeven MER: 3.06 at 25% off, 2.29 at full price.
  • Spend stops at: margin floor. Budget cap: $87,000. Today's run rate $951/day; day-over-day change capped at +30% / -50%.
  • Peak budget $6,672 on Nov 27 (Black Friday). Projected new customers 1,582, blended new-customer cost $39.

Warnings

  • On 1 day(s) (Nov 25) the plan spends more than 2x last year. Revenue there is extrapolated beyond the fitted range.
  • Warm-up elasticity is assumed, not fitted: last year's raw fit was 1.04, outside 0.20–0.70, so 0.70 (the ceiling, the most optimistic response to extra spend allowed) is used.
  • Cyber week elasticity is assumed, not fitted: last year's raw fit was 1.06, outside 0.20–0.70, so 0.70 (the ceiling, the most optimistic response to extra spend allowed) is used.
  • The Black Friday budget rests on an assumed cyber-week curve, not a fitted one. If the real elasticity were 0.40 in every phase, this same budget would bring $234,716 net sales (MER 3.84, $27,548 contribution after ads, 11.7% of net sales).
Unit economics (per $100 of net sales)

Net sales = gross sales − discounts, before returns/refunds, excluding tax and shipping. AOV $89 on sale days, $88 at full price. Gross margin 64% at full price, returns 7% (goods restocked, shipping and fees not recovered). The 25% off sale runs Nov 24–Dec 1; the other days of the window sell at full price.

Line25% offFull price
Sales kept after returns93.0093.00
COGS on kept goods-44.64-33.48
Payment fees-3.24-3.24
Shipping and fulfillment-12.41-12.54
Contribution before ads32.7243.74

Breakeven MER = 100 / 32.72 = 3.06 at 25% off, 100 / 43.74 = 2.29 at full price. Target MER = 100 / (32.72 − 12.00) = 4.83 at 25% off, 100 / (43.74 − 12.00) = 3.15 at full price.

Last year's curve (aligned to Black Friday)

Last year's dates are shifted 364 days so Black Friday lines up with Black Friday.

PhaseLast year's datesDaysNet salesShare of windowAd spendMERBest day
warm-upNov 2–Nov 2625$85,86539.8%$28,0833.06Nov 26 ($7,440)
cyber weekNov 27–Dec 26$101,52047.0%$25,2404.02Nov 28 ($28,320)
taperDec 3–Dec 75$28,57813.2%$8,2493.46Dec 3 ($6,248)

Diminishing returns (estimate). Log-log fit of daily net sales on daily ad spend within each phase. Elasticity 0.5 means doubling spend lifts sales about 41%. Fits on last year's days mix demand with spend, so read them as rough. A fit outside 0.20–0.70 is clamped to the nearer bound and labelled assumed, not fitted. Warm-up and taper days use a 7-day centered average of last year so one lucky day doesn't steer the plan; cyber-week days are kept as they were.

CurveDaysFitted on daily spendElasticityr²Note
last October31$693–$1,1610.590.86—
warm-up25$753–$1,9400.700.83assumed 0.70 (clamped from 1.04); demand rose with spend
cyber week6$2,410–$6,6600.700.98assumed 0.70 (clamped from 1.06); demand rose with spend
taper5$1,394–$1,9540.470.48—

Sensitivity: at elasticity 0.40 in every phase, the same budget projects $234,716 net sales (MER 3.84, $27,548 contribution after ads, 11.7% of net sales; Black Friday $31,376).

Demand index 1.11 (estimated: Sep 9–Oct 6 actual sales vs what last October's curve predicts at the same spend). Every day's projection is last year's same day × this index, adjusted along the curve for the planned spend.

Daily plan (base scenario)
DateDayPhasePriceBudgetMetaGoogleTikTokvs prior dayProj. net salesMERContributionPull back belowScale above
Nov 1Sunwarm-upfull price$951$526$301$124—$2,9083.06$3212.603.36
Nov 2Monwarm-upfull price$951$526$301$124+0%$2,8853.03$3112.583.34
Nov 3Tuewarm-upfull price$951$526$301$124+0%$2,9263.08$3292.623.38
Nov 4Wedwarm-upfull price$951$526$301$124+0%$2,9633.12$3452.653.43
Nov 5Thuwarm-upfull price$951$526$301$124+0%$2,9663.12$3462.653.43
Nov 6Friwarm-upfull price$951$526$301$124+0%$2,9743.13$3502.663.44
Nov 7Satwarm-upfull price$951$526$301$124+0%$3,0273.18$3732.713.50
Nov 8Sunwarm-upfull price$951$526$301$124+0%$3,0633.22$3892.743.54
Nov 9Monwarm-upfull price$951$526$301$124+0%$3,1103.27$4092.783.60
Nov 10Tuewarm-upfull price$951$526$301$124+0%$3,1533.32$4282.823.65
Nov 11Wedwarm-upfull price$951$526$301$124+0%$3,1453.31$4252.813.64
Nov 12Thuwarm-upfull price$951$526$301$124+0%$3,1823.35$4412.843.68
Nov 13Friwarm-upfull price$951$526$301$124+0%$3,1993.36$4482.863.70
Nov 14Satwarm-upfull price$951$526$301$124+0%$3,1743.34$4382.843.67
Nov 15Sunwarm-upfull price$951$526$301$124+0%$3,1783.34$4392.843.68
Nov 16Monwarm-upfull price$951$526$301$124+0%$3,1923.36$4452.853.69
Nov 17Tuewarm-upfull price$951$526$301$124+0%$3,1833.35$4412.843.68
Nov 18Wedwarm-upfull price$951$526$301$124+0%$3,2383.40$4652.893.74
Nov 19Thuwarm-upfull price$951$526$301$124+0%$3,3153.49$4992.963.83
Nov 20Friwarm-upfull price$1,190$659$376$155+25%$4,1883.52$6422.993.87
Nov 21Satwarm-upfull price$1,423$788$450$185+20%$5,0063.52$7672.993.87
Nov 22Sunwarm-upfull price$1,798$995$569$234+26%$6,2943.50$9552.983.85
Nov 23Monwarm-upfull price$2,337$1,294$739$304+30%$7,8183.35$1,0832.843.68
Nov 24Tuewarm-up25% off$3,038$1,682$961$395+30%$9,7543.21$1533.064.83
Nov 25Wedwarm-up25% off$3,949$2,186$1,249$514+30%$12,2683.11$653.064.83
Nov 26 ThanksgivingThucyber week25% off$5,133$2,841$1,624$668+30%$18,9453.69$1,0653.144.83
Nov 27 Black FridayFricyber week25% off$6,672$3,693$2,110$869+30%$31,3934.71$3,5984.005.18
Nov 28Satcyber week25% off$3,336$1,847$1,055$434-50%$15,3994.62$1,7023.925.08
Nov 29Suncyber week25% off$3,390$1,876$1,073$441+2%$15,9464.70$1,8274.005.17
Nov 30 Cyber MondayMoncyber week25% off$3,954$2,188$1,251$515+17%$18,5994.70$2,1314.005.17
Dec 1Tuecyber week25% off$1,977$1,094$626$257-50%$9,0214.56$9743.885.02
Dec 2Wedtaperfull price$989$547$313$129-50%$5,1005.16$1,2424.385.67
Dec 3Thutaperfull price$951$526$301$124-4%$4,8955.15$1,1904.375.66
Dec 4Fritaperfull price$951$526$301$124+0%$4,8955.15$1,1904.375.66
Dec 5Sattaperfull price$951$526$301$124+0%$4,8955.15$1,1904.375.66
Dec 6Suntaperfull price$951$526$301$124+0%$4,7995.05$1,1484.295.55
Total$61,059$33,788$19,319$7,952$237,9953.90$28,562

Channel split: meta 55%, google 32%, tiktok 13% (this year's trailing mix, held inside your min/max). MER is blended across all channels on purpose: no per-channel revenue is projected.

Scenarios
PhaseSpendLow salesLow MERBase salesBase MERHigh salesHigh MERBase contributionBase margin
warm-up$31,804$88,4922.78$104,1083.27$114,5193.60$11,30510.9%
cyber week$24,462$92,9083.80$109,3044.47$120,2344.92$11,29710.3%
taper$4,793$20,8964.36$24,5835.13$27,0415.64$5,96024.2%
Window$61,059$202,2963.31$237,9953.90$261,7954.29$28,56212.0%

Low = demand 0.85x base, high = 1.10x, same spend. Window contribution after ads: low $15,119, base $28,562, high $37,524.

Guardrails

MER to date = Shopify net sales so far today ÷ total ad spend so far today across every platform. Never use a platform's ROAS for these calls. Each day has its own lines, set from that day's plan MER: pull back below the higher of breakeven MER and plan MER x 0.85; scale above the higher of target MER and plan MER x 1.10. Every pull-back line sits below its day's plan MER and every scale line above it. They are the last two columns of the daily plan and the pull_back_mer and scale_mer columns of the --csv plan. The table below summarises them by phase.

PhaseDatesPricingPlan MERBreakeven MERTarget MERPull back if MER to date is belowScale if aboveCheck at (store time)
warm-upNov 1–Nov 23full price3.312.293.152.58–2.993.34–3.872 pm
warm-upNov 24–Nov 2525% off3.153.064.833.064.832 pm
cyber weekNov 26–Dec 125% off4.473.064.833.14–4.004.83–5.18noon and 6 pm
taperDec 2–Dec 6full price5.132.293.154.29–4.385.55–5.672 pm
  • Warm-up, Nov 1–Nov 23 (full price): if MER to date is below that day's pull-back line (2.58–2.99) by 2 pm, cut the rest of today's budgets 20% and hold tomorrow at today's level. If it is above that day's scale line (3.34–3.87) by 2 pm two days running, raise tomorrow's budget by up to 30%, never more.
  • Warm-up, Nov 24–Nov 25 (25% off): if MER to date is below that day's pull-back line (3.06) by 2 pm, cut the rest of today's budgets 20% and hold tomorrow at today's level. If it is above that day's scale line (4.83) by 2 pm two days running, raise tomorrow's budget by up to 30%, never more.
  • Cyber week, Nov 26–Dec 1 (25% off): if MER to date is below that day's pull-back line (3.14–4.00) by noon, cut the rest of today's budgets 20% and hold tomorrow at today's level. If it is above that day's scale line (4.83–5.18) by noon two days running, raise tomorrow's budget by up to 30%, never more. Check again at 6 pm. On Black Friday and Cyber Monday, judge from noon onward, not the first hours after a midnight launch.
  • Taper, Dec 2–Dec 6 (full price): if MER to date is below that day's pull-back line (4.29–4.38) by 2 pm, cut the rest of today's budgets 20% and hold tomorrow at today's level. If it is above that day's scale line (5.55–5.67) by 2 pm two days running, raise tomorrow's budget by up to 30%, never more.
  • Every morning, compare yesterday's full-day MER with the plan row. Re-run the plan with the new days if actuals miss the base case by more than 15% for three days.
Hand-off
  • 2026-11: $54,289 (meta $30,043, google $17,176, tiktok $7,070)
  • 2026-12: $6,770 (meta $3,745, google $2,143, tiktok $882)
  • Budget Pacer: use each month's total as that month's committed media spend and the daily rows as campaign bounds. December above covers only the days in this window; add the rest of December yourself.
  • Meta Budget Scaling: use the Meta column as the daily budget path and the 30% daily increase limit as its scaling guardrail.

Estimates from last year's curve, an elasticity per phase (fitted or assumed) and a demand index; not a forecast guarantee. There is no organic baseline: every projected sale is tied to ad spend, so the model projects $0 at $0 spend. Read-only: no ad account or budget was changed.

AI for marketing teams: a practical guide

Browse the technical files
---
name: bfcm-budget-planner
description: Plan a DTC brand's paid-media budget for Black Friday / Cyber Monday from MER (store revenue ÷ total ad spend) and contribution margin, not platform ROAS. Computes breakeven and target MER at the planned discount, shapes a daily budget per channel from last year's BFCM curve with a learning-safe ramp and a hard cap, projects revenue, MER and contribution per day in low/base/high scenarios, and sets pull-back and scale guardrails for every day. Read-only.
---

# BFCM Holiday Budget Planner

Black Friday 2026 is **Nov 27** and Cyber Monday is **Nov 30**. Most BFCM budgets get set by asking each ad platform what it returned last year. That is the wrong number. Meta, Google and TikTok each claim credit for the same sale, so their ROAS figures add up to more revenue than the store actually made. A widely shared r/FacebookAds BFCM prep thread ("BFCM Prep you should do THIS WEEK", https://www.reddit.com/r/FacebookAds/comments/1waqvp4/) makes the same point: plan off total revenue against total spend.

This skill plans the whole window from two numbers the business can verify: **MER** (Shopify net sales ÷ total ad spend across every platform) and **contribution margin** (what is left after COGS, the discount, shipping, payment fees and returns). It tells you the MER you need to break even at your discount depth, how much to spend each day and on which channel, what that should bring in, and when to pull back or push harder.

It sits next to three other library skills and does not repeat them:
- **Promo Margin Guard** settles how deep a discount each product can carry. Run it first if the discount depth is still open.
- **Budget Pacer** tracks a month's committed spend once it is live. This plan's monthly totals are its commitments.
- **Meta Budget Scaling** handles Meta campaign structure and day-to-day scaling. This plan's Meta column is its daily budget path.

## 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-budget-planner`.
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

- Someone asks "how much should we spend on BFCM?", "what MER do we need at 25% off?", or "how do we ramp into Black Friday?"
- A founder or finance lead wants the BFCM ad budget tied to margin, not to platform ROAS.
- An agency is setting BFCM budgets for each brand it manages.
- October and early November: plan once, then re-run weekly with the new days of history. During cyber week, re-run if actuals miss the base case for three days.

## Operating rules

1. **Read-only.** The script reads two local files and prints a plan. It never touches an ad account. Never change a budget, campaign or bid unless the user approves that specific change. When they do, make it, read the setting back, and compare the next day's MER with the plan row.
2. **MER and contribution, never platform ROAS.** If someone asks to plan from platform ROAS, explain why not and convert the goal into a margin floor or a target MER. The script refuses a ROAS target (exit 3) and ignores ROAS columns in the history.
3. **Say what is estimated.** Elasticities, the demand index and every projected number are estimates fitted on last year's days. Quote them with the data range they came from, and give the low/base/high spread rather than a single number.
4. **Use the business's definitions.** If the business has approved revenue and spend definitions, use them for `net_sales` and spend. The planner's own definition is net sales = gross sales − discounts, before returns/refunds, excluding tax and shipping. If the business's revenue is already net of returns, set the return rate to 0.
5. **Never invent economics.** Gross margin, shipping cost, fees, return rate, discount depth and sale dates come from the user or their finance data. If one is missing, ask. Don't guess.
6. Say which state each item is in: **planned**, **approved**, **applied**, or **verified against actuals**.

## Gathering the inputs

The planner reads two files. Their exact formats are in `DATA_CONTRACT.md`.

**1. `history.csv`: one row per day.** Last year's October 1 – December 31, plus this year's trailing 90 days up to today. Columns: `date, net_sales, total_orders, new_customer_orders`, plus one spend column per paid channel you run: `meta_spend`, `google_spend`, `tiktok_spend`, `other_spend` (leave out any you don't run).
- Sales and orders come from Shopify: Analytics → Reports → *Sales over time* by day (net sales = gross sales − discounts, before returns/refunds, excluding tax and shipping), and *Customers over time* or the first-time/returning orders report for new-customer orders. With the Shopify Admin API, sum orders by `processed_at` day in the store's time zone.
- Numbers must use `.` as the decimal separator (`1,234.56`, `$1,234.56` and `€1,234.56` are fine). Comma decimals (`2255,74`), `nan`, `inf` and exponents are rejected (exit 2, naming the file, line and column) rather than guessed. Headers are matched case-insensitively, with spaces and hyphens read as `_` (`Meta Spend` = `meta_spend`); a column that looks like spend or cost under any other name stops the run so it is never silently dropped.
- Spend comes from each ad platform's daily report: Meta Ads Manager (*Amount spent*), Google Ads (*Cost*), TikTok Ads Manager (*Cost*); rename each export column to its `*_spend` name (for example *Amount spent* → `meta_spend`), because the platforms' own header names are rejected rather than guessed. Any other column of numbers the planner doesn't recognise (for example a channel exported as *Facebook Ads*) also stops the run. Put affiliate, Pinterest, Snap and anything else in `other_spend`. Use the store's time zone everywhere.
- In type.com, pull these through the connected Shopify and ad-platform integrations. Read-only scopes are enough. Never ask the user to paste a key into chat.

**2. `economics.json`: unit economics and the goal.** Gross margin %, shipping and fulfillment cost per order, payment fee %, expected return rate, the planned BFCM discount depth and the days it runs (`sale_window`, early access included), and a target: a **contribution-margin floor** (recommended, for example "keep at least 12% of net sales after ads") or a **target MER**. Days outside the sale window are priced at full price; without a sale window every day of the plan is priced at the discount, which understates November margin if the sale is shorter. Optional: total budget cap, channel min/max shares, max day-over-day increase (default 30%, for learning-phase safety), the plan window (default Nov 1 – Dec 6) and cyber week (default Nov 26 – Dec 1).

## Running it

```bash
python3 scripts/plan_budget.py --history history.csv --economics economics.json [--csv plan.csv] [--json plan.json] \
    [--as-of YYYY-MM-DD] [--starting-daily-budget N] [--accept-demand-index]
```

`--as-of` is today's date (default: the system date), used only to warn when the history is stale; the sample run pins it. `--starting-daily-budget` sets the budget the ramp starts from (default: the last 14 days' average spend). `--accept-demand-index` plans even when the estimated demand index is outside 0.25–4.0, which otherwise stops the run.

`bash examples/run.sh` runs it on the bundled sample store, Fernhill Goods (fictional): a Shopify home and kitchen brand doing about $1.05M a year, spending about $950 a day on Meta, Google and TikTok at a blended MER of about 3.0, with a 25% off sale Nov 24 – Dec 1. Last BFCM it ran at a blended MER of 4.00 ($120,000 net sales on $30,000 of ads). The sample plan holds Black Friday spend about level with last year and keeps 12% of net sales after ads across the window.

What it does:

1. **Prices one dollar of sales.** Contribution per $100 of net sales after COGS, payment fees, shipping and returns, at the planned discount on sale days and at full price on the other days. **Breakeven MER** = 100 ÷ that. **Target MER** = 100 ÷ (that − your margin floor).
2. **Reads last year's curve.** It shifts last year's days so Black Friday lines up with Black Friday (364 days for 2025 → 2026), then shows each phase's share of the window's revenue, its MER and its best day.
3. **Estimates diminishing returns per phase.** A log-log fit of daily sales on daily spend (last October, warm-up, cyber week, taper), printed with the days and spend range it was fitted on. Weak fits fall back to 0.5 and are flagged. A fit outside 0.20–0.70 is clamped to the bound and labelled **assumed (clamped from X)**, not fitted, and listed under Warnings; if the cyber-week curve is assumed, the report says the Black Friday budget rests on it. A sensitivity line shows the same budget at elasticity 0.40. A **demand index** compares this year's last 28 days against last October's curve, so the plan scales with this year's growth. Days with no ad spend are left out of both sides of that ratio and counted; the report warns when more than 20% were left out or the index is outside 0.5–2.0, and stops (exit 2) outside 0.25–4.0 unless you pass `--accept-demand-index`.
4. **Builds the daily plan.** Spend goes where the next dollar earns the most contribution (a full-price dollar of sales is worth more than a sale-day one), until the next dollar would lose money, the window's margin hits your floor, or the cap runs out. Then: never below today's run rate, never more than the max daily increase (so the ramp starts days before Thanksgiving), never more than a 50% drop in a day (whole dollars, rounded so both limits hold), and split across channels by this year's mix held inside your min/max.
5. **Projects every day** in low (0.85x demand), base and high (1.10x) scenarios: net sales, MER and contribution after ads, plus per-phase and window totals and new-customer cost.
6. **Sets guardrails for every day** from that day's own plan MER: pull back if MER to date is below the higher of breakeven MER and plan MER × 0.85 by the check time; scale if above the higher of target MER and plan MER × 1.10 two days running. Every pull-back line sits below its day's plan MER and every scale line above it, so an on-plan day never trips a pull back. If a day is planned at or below breakeven, its pull-back line stays below the plan and the report warns that the day is planned to lose money. The daily table and `--csv` carry each day's lines (`pull_back_mer`, `scale_mer`, with breakeven and target MER and check times); the phase table shows their range.
7. **Warns** when the plan can't be trusted or can't work: platform-ROAS columns ignored, history too short, stale (ending more than 14 days before today or the plan start) or missing 7+ of the last 28 days, ads paused in the last 14 days (the ramp then starts from the last 14 days with spend, or `--starting-daily-budget`), zero-spend days left out of the demand index, an assumed elasticity, breakeven MER unreachable at this discount, target unreachable, days planned below breakeven, budget cap below the ramp minimum, or days where spend runs past twice last year's (extrapolated).

Limits: there is no organic baseline. Every projected sale is tied to ad spend, so the model projects $0 of sales at $0 spend; read the projection as paid-driven demand, not total store revenue. Full-price days use last year's non-sale AOV on the matching days (or `full_price_aov`); sale days use last year's cyber-week AOV (or `bfcm_aov`).

Exit codes: 0 means the plan was written, 2 means invalid input (with the reason: unparseable or non-finite number, unrecognised spend column, Black Friday not a Friday, implausible demand index, and the checks in `DATA_CONTRACT.md`), 3 means the target was platform ROAS and the plan was refused.

## Acting on the report

Present the headline numbers, the warnings, and the daily table. Then, each with the user's approval:

1. **Settle the discount and the goal.** If breakeven MER is above what last year's cyber week delivered, last year's peak spend loses money at this discount: the plan cuts it back, and the discount may be too deep. Run Promo Margin Guard and re-plan. Agree the margin floor with whoever owns the P&L.
2. **Prospect in October.** Keep spend at or a little above today's run rate through October and early November to fill retargeting pools and email/SMS lists cheaply. The plan holds today's run rate as its floor for this reason.
3. **Load the plan into the pacing tools.** Use the monthly totals as Budget Pacer commitments and the Meta column as Meta Budget Scaling's daily budget path. Set budgets in the ad platforms only after approval, and read them back.
4. **Run the guardrails daily.** At the check time, compute MER to date from Shopify net sales and the sum of every platform's spend. Pull back or scale by that day's lines in the daily table. Never use a platform's own ROAS for the call. A monitor such as BFCM Live Pulse can read the same lines from today's row of the `--csv` plan (`pull_back_mer`, `scale_mer`, `check_times`).
5. **Re-plan with actuals.** Add the new days to `history.csv` and re-run weekly in November, and any time actuals miss the base case by more than 15% for three days running.

Full phase strategy, the MER-vs-ROAS argument, data-pull steps and the hand-off formats are in `references/playbook.md`.

## Handoffs

Exactly what each downstream skill takes from this plan:

| To | File | Notes |
| --- | --- | --- |
| BFCM Live Pulse | the `--csv` daily plan | Used unchanged. It reads today's row: the budgets, projected net sales and plan MER, and the guardrail columns (`breakeven_mer`, `target_mer`, `pull_back_mer`, `scale_mer`, `check_times`). |
| Holiday Shipping Cutoffs | the `--csv` daily plan, plus `--aov <planner AOV>` | It turns `proj_net_sales_base` into orders with the AOV printed under "Unit economics"; without `--aov` it exits 2. |
| Holiday Support Macros | the `--json` plan | Its `rows` give each day's projected orders. Pass **last season's** orders only as history; remove this season's dates. |
| BFCM Cohort Quality | `economics.json` | Optional; reuses the margin inputs. |
| Budget Pacer | the monthly totals under "Hand-off" | Each month's total is that month's committed spend; the daily rows are campaign bounds. December covers only the window's days. |
| Meta Budget Scaling | the Meta column of the daily plan | Its daily budget path, with the max daily increase as the step limit. |

## Files

- `scripts/plan_budget.py`: the planner. Local files only, deterministic output.
- `DATA_CONTRACT.md`: the `history.csv` and `economics.json` formats, validation rules, every formula and every `--csv` column.
- `references/playbook.md`: MER vs ROAS, pulling history from Shopify and the ad platforms, phase strategy (October prospecting, warm-up, cyber week, taper), guardrail operation, and hand-off to Budget Pacer and Meta Budget Scaling.
- `examples/run.sh`, `examples/expected_output.txt`, `examples/data/`: sample run. `examples/make_fixtures.py` regenerates the sample data.
- `tests/run_tests.sh`: 22 end-to-end checks. No network needed.