Aurora 00: The Trap
Aurora Coffee Co. is a fast-growing direct-to-consumer coffee brand with two
product lines — Original (ground & whole bean) and Cold Brew
(ready-to-drink) — and four paid channels: TV, Search, Social, Display. It's
planning season. The CMO has to defend the budget to the CFO and answer one deceptively simple
question: what is each channel actually worth, and how should we reallocate? A normal
dashboard answers with correlations. This page — the hook of the six-part Aurora showcase,
mirroring nbs/aurora/00_overview.ipynb — shows why that answer is wrong in a way that
costs real money. Because Aurora's world is synthetic with a sealed answer key, every claim
here can be graded against ground truth.
🧭 Before you start
Prerequisites: Workshop 00–05 (Bayesian fluency assumed) — or equivalent. · Time: ~35–45 min reading + fit time if running (full series ≈ 3.5–4.5 h reading-only)
You will learn:
- Why correlation ranks Aurora's channels exactly backwards — the dashboard hero (Search, corr 0.85) truly returns $0.66 per $1, while the first cut (TV) returns $2.14.
- How three ordinary behaviors — demand-chasing spend, brand effects through awareness, and product cannibalization — mechanically manufacture misleading correlations.
- How the sealed synthetic answer key grades every estimate in the series, and why the causal-vs-naive gap is worth roughly $12M/year.
Run it:
nbs/aurora/00_overview.ipynb on GitHub.
Meet the business
Two years (104 weeks) of weekly data: revenue in $000s by product, spend by channel, plus a few business drivers (a category demand index, average price, and a brand-awareness survey with gaps). Average revenue is $839k/week, and media truly drives 26% of revenue — there is real money on the table either way.
Measured — the actual weekly series from the shared Aurora world
(nbs/builders/aurora.py, seeded; the same dataset every notebook in the series fits).
Top: revenue stacked by product — notice Cold Brew surging each summer exactly as Original dips.
Bottom: media spend by channel — notice Search and Social swelling into the winter peaks. Both
of those rhythms are about to matter.
The trap
The fastest way to "value" a channel is to correlate its spend with sales — that is what most dashboards quietly do. Do exactly that for Aurora, and put the result next to the true ROAS, which we know because we built this world.
Measured — left panel: Pearson correlation of weekly spend with total sales, computed on the seeded Aurora dataset (TV 0.29, Search 0.85, Social 0.69, Display 0.52). Right panel: the generative ground-truth ROAS — revenue per $1 of spend (TV 2.14, Search 0.66, Social 0.49, Display 2.11). The dashed line is breakeven. The ranking doesn't just shift — it inverts.
⚠️ Acting on the left chart
Search has the strongest correlation with sales (0.85) — the dashboard's hero. Its true ROAS is 0.66: it loses money. TV has the weakest correlation (0.29) — the dashboard's first cut. Its true ROAS is 2.14: it's the best investment Aurora has. A correlation-driven reallocation would pour money into Search and starve TV — exactly backwards. Notebook 5 prices that mistake at roughly $12M/year on the same total budget.
The sealed answer key
Definition: graded against ground truth
Aurora's data is generated by nbs/builders/aurora.py from explicit causal equations,
so the true incremental contribution and ROAS of every channel are stored alongside
the data. Every model in this series is graded against that answer key — the same discipline
as the pressure-testing stress series, where the
recorded scorecard shows that clean convergence diagnostics catch none of these
attribution failures.
The key says: Search is a demand-chasing mirage. Aurora's team bids harder on Search when demand is already high, so Search spend rides the same wave as sales — the latent demand signal and Search spend correlate at 0.94. The credit belongs to demand, not the channel. In notebook 1, an MMM reads Search at 2.51 demand-blind (still 1.04 even after a demand control); folding a geo-lift experiment into the likelihood collapses it to 0.66 — right on top of the true 0.66.
Meanwhile TV and Display are Aurora's brand engines — true ROAS 2.14 and 2.11 — but their effect flows almost entirely through awareness (98.8% and 96.7% of their true effect is mediated). They barely sell anything directly, which is why correlation — and, as notebook 3 shows, even a well-specified base MMM — underrates them.
Over the two years, the true incremental revenue ledger reads: TV $13.0M, Display $6.0M, Search $2.2M, Social $1.5M. The dashboard's ranking is that ledger upside down.
Three seeded pathologies
Three things are breaking the correlation, and each was deliberately seeded into the world so the framework has something real to find. Toggle between them:
Measured — z-scored latent demand against z-scored Search spend from the seeded Aurora world. They are nearly the same curve (correlation 0.94): the team bids on Search when demand is already high, so Search inherits demand's credit.
| Pathology | What it does in the data | The cost if ignored | Fixed in |
|---|---|---|---|
| ① Unobserved-demand confounding | Latent demand drives both Search/Social spend and sales (demand–Search-spend correlation 0.94). | Search looks like the hero (corr 0.85) while truly losing money (ROAS 0.66); budget flows to a channel that returns $0.66 per $1. | 01 · Causality — DAG, the demand-proxy control, experiment calibration. See also stress 03. |
| ② Mediation (TV → awareness → sales) | TV and Display sell almost nothing directly; they build awareness, and awareness sells (98.8% / 96.7% of their true effect is indirect). | TV's value hides one step upstream; the base model reads it weak and the dashboard cuts the brand's best channel (true ROAS 2.14). | 03 · Extended Models — NestedMMM decomposes direct vs awareness-mediated effects. |
| ③ Product cannibalization | Cold Brew steals from Original every summer — the two products' weekly revenues correlate at −0.90. | Single-outcome models credit media for "growth" that is partly one product eating the other; portfolio-level ROAS is overstated. | 03 · Extended Models — MultivariateMMM tests the cross-effect directly; the verdict: direct cannibalization is negligible (ψ ≈ 0), and the co-movement is a shared demand wave. |
💡 None of this is exotic
Bidding harder when demand is high is the job description of a performance team. Brand channels working through awareness is the textbook theory of TV. Product lines competing for the same stomach is every multi-SKU brand. Aurora isn't a pathological edge case — it's an ordinary business whose data-generating process breaks the assumptions a correlation silently makes. The stress-test scorecard measures how often the same breaks fool a full MMM, not just a dashboard.
How chasing inflates a channel
Why exactly does demand-chasing manufacture correlation? Suppose sales are \( y_t = \alpha + \gamma\, d_t + \beta\, s_t + \varepsilon_t \) where \(d_t\) is latent demand and \(s_t\) is spend — but the team sets spend as \( s_t = \mu + \kappa\, d_t + u_t \), chasing demand with intensity \(\kappa\). A naive regression of sales on spend then recovers not \(\beta\) but \( \beta + \gamma\,\mathrm{cov}(d_t, s_t)/\mathrm{var}(s_t) \): the channel inherits demand's effect in proportion to how hard it chases. Drag the slider and watch a money-losing channel (true ROAS fixed at 0.66, Aurora's Search) turn into a dashboard hero:
Illustrative simulation (seeded) — not the Aurora dataset. 104 simulated weeks where latent demand moves sales, spend chases demand with the intensity you set, and the channel's true effect never changes. The red line is the naive spend–sales fit; the dashed dark line is the true incremental effect (slope 0.66). At \(\kappa = 0\) they nearly coincide; near \(\kappa = 0.85\) — Aurora's actual Search chasing intensity in the generator — the naive line is several times too steep and the correlation reads like a hero channel.
The tour: notebooks 01–05
The rest of the series undoes the trap one pathology at a time, on this same dataset, and ends with a reallocation worth real money. Read in order — the story builds — but each chapter regenerates the data and fits what it needs, so any one stands alone.
Causality
Can we trust the number? The DAG, which controls close the back-door (and which "controls" make things worse), bad-control detection, and folding a geo-lift experiment into the likelihood.
The Base MMM
What is each channel worth? Fit the Bayesian MMM: contributions and ROAS with credible intervals, marginal ROAS, what-if scenarios, and the diagnostics that validate the sampler (not the causal claim).
Extended Models
What is TV really doing? NestedMMM splits TV's effect into direct vs awareness-mediated; MultivariateMMM puts the Cold Brew cannibalization claim through an honesty test — and finds direct cannibalization negligible (ψ ≈ 0); the co-movement is a shared demand wave.
Reporting
How do we put this in front of the board? A themed, self-contained HTML report from
MMMReportGenerator — decomposition, ROAS, diagnostics, response curves.
Unified Workflow
Put it together: causal checks, calibrated fit, mediation-aware attribution — ending in a defensible budget reallocation compared head-to-head against the dashboard plan.
What to take away
What to take away
- Correlation ranks Aurora's channels exactly backwards. The hero (Search, corr 0.85) truly returns $0.66 per $1; the first cut (TV, corr 0.29) truly returns $2.14.
- Three ordinary behaviors break the dashboard: spend that chases demand, brand channels that work through awareness, and products that cannibalize each other (Cold Brew vs Original at −0.90).
- Demand-chasing manufactures correlation mechanically — a channel inherits demand's credit in proportion to how hard it chases, with its true effect unchanged.
- Ground truth is the grading standard: every estimate in this series is checked against the sealed answer key, the same discipline as the stress series.
- The stakes are priced: on the same budget, the causal plan beats the correlation plan by roughly $12M/year (notebook 5).
💡 Run it yourself
This page mirrors nbs/aurora/00_overview.ipynb, generated by
nbs/builders/build_aurora_notebooks.py on the shared world in
nbs/builders/aurora.py. The series README (nbs/README_aurora.md) has run
instructions — regenerate with uv run python build_aurora_notebooks.py, execute
with jupyter nbconvert --execute. Source:
github.com/redam94/mmm-framework.