Acquisition
Acquisition by channel
Spend, sessions, orders, CAC, ROAS and conversion rate per channel for the chosen Window, read from the production dataset. Unknown is a row like any other, carrying its real orders and revenue, and every row resting on fewer than five orders is marked and still shown — so a channel that does not work reads differently from a channel whose orders are not being attributed.
This page's coverage is wider than /company's on purpose. It runs on analytics.channel_reporting, which carries sessions and ad spend from 27 May 2026 — before the first order on 16 June 2026 — because a business buys traffic before it sells anything. /company runs on analytics.orders and ends at the last order date. So All time here opens earlier and can end a day later, and the two pages are describing the same trading with different bounds rather than disagreeing.
What tripped on this page
The acquisition flags, and only those: the same rules the panel on /company runs, filtered to the section this page shows. Each is computed over the Window above, against the same rowset the table below divides, so a card and the row it names cannot print different numbers.
WARNING 2
These are the acquisition cards from /company, computed here over this page's Window rather than that page's. The two controls do not carry the same bounds and are not meant to: /company ends its ranges at the last order date and this page ends at the last day the marketing mart carries, so All time here opens on 27 May 2026 against 16 June 2026 there. Each panel states the window it ran over, which is what keeps two honest answers from reading as a disagreement.
Every card names the counts its rule divided and the bar it crossed, and none of them says what to do about it — that is the line ADR 0007 draws between a computed flag and generated prose. A CAC resting on a single new order is marked, not hidden: the detail line prints the denominator, the same way a row under five orders is marked and kept in the table below.
This window, blended
Every channel added together, for the Window above. These are the figures a per-channel column cannot carry: MER is only meaningful across all channels, and the blended CAC and conversion rate are what the table below decomposes.
Window: Last 30 · 7 Jul 2026 → 5 Aug 2026 — 30 of 30 days carried marketing data, across 12 channels.
MER appears once, here, and never as a column. It is roas's arithmetic — Sales ÷
spend — but the registry keeps the two as separate names because MER describes the whole
business and ROAS describes a channel. A per-channel MER column would be that channel's
ROAS under a name claiming to speak for everything, which is how a dashboard ends up
disagreeing with itself.
Blended over this window: £2,185.89 ÷ £6,324.70 = 0.35 — every channel's Sales over every channel's spend, summed before dividing.
These tiles carry no prior-period pills. The honest-comparison rule — a comparison is
shown only where the whole prior period sits inside trading history — is applied by the
headline tiles on /company, against the trading history in analytics.orders.
This strip is the window's blended arithmetic and the decomposition below is what the page
is for, so it states its window and stops. Each tile still resolves through
components/presentationState.js, so a window that narrows the order count below the
five-order bar marks these figures on its own.
By channel
Channel grain, not channel group: folding Paid Search - Brand, Generic and Uncategorised into one row would hide the distinction this page exists to draw. Every ratio is summed then divided over the Window above, and a ratio with no usable denominator prints an em rule with its reason on the same row.
The channel rows sum to the headline order count for the same days, with Unknown counted
in — measured against analytics.orders over the same window rather than asserted, so a
break in either mart shows up here as a difference instead of as a plausible wrong number.
† fewer than 5 orders in the window, so every ratio on the row rests on that denominator. The row is still shown in full, in its natural position — a thin figure is marked, never hidden. 7 of 12 rows on this table carry it.
Reading the em rules. A — is a rate whose denominator is zero, and the Basis column
on the same row names which denominator went missing. It is never £0, never 0% and never
∞. Performance Max is the live case on the all-time window: £287.23 of spend and no new
orders at all, so cost per first order has nothing to divide by. Printing £0 there would
say acquisition was free and printing ∞ would say it was infinitely expensive; the data
supports neither, and only the row itself — spend beside a zero — supports anything. The same
rule gives ROAS an em rule on every channel with no spend, and AOV and repeat order share an
em rule on a channel with no orders. Those two figures are a worked example on the all-time
window, not live values; the row above is the live one.
Why every ratio column is text, and why the table does not sort. A NULL in a numeric column renders as an empty cell, which reads as "nobody filled this in" rather than "this cannot be computed". Formatting each ratio in the fence is what lets the em rule be an em rule; the columns are right-aligned so the table still reads as figures. The cost is that a text column sorts alphabetically, which would rank £1,529.89 above £118.97, so column sorting is off here and the rows stay in the order the fence states — most orders first, Sales breaking a tie.
new_customer_conversion_rate divides by total sessions, not new sessions, and the
mart's new_sessions column is deliberately not on this table. A GA4 first-visit session
and a customer's first order are different populations — a first-time buyer often converts
on a returning session — so dividing new orders by new sessions divides two unrelated
populations. The registry says so explicitly and the column is left out rather than left
available to be picked up by mistake.
Conversion rate is Shopify order rows over GA4 sessions, both taken off the same mart
row. GA4's own purchase event is never used as an order count: it double-fires from
3 July 2026 and reads roughly twice the true rate.
Unknown is a row, not a residual
Unattributed orders are real orders with real revenue. They are shown as their own channel, with their own sessions, orders and Sales, rather than netted off the totals or redistributed across the channels that can be named.
Unknown carries 41 orders, 783 sessions and £494.20 in this window — 30.1% of every order on the page.
That share is the point. channel_reporting_reattributed exists in the dbt project and is
deliberately not what this page reads: it carries no analytics tag, so it is never
promoted to the production dataset, and reattribution is deferred on purpose (PRD044 §7).
Redistribution earns its keep once there are more channels to redistribute credit between,
which is not the case with nine channels carrying an order between them. Until then, Unknown
as its own row keeps pressure on the mapping gap rather than smoothing it away — and it is
the same count the unattributed-orders flag on /company trips on, so the two
pages cannot disagree about how much of the business cannot be judged.
Nothing on this page is suppressed. Thin rows are marked and kept, Unknown is shown with
its real figures, and a rate with no denominator prints an em rule with its reason.
Suppression is reserved for figures that would be actively misleading (CONTEXT.md,
Small-n marking), and a channel resting on one order is thin, not misleading — hiding it
would remove the only evidence that the channel exists.
The deployed site is static with no runtime connection to BigQuery, so this rebuild is the data refresh, and the stamp above is the only on-page evidence that the numbers moved.