Skill · Sales and Marketing · Finance and Operations

BFCM Live Pulse

Turn hourly sales and ad spend into a BFCM status update with sales pacing, budget guardrails, checkout alerts, code-use anomalies and stock risk.

Created by type.com

What it does

  • Reads the BFCM Holiday Budget Planner's daily CSV alongside hourly Shopify sales and cumulative ad spend, and paces today's sales against the plan
  • Computes blended MER (Shopify net sales ÷ all ad spend, never platform ROAS) with sales and spend cut off at the same minute, judged against the plan's lines at check times
  • Flags stalled orders, checkout conversion drops, falling AOV, unusual code use and low stock cover, and reports recovered problems once
  • Blocks budget calls (DATA INCOMPLETE) when a planned channel's spend is missing, stale or reads $0, and labels every SCALE or PULL BACK line "Proposed (needs approval)"
  • Produces a concise Slack-ready draft plus full JSON, with quiet mode for clean hours. Read-only: it never posts, and never changes a budget, discount or store setting

Before you start

  • Local exports for every completed hour today and last year's matching day; net sales = gross sales minus discounts, before returns, excluding tax and shipping
  • Cumulative spend so far today for every active ad account, with read times that carry a UTC offset; optional discount code use by hour and stock on hand
  • Scheduling, live data retrieval and Slack delivery must be set up separately; this skill deploys none of them. Run the sample and read DATA_CONTRACT.md first
  • Validation: 28 offline end-to-end tests on fictional data; no live store or Slack integration tested
  • 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 Live Pulse · Fernhill Goods · Black Friday, Fri Nov 27 · 2:00 pm ET ACTION NEEDED: 1 critical, 2 warnings, 1 resolved. Pacing to $31,128, 0.8% under base $31,393 (low $26,684, high $34,533). MER to 1:20 pm 4.32: HOLD (noon check passed; next check 6 pm).

Alerts

  1. CRITICAL MAYA20: 40 uses in the 1 pm hour (40 of 43 orders) vs its usual 0.6/hour (67x); 55 uses today. Likely leaked. Next: pause it or set a usage limit and once per customer now; check it can't combine with the sale (BFCM Offer & Discount Config QA).
  2. WARNING AOV $73.09 over noon–2 pm vs $88.27 last year, same hours (-17.2%). MAYA20 was on 52 of these 70 orders. Next: check whether MAYA20 stacks with the sale (BFCM Offer & Discount Config QA).
  3. WARNING Linen Apron: 46 left, 30 sold in the last 3 hours (10/hour), 4.6 hours of cover (out about 6:35 pm). Next: pull it from ads and emails or accept the sell-out; reorder with Inventory Stockout Risk.
  4. RESOLVED Checkout conversion fell to 37.1% in the 11 am hour (61.2% the 3 hours before); back to 64.3% at noon and 60.6% at 1 pm.

Numbers to 2:00 pm (14 of 24 hours; LY = Fri Nov 28, 2025, same hours)

Net sales $14,344 · LY $13,050 (+9.9%)
Orders    175 · LY 148 (+18.2%) · AOV $81.97 · LY $88.18 (-7.0%)
Spend     $3,004 of $6,672 (45.0%) · 102% of expected by now
          Meta $1,714 103% · Google $895 101% · TikTok $395 102%
MER       4.32 to 1:20 pm · pull back < 4.00 · scale > 5.18 · plan 4.71
Funnel    3.0% of 5,833 sessions ordered · LY 2.9%

Read-only: nothing was changed. Expected spend assumes budgets pace like last year's hourly sales.

AI for marketing teams: a practical guide

Browse the technical files
---
name: bfcm-live-pulse
description: Turn hourly Shopify sales, blended ad spend and the BFCM Holiday Budget Planner into a concise BFCM status update. Flag stalled orders, checkout drops, unusual code use, AOV changes and stock risk. Produces a Slack-ready draft; never posts or changes budgets automatically.
---

# BFCM Live Pulse

Use during BFCM to answer “are sales on plan, and what needs attention now?” This package runs on local exports. It does not install an hourly automation, fetch live data or post to Slack. The accompanying sample uses fictional Fernhill Goods data.

## Setup

Get every package file with its original relative path and verify published hashes when available. Never reconstruct scripts from an abbreviated skill description. Run from this folder with Python 3.8+ and Bash; no Python dependencies. Try `bash examples/run.sh`, compare with `examples/expected_output.txt`, then run `bash tests/run_tests.sh`. Runtime verification details are in QA.md.

Read DATA_CONTRACT.md before gathering data. Use read-only connected tools where available, or ask for exports. Confirm the store, currency, time zone, plan, covered advertising accounts and data freshness. Never ask for API keys in chat. Do not substitute platform-attributed revenue for Shopify net sales.

## Run

```bash
python3 scripts/live_pulse.py --plan plan.csv --hourly hourly.csv \
  --ly-hourly ly_hourly.csv --spend spend_today.csv \
  --discounts discount_usage.csv --inventory inventory.csv \
  --config config.json --as-of "2026-11-27 14:00" \
  --state pulse-state.json --json pulse.json
```

Discounts, inventory, config, state and JSON output are optional. Supply an explicit as-of date/time through the flag or config. All CSVs use the same store currency, with `.` as the decimal separator: comma decimals, `nan`, `inf` and European-format files exit 2 naming the file, line and column. Spend read_at needs a UTC offset or `Z` unless config sets `"spend_timestamps_tz": "store"`. MER compares sales and spend to the same minute, the earliest spend read time; budget calls also need the spend reads within 30 minutes of each other, and need the raw MER (spend as read, not scaled back to the cut-off) to point the same way; if the two disagree the call is HOLD. Whenever MER is above the scale line, total spend behind its pace band or any budgeted channel behind its own pace band makes the call HOLD ("spend is behind"), because a lagging spend read makes MER read high; that reading is not saved as a check, so it can never confirm a later SCALE. Spend that lags by less than the band (20% by default, `spend_pace_band_pct`) on every channel can't be told apart from genuinely slow spending and still counts as on pace, so refresh spend before acting on a SCALE proposal.

Net sales everywhere means gross sales − discounts, before returns/refunds, excluding tax and shipping (the Planner's definition). Hourly sales are therefore never negative; a negative hour exits 2. Shopify's own *Net sales* report column is after returns, so don't export it: export gross sales minus discounts per hour.

1. Gather fresh inputs, reconcile totals and ensure every spending channel is represented. Missing channels outside the plan cannot be detected automatically.
2. Run the script, retain JSON for full detail, and inspect `notes`, `not_checked` and `defaults` before sharing the draft.
3. Lead with status and exceptions, then the numbers and recommended investigation. Treat code-use spikes as a hypothesis, not proof of leakage. Confirm whether promotions or planned sends explain them before proposing a code change.
4. Budget calls are recommendations, printed as "Proposed (needs approval):". Missing planned channels, stale spend, or a budgeted channel reading $0 after the first check yield DATA INCOMPLETE and block budget actions and checkpoint writes. If a channel is paused on purpose, list it in config `paused_channels`. Never apply suggested changes without authorization for the specific change.
5. To automate, first agree on cadence, destination, time zone, covered dates and who owns responses. Configure retrieval and posting separately using available type.com tools; a local script is not a deployed monitor. Post only when that workflow is authorized. Retry safely and deduplicate by store/date/hour.

## Interpretation and limitations

Pace extrapolates today's sales using last year's matching day's hourly revenue distribution. It is a scenario, not a forecast confidence interval; changing offer timing or send schedules can invalidate it. Last year's date comes from the planner or defaults to 364 days earlier; confirm holiday alignment. MER is net sales divided by all recorded ad spend, not profit or platform ROAS.

Guardrails come from the planner. When absent, the script derives pull-back at max(breakeven MER, 0.85 × plan MER) and scale at max(target MER, 1.10 × plan MER); these are heuristics. Checks default to noon and 6 pm. At or after a check, the latest snapshot is evaluated and replaces that checkpoint's state. SCALE requires this and the immediately preceding checkpoint above their respective thresholds. State is local, retains 30 checks and has no cross-worker locking; use one writer per store and preserve it between scheduled runs. It is not an exact historical reconstruction of a missed check.

Use `--quiet` to suppress clean updates outside the hour following each summary time. Active/resolved alerts, budget actions and incomplete spend still print. Quiet mode is not delivery deduplication: repeated runs can print the same alert. The message targets 1,800 characters and may omit notes; always examine JSON before sending.

Thresholds in config are tunable rules of thumb, not industry benchmarks. Stock cover assumes the last three hours' sales rate continues and excludes replenishment. Discount input omits zero-use rows, so incomplete exports can appear normal; validate completeness upstream. Inventory has no timestamp column; verify freshness upstream. The timezone name is descriptive: provide an explicit utc_offset correct for that day; DST is not inferred, and a 25-hour DST-change export must be merged to 24 rows.

Use BFCM Offer & Discount Config QA to investigate stacking, Inventory Stockout Risk for stock planning, and BFCM Holiday Budget Planner to revise the plan. Return a draft report plus input coverage, freshness and proposed actions; clearly distinguish detected, recommended, applied and verified. Nothing is applied by this package.