Marketing Operations · Reference Doc · v1.0 (2026-05-19)
How every number on the dashboard is computed.
Plain-English formulas + worked examples for every metric the Marketing Ops dashboard surfaces. Use this when you need to defend a number, reconcile against another tool, or understand what's not being counted.
Contribution Margin
The headline KPI on the Paid Ads dashboard. The number Ryan uses to decide how much to reinvest in ads next month.
Contribution Margin = Σ (per-order Product GP − rep commission) − ad spend − marketing overhead
Where:
- Per-order Product GP = Product Revenue − COGS. For offline orders, this comes from
workstation_meta.financials.grossProfit(already nets out Shopify fees + freight margin + card fees from the workstation save flow). For online direct purchases, it's recomputed from Shopify line items + variantinventoryItem.unitCostat read time. - Rep commission =
lifecycle.commissionAmount, set by the rep on the Workstation Commission card OR auto-computed from the lead-source × payment-terms grid (Outbound 37% Pay in Full / Inbound 27% / Shared 17%). Code-attributed online orders compute commission the same way once admin picks leadSource. - Ad spend = sum of platform-reported spend across Google Ads + Meta in the window, from the nightly ETL.
- Marketing overhead = the monthly
MARKETING_MONTHLY_OVERHEADenv var × number of months in the window. Captures fixed costs that don't scale with ad spend (Malt retainer, Hotjar, etc.).
Worked example
Scenario: May 2026 (1 month). 8 sold orders, total Product Revenue $42,500, COGS $26,100, total commission $2,800. Ad spend $4,200. Overhead $1,800/mo.
Math:
- Per-order Product GP sum = $42,500 − $26,100 = $16,400
- Less commission = $16,400 − $2,800 = $13,600
- Less ad spend = $13,600 − $4,200 = $9,400
- Less overhead = $9,400 − $1,800 = $7,600 Contribution Margin
The hero KPI shows $7,600 (post-overhead). The Show-Your-Work drawer breaks out each subtraction step.
Common reconciliation questions
- Why doesn't my CM match Meta Ads Manager's "purchases × value"?
- Meta reports gross order value, not Product GP. CM nets out COGS, commission, Shopify fees, and marketing overhead — Meta knows none of those.
- Why is the CM number different from yesterday?
- Three causes typically: (1) new orders closed today, (2) admin set
leadSourceon a code-attributed sale and commission auto-computed, (3) workstation save normalized install fields (the install-status=none defense from 2026-05-19). - How is per-channel CM computed?
- Same formula, restricted to orders attributed to that channel via UTM journey last-paid touch. Per-channel does not allocate fixed overhead — that wouldn't be methodologically clean. Hero CM is post-overhead; per-channel is pre-overhead.
Product GP vs Order Total
These two numbers always differ. Knowing why matters when reconciling against Shopify or QuickBooks.
Product Revenue = Shopify subtotal (line item totals, post-discount, pre-tax, pre-shipping)Product GP = Product Revenue − COGSOrder Total (what customer paid) = Product Revenue + freight + taxes + card fees − discounts + balance payments
Why Product GP is the right metric for marketing decisions: ad spend influences which products move; it doesn't influence freight pricing, sales tax rates, or Shopify's fee schedule. Mixing those in would obscure the per-ad signal.
Where Shopify fees + freight margin show up: they're tracked in the Order Economics section of each order's detail drawer, and they roll into workstation_meta.financials.grossProfit which feeds into Contribution Margin — so the dashboard's CM hero does include them. They're just kept out of the per-order Product GP column to keep that number purely about goods margin.
ROAS & CPL
Computed per campaign + per channel; surfaced on the Channel Breakdown and Campaigns tables.
ROAS = revenue ÷ spendCPL = spend ÷ conversions
Revenue source: the platform's own reported conversion value (Meta Pixel + Google Ads conversion tracking). We don't try to back into revenue from Shopify orders → spend allocation here — that's on the roadmap.
Conversion source: for now, platform-reported. The roadmap adds a second "Shopify-attributed" column to the Campaigns table for comparison against actual closed orders. Today, all conversion + revenue numbers in the campaign tables are platform claims, not Shopify truth.
ROAS sanity range: 2.0 means the channel paid for itself in revenue (but probably not in profit after COGS). 4.0+ usually means real contribution. Use the Insights → By-Source table to see avg deal size + days-to-close per source — high ROAS with long days-to-close on a small avg deal might still be a loss leader.
Source groups
Every order is categorized into a source group based on how the customer entered the funnel. Drives the Source filter on the Orders page + the Insights → By-Source table.
| Source group | Trigger | What it means for attribution |
|---|---|---|
online | Direct Shopify checkout — no RFQ, no form. Including code-attributed online orders. | Shop-direct conversion. UTM journey shows the last paid touch. |
form | Storefront contact / vertical-landing / bulk-quote / contact form submission. | Inbound lead → quote → close. Form name preserved on the lead record. |
chatbot | Conversation handed off to the rep dashboard. | Pre-purchase conversation drove the inquiry. |
sample | Sample-request order ($0 Shopify checkout via the request-sample form). | Top-of-funnel material request that later converted. |
email | Inbound email caught by Abby's intake or manually entered by the rep. | Inbound conversation without a form trail. |
inbound (other) | Catch-all for inbound sources without a more specific bucket. | Usually phone calls or referrals. |
Repeat customer rate
One of the four Insights tiles on the Paid Ads dashboard. Defined as:
Repeat Customer Rate = (orders in window whose customer email has a prior order) ÷ (total orders in window) × 100
How it's computed:
- Walk every
lifecycle:*record withstatus='sold', all time. - Build a map: customer email → earliest
dateSold. - For each order in the active date window, compare its
dateSoldto the earliest date for that customer. - If earliest < this order's
dateSold→ counts as a repeat.
Edge cases:
- Orders with no customer email (rare, mostly manual offline records) are excluded from both numerator and denominator.
- "Prior order" means any earlier sold order anywhere in time — not restricted to paid-attributed orders.
- Customers whose first ever order falls within the active window count as first-time, not repeat.
By-Source metrics (Insights table)
Per source group: orders count + average deal size + average days-to-close.
avg deal size = Σ (order revenue) ÷ order countavg days to close = mean (lc.dateSold − lead.createdAt) in days
"Days to close" sample size caveat: only orders with a matching lead:{ref} record contribute to the days-to-close average. Orders without a lead record (mostly online direct purchases that didn't flow through universal-lead-capture) are still counted in the deal-size + order-count columns but skipped from days-to-close. Sample size is reported separately in the API response (daysSampleSize field on each row).
Top verticals (Insights table)
Aggregates sold orders by vertical (saunas, k12, corporate, etc.), reports count + revenue + share %.
Vertical source: set on the lifecycle record as lc.vertical or on the workstation as wm.vertical. Online direct purchases without a vertical tag are excluded from this table (no signal to surface). Use the Vertical chip on the Orders page to filter the table to a single vertical.
By-State (Insights table)
Geographic breakdown by shipping state, online direct purchases only in v0.
Why online-only: offline orders store shipping addresses heterogeneously across lc.shipTo and wm.customer with different schemas. v1 will normalize across both. For now this table is most useful for understanding where Shopify direct-purchase traffic converts geographically — which is the primary marketing-targeting question.
State extraction: prefers shippingAddress.provinceCode over billingAddress.provinceCode — geographic targeting follows where the goods land, not where the card is. Top N states by revenue (default 10).
Budget burn pace + YoY comparison
On the Budget Plan page. Each ad-pool gets a two-bar strip: "Time passed this month" + "Spent this month".
time passed = day-of-month ÷ days-in-month × 100spent = mtd spend ÷ month target × 100
If spent > time passed by a meaningful margin → you're outpacing the budget; check whether to dial back or raise the cap. If spent < time passed → underspending the budget; usually a Pause-bid or limited eligibility issue.
YoY comparison sublines appear beneath each annual totals cell when there's a prior-year plan in KV. Green delta = up vs last year, red = down. New campaigns / pools without prior-year data show "New this year (no prior)".
Glossary
- Product Revenue
- Goods only — Shopify
subtotalPriceSet. Excludes freight + taxes + discounts. - COGS
- Cost of goods sold — sum of variant
inventoryItem.unitCost × quantityacross line items, plus admin-entered custom costs (drop-ship fees, state tax we can't avoid, etc.). - Margin %
- Product GP ÷ Product Revenue × 100. A pure goods margin; doesn't reflect freight, fees, or overhead.
- Shopify fees
- Sum of
transactions[].fees[].amountacross the order's payment transactions. When zero (manual / external transactions return null) we fall back to the Grow-plan rate (2.7% + $0.30). - Last paid touch
- The most recent UTM-tagged paid click in the customer's captured journey. Drives the Channel column on Orders + the per-channel breakdown on the dashboard.
- Assisted conversion
- True when there was a paid touch in the journey but it wasn't the last click. Surfaced as "(assisted)" in the Touches column on Orders.