{"name":"7-figure store workflow","nodes":[{"id":"8f1a2b3c-1000-4a00-8a00-000000000001","name":"Every Morning 07:00","type":"n8n-nodes-base.scheduleTrigger","typeVersion":1.2,"position":[0,300],"parameters":{"rule":{"interval":[{"field":"days","daysInterval":1,"triggerAtHour":7}]}}},{"id":"8f1a2b3c-1000-4a00-8a00-000000000002","name":"Fetch Shopify Orders","type":"n8n-nodes-base.httpRequest","typeVersion":4.2,"position":[260,40],"parameters":{"url":"https://STORE.myshopify.com/admin/api/2024-10/orders.json","sendQuery":true,"queryParameters":{"parameters":[{"name":"status","value":"any"},{"name":"financial_status","value":"paid"},{"name":"created_at_min","value":"={{ $now.minus({ days: 1 }).startOf('day').toISO() }}"},{"name":"created_at_max","value":"={{ $now.startOf('day').toISO() }}"},{"name":"fields","value":"id,email,total_price,total_discounts,total_shipping_price_set,line_items,customer,created_at"},{"name":"limit","value":"250"}]},"options":{}}},{"id":"8f1a2b3c-1000-4a00-8a00-000000000003","name":"Fetch Shopify Refunds","type":"n8n-nodes-base.httpRequest","typeVersion":4.2,"position":[260,200],"parameters":{"url":"https://STORE.myshopify.com/admin/api/2024-10/refunds.json","sendQuery":true,"queryParameters":{"parameters":[{"name":"created_at_min","value":"={{ $now.minus({ days: 1 }).startOf('day').toISO() }}"},{"name":"created_at_max","value":"={{ $now.startOf('day').toISO() }}"},{"name":"fields","value":"id,order_id,created_at,transactions,refund_line_items"}]},"options":{}}},{"id":"8f1a2b3c-1000-4a00-8a00-000000000004","name":"Fetch Meta Ad Spend","type":"n8n-nodes-base.httpRequest","typeVersion":4.2,"position":[260,400],"parameters":{"url":"https://graph.facebook.com/v21.0/act_ID/insights","sendQuery":true,"queryParameters":{"parameters":[{"name":"fields","value":"campaign_name,spend,impressions,clicks,ctr,frequency,purchase_roas,actions"},{"name":"level","value":"campaign"},{"name":"date_preset","value":"yesterday"},{"name":"limit","value":"200"}]},"options":{}}},{"id":"8f1a2b3c-1000-4a00-8a00-000000000005","name":"Fetch Google Ads Cost","type":"n8n-nodes-base.httpRequest","typeVersion":4.2,"position":[260,560],"parameters":{"method":"POST","url":"https://googleads.googleapis.com/v18/customers/CUSTOMER_ID/googleAds:searchStream","sendBody":true,"specifyBody":"json","jsonBody":"={{ JSON.stringify({ query: \"SELECT campaign.name, metrics.cost_micros, metrics.conversions, metrics.conversions_value, metrics.clicks FROM campaign WHERE segments.date DURING YESTERDAY\" }) }}","options":{}}},{"id":"8f1a2b3c-1000-4a00-8a00-000000000006","name":"Merge Store Financials","type":"n8n-nodes-base.merge","typeVersion":3,"position":[520,120],"parameters":{"mode":"combine","combineBy":"combineByPosition","options":{}}},{"id":"8f1a2b3c-1000-4a00-8a00-000000000007","name":"Merge Paid Spend","type":"n8n-nodes-base.merge","typeVersion":3,"position":[520,480],"parameters":{"mode":"combine","combineBy":"combineByPosition","options":{}}},{"id":"8f1a2b3c-1000-4a00-8a00-000000000008","name":"Merge Store And Paid","type":"n8n-nodes-base.merge","typeVersion":3,"position":[780,300],"parameters":{"mode":"combine","combineBy":"combineByPosition","options":{}}},{"id":"8f1a2b3c-1000-4a00-8a00-000000000009","name":"Compute Executive KPIs","type":"n8n-nodes-base.code","typeVersion":2,"position":[1040,300],"parameters":{"jsCode":"// Executive daily loop — normalise Shopify + refunds + Meta + Google Ads into one KPI row.\nconst src = $input.all().map(i => i.json).reduce((a, b) => Object.assign(a, b), {});\n\nconst orders   = Array.isArray(src.orders)  ? src.orders  : [];\nconst refunds  = Array.isArray(src.refunds) ? src.refunds : [];\nconst metaRows = Array.isArray(src.data)    ? src.data    : [];\nconst gAdsRows = Array.isArray(src.results) ? src.results : [];\n\nconst num = v => { const n = parseFloat(v); return isNaN(n) ? 0 : n; };\nconst round = (v, d = 2) => Math.round(v * Math.pow(10, d)) / Math.pow(10, d);\n\n// ---------- store side ----------\nlet revenue = 0, cogs = 0, shippingCost = 0, discounts = 0;\nconst customerOrderCount = {};\nlet newCustomerOrders = 0;\n\nfor (const o of orders) {\n  const gross = num(o.total_price);\n  revenue   += gross;\n  discounts += num(o.total_discounts);\n  shippingCost += num(o.total_shipping_price_set && o.total_shipping_price_set.shop_money\n    ? o.total_shipping_price_set.shop_money.amount : o.shipping_cost);\n\n  for (const li of (o.line_items || [])) {\n    // unit_cost is the Shopify InventoryItem cost, qty from the line item\n    cogs += num(li.unit_cost || li.cost) * num(li.quantity || 1);\n  }\n\n  const cid = String((o.customer && o.customer.id) || o.email || 'guest');\n  customerOrderCount[cid] = (customerOrderCount[cid] || 0) + 1;\n  // Shopify tags the first order of a customer with orders_count === 1\n  if (!o.customer || num(o.customer.orders_count) <= 1) newCustomerOrders += 1;\n}\n\nconst refundAmount = refunds.reduce((s, r) => {\n  const txs = r.transactions || [];\n  return s + (txs.length\n    ? txs.reduce((t, x) => t + num(x.amount), 0)\n    : num(r.amount));\n}, 0);\n\nconst orderCount = orders.length;\nconst aov = orderCount ? revenue / orderCount : 0;\nconst grossProfit = revenue - cogs - shippingCost - refundAmount;\nconst grossMarginPct = revenue ? (grossProfit / revenue) * 100 : 0;\nconst refundRatePct = revenue ? (refundAmount / revenue) * 100 : 0;\n\n// ---------- paid side ----------\nconst metaSpend = metaRows.reduce((s, r) => s + num(r.spend), 0);\nconst metaPurch = metaRows.reduce((s, r) => s + (r.actions || [])\n  .filter(a => a.action_type === 'purchase')\n  .reduce((t, a) => t + num(a.value), 0), 0);\nconst metaImpr = metaRows.reduce((s, r) => s + num(r.impressions), 0);\nconst metaRoas = metaRows.reduce((s, r) => s + num((r.purchase_roas || [])[0] && (r.purchase_roas || [])[0].value), 0)\n  / (metaRows.length || 1);\n\nconst gAdsSpend = gAdsRows.reduce((s, r) => s + num(r.metrics && r.metrics.cost_micros) / 1e6, 0);\nconst gAdsConv  = gAdsRows.reduce((s, r) => s + num(r.metrics && r.metrics.conversions), 0);\n\nconst adSpend = metaSpend + gAdsSpend;\nconst paidConversions = metaPurch + gAdsConv;\n\n// CAC on NEW customers only — the number that actually matters for an exec loop.\nconst cac = newCustomerOrders ? adSpend / newCustomerOrders : (adSpend > 0 ? Infinity : 0);\n\n// LTV = contribution margin per order x expected lifetime orders.\n// Repeat rate is measured on today's cohort of buyers, floored so a slow day\n// can't collapse the projection to a single purchase.\nconst repeatBuyers = Object.values(customerOrderCount).filter(c => c > 1).length;\nconst distinctBuyers = Object.keys(customerOrderCount).length || 1;\nconst repeatRate = Math.min(0.75, Math.max(0.15, repeatBuyers / distinctBuyers));\nconst expectedOrders = 1 / (1 - repeatRate);            // geometric series\nconst contributionPerOrder = orderCount ? grossProfit / orderCount : 0;\nconst ltv = contributionPerOrder * expectedOrders;\nconst ltvToCac = cac && isFinite(cac) ? ltv / cac : 0;\n\nconst blendedRoas = adSpend ? revenue / adSpend : 0;\nconst mer = blendedRoas;                                 // marketing efficiency ratio\nconst contributionProfit = grossProfit - adSpend;\n\nreturn [{ json: {\n  report_date: new Date(Date.now() - 864e5).toISOString().slice(0, 10),\n  orders: orderCount,\n  new_customers: newCustomerOrders,\n  revenue: round(revenue),\n  discounts: round(discounts),\n  cogs: round(cogs),\n  shipping_cost: round(shippingCost),\n  refund_amount: round(refundAmount),\n  gross_profit: round(grossProfit),\n  gross_margin_pct: round(grossMarginPct, 1),\n  refund_rate_pct: round(refundRatePct, 2),\n  aov: round(aov),\n  ad_spend: round(adSpend),\n  meta_spend: round(metaSpend),\n  google_spend: round(gAdsSpend),\n  meta_roas: round(metaRoas),\n  meta_impressions: metaImpr,\n  paid_conversions: round(paidConversions, 1),\n  cac: isFinite(cac) ? round(cac) : null,\n  ltv: round(ltv),\n  ltv_to_cac: round(ltvToCac, 2),\n  repeat_rate_pct: round(repeatRate * 100, 1),\n  blended_roas: round(blendedRoas, 2),\n  mer: round(mer, 2),\n  contribution_profit: round(contributionProfit)\n} }];\n"}},{"id":"8f1a2b3c-1000-4a00-8a00-00000000000a","name":"Score Against Targets","type":"n8n-nodes-base.code","typeVersion":2,"position":[1300,300],"parameters":{"jsCode":"// Compare today's KPI row against the board-agreed targets, score every breach,\n// and project month pacing. One item out, ready for the email builder.\nconst k = $input.first().json;\n\nconst round = (v, d = 2) => Math.round(v * Math.pow(10, d)) / Math.pow(10, d);\n\n// direction: 'min' = higher is better, 'max' = lower is better\nconst TARGETS = [\n  { key: 'revenue',          label: 'Revenue',        target: 32000, direction: 'min', unit: '$',  weight: 3 },\n  { key: 'gross_margin_pct', label: 'Gross margin',   target: 62,    direction: 'min', unit: '%',  weight: 3 },\n  { key: 'cac',              label: 'CAC',            target: 48,    direction: 'max', unit: '$',  weight: 2 },\n  { key: 'ltv_to_cac',       label: 'LTV : CAC',      target: 3,     direction: 'min', unit: 'x',  weight: 3 },\n  { key: 'refund_rate_pct',  label: 'Refund rate',    target: 3.5,   direction: 'max', unit: '%',  weight: 2 },\n  { key: 'blended_roas',     label: 'Blended ROAS',   target: 2.2,   direction: 'min', unit: 'x',  weight: 1 }\n];\n\nconst MONTH_REVENUE_TARGET = 1000000; // 7-figure run-rate, per month\n\nconst scored = TARGETS.map(t => {\n  const raw = k[t.key];\n  const value = (raw === null || raw === undefined) ? 0 : Number(raw);\n  // gap > 0 always means \"worse than target\", regardless of direction\n  const gapAbs = t.direction === 'min' ? t.target - value : value - t.target;\n  const gapPct = t.target ? round((gapAbs / t.target) * 100, 1) : 0;\n  const breached = gapAbs > 0;\n\n  // severity: how far past the line, amplified by how much the metric matters\n  let severity = 'ok';\n  if (breached) {\n    if (gapPct >= 20) severity = 'critical';\n    else if (gapPct >= 8) severity = 'warning';\n    else severity = 'watch';\n  }\n  const points = breached ? round((gapPct / 10) * t.weight, 2) : 0;\n\n  return {\n    key: t.key,\n    label: t.label,\n    unit: t.unit,\n    value: round(value, 2),\n    target: t.target,\n    direction: t.direction,\n    breached,\n    gap_pct: gapPct,\n    severity,\n    points\n  };\n});\n\nconst breaches = scored.filter(s => s.breached)\n  .sort((a, b) => b.points - a.points);\nconst healthPenalty = breaches.reduce((s, b) => s + b.points, 0);\nconst healthScore = Math.max(0, Math.min(100, round(100 - healthPenalty * 4, 0)));\n\n// ---- month pacing on revenue ----\nconst now = new Date();\nconst dayOfMonth = now.getUTCDate();\nconst daysInMonth = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth() + 1, 0)).getUTCDate();\nconst projectedMonthRevenue = round(k.revenue * daysInMonth, 0);\nconst pacePct = round((projectedMonthRevenue / MONTH_REVENUE_TARGET) * 100, 1);\nconst requiredDailyRun = round(MONTH_REVENUE_TARGET / daysInMonth, 0);\n\n// One line the CEO reads first.\nconst headline = breaches.length === 0\n  ? 'All six metrics inside target. Nothing to action.'\n  : breaches.length + ' metric' + (breaches.length > 1 ? 's' : '') + ' breached — worst: ' +\n    breaches[0].label + ' at ' + breaches[0].value + breaches[0].unit +\n    ' vs ' + breaches[0].target + breaches[0].unit + ' (' + breaches[0].gap_pct + '% off).';\n\nreturn [{ json: Object.assign({}, k, {\n  scored,\n  breaches,\n  breach_count: breaches.length,\n  critical_count: breaches.filter(b => b.severity === 'critical').length,\n  health_score: healthScore,\n  headline,\n  day_of_month: dayOfMonth,\n  days_in_month: daysInMonth,\n  projected_month_revenue: projectedMonthRevenue,\n  month_target: MONTH_REVENUE_TARGET,\n  pace_pct: pacePct,\n  required_daily_run: requiredDailyRun\n}) }];\n"}},{"id":"8f1a2b3c-1000-4a00-8a00-00000000000b","name":"Any Metric Breached?","type":"n8n-nodes-base.if","typeVersion":2,"position":[1560,300],"parameters":{"conditions":{"options":{"caseSensitive":true,"version":2},"combinator":"and","conditions":[{"id":"c1","leftValue":"={{ $json.breach_count }}","rightValue":0,"operator":{"type":"number","operation":"gt"}}]},"options":{}}},{"id":"8f1a2b3c-1000-4a00-8a00-00000000000c","name":"Build Breach Summary Email","type":"n8n-nodes-base.code","typeVersion":2,"position":[1820,120],"parameters":{"jsCode":"// One-screen executive summary — breached metrics first, everything else compressed.\nconst d = $input.first().json;\n\nconst COLOR = { critical: '#b00020', warning: '#c76a00', watch: '#8a6d00', ok: '#1a7f37' };\nconst money = v => '$' + Number(v || 0).toLocaleString('en-US');\nconst fmt = (row) => {\n  if (row.unit === '$') return money(row.value);\n  if (row.unit === '%') return row.value + '%';\n  if (row.unit === 'x') return row.value + 'x';\n  return String(row.value);\n};\nconst fmtTarget = (row) => {\n  const prefix = row.direction === 'min' ? '≥ ' : '≤ ';\n  if (row.unit === '$') return prefix + money(row.target);\n  return prefix + row.target + (row.unit === '%' ? '%' : row.unit === 'x' ? 'x' : '');\n};\n\nconst rows = d.scored.map(r => {\n  const c = COLOR[r.severity] || COLOR.ok;\n  const arrow = r.breached ? (r.direction === 'min' ? '▼' : '▲') : '✓';\n  return '<tr>' +\n    '<td style=\"padding:8px 10px;border-bottom:1px solid #eee;\">' + r.label + '</td>' +\n    '<td style=\"padding:8px 10px;border-bottom:1px solid #eee;font-weight:700;color:' + c + '\">' +\n      arrow + ' ' + fmt(r) + '</td>' +\n    '<td style=\"padding:8px 10px;border-bottom:1px solid #eee;color:#666;\">' + fmtTarget(r) + '</td>' +\n    '<td style=\"padding:8px 10px;border-bottom:1px solid #eee;color:' + c + '\">' +\n      (r.breached ? r.gap_pct + '% off · ' + r.severity : 'on target') + '</td>' +\n  '</tr>';\n}).join('');\n\n// Concrete next action per breached metric — the part an operator actually uses.\nconst PLAYBOOK = {\n  revenue: 'Check yesterday\\'s top-3 campaigns for delivery drop; raise budget on anything above target ROAS.',\n  gross_margin_pct: 'Pull the 10 highest-volume SKUs and re-check landed COGS + shipping subsidy.',\n  cac: 'Cut the bottom-quartile ad sets by CAC and shift spend to the retargeting pool.',\n  ltv_to_cac: 'Payback is stretching — pause prospecting scale, push the post-purchase upsell flow.',\n  refund_rate_pct: 'Segment refunds by SKU and reason code; a single defective batch usually explains it.',\n  blended_roas: 'Blended efficiency slipped — compare MER to last 7d before touching campaign budgets.'\n};\n\nconst actions = d.breaches.length\n  ? '<ol style=\"margin:8px 0 0 18px;padding:0;color:#222;\">' +\n      d.breaches.map(b => '<li style=\"margin-bottom:6px;\"><b>' + b.label + '</b> — ' +\n        (PLAYBOOK[b.key] || 'Investigate.') + '</li>').join('') +\n    '</ol>'\n  : '';\n\nconst paceColor = d.pace_pct >= 100 ? COLOR.ok : d.pace_pct >= 85 ? COLOR.warning : COLOR.critical;\n\nconst html =\n'<div style=\"font-family:-apple-system,Segoe UI,Helvetica,Arial,sans-serif;max-width:640px;color:#111;\">' +\n  '<div style=\"background:#111;color:#fff;padding:14px 16px;\">' +\n    '<div style=\"font-size:12px;letter-spacing:.08em;opacity:.7;\">DAILY EXECUTIVE LOOP · ' + d.report_date + '</div>' +\n    '<div style=\"font-size:20px;font-weight:700;margin-top:4px;\">Health ' + d.health_score + '/100 · ' +\n      d.breach_count + ' breached</div>' +\n    '<div style=\"font-size:13px;margin-top:6px;opacity:.85;\">' + d.headline + '</div>' +\n  '</div>' +\n  '<table style=\"width:100%;border-collapse:collapse;font-size:14px;margin-top:12px;\">' +\n    '<tr style=\"text-align:left;font-size:11px;color:#888;letter-spacing:.06em;\">' +\n      '<th style=\"padding:6px 10px;\">METRIC</th><th style=\"padding:6px 10px;\">ACTUAL</th>' +\n      '<th style=\"padding:6px 10px;\">TARGET</th><th style=\"padding:6px 10px;\">STATUS</th></tr>' +\n    rows +\n  '</table>' +\n  '<div style=\"margin-top:14px;padding:12px 14px;background:#fafafa;font-size:13px;\">' +\n    '<b>Pacing</b> · day ' + d.day_of_month + '/' + d.days_in_month + ' · projecting ' +\n    money(d.projected_month_revenue) + ' vs ' + money(d.month_target) + ' target ' +\n    '<span style=\"color:' + paceColor + ';font-weight:700;\">(' + d.pace_pct + '%)</span>. ' +\n    'Needs ' + money(d.required_daily_run) + '/day to land it.' +\n  '</div>' +\n  '<div style=\"margin-top:14px;font-size:13px;\">' +\n    '<b>Do today</b>' + actions +\n  '</div>' +\n  '<div style=\"margin-top:14px;font-size:12px;color:#777;\">' +\n    'Orders ' + d.orders + ' · AOV ' + money(d.aov) + ' · new customers ' + d.new_customers +\n    ' · ad spend ' + money(d.ad_spend) + ' · contribution ' + money(d.contribution_profit) + '.' +\n  '</div>' +\n'</div>';\n\nconst subject = '[' + (d.critical_count ? 'CRITICAL' : 'ALERT') + '] ' + d.report_date +\n  ' · ' + d.breach_count + ' metric' + (d.breach_count > 1 ? 's' : '') + ' off target · health ' +\n  d.health_score + '/100';\n\nconst slack_text = '*' + subject + '*\\n' + d.headline + '\\nProjecting ' +\n  money(d.projected_month_revenue) + ' this month (' + d.pace_pct + '% of target).';\n\nreturn [{ json: Object.assign({}, d, { html, subject, slack_text, status: 'breach' }) }];\n"}},{"id":"8f1a2b3c-1000-4a00-8a00-00000000000d","name":"Build All Clear Email","type":"n8n-nodes-base.code","typeVersion":2,"position":[1820,500],"parameters":{"jsCode":"// No breaches — still send the one-screen summary, but short, green, and pacing-led.\nconst d = $input.first().json;\nconst money = v => '$' + Number(v || 0).toLocaleString('en-US');\n\n// Even on a green day, surface the metric with the thinnest cushion so\n// tomorrow's breach is never a surprise.\nconst cushions = d.scored.map(r => ({\n  label: r.label,\n  cushion_pct: Math.abs(r.gap_pct)\n})).sort((a, b) => a.cushion_pct - b.cushion_pct);\nconst tightest = cushions[0];\n\nconst rows = d.scored.map(r => {\n  const val = r.unit === '$' ? money(r.value) : r.value + (r.unit === '%' ? '%' : r.unit === 'x' ? 'x' : '');\n  return '<tr><td style=\"padding:7px 10px;border-bottom:1px solid #eee;\">' + r.label + '</td>' +\n    '<td style=\"padding:7px 10px;border-bottom:1px solid #eee;font-weight:700;color:#1a7f37;\">✓ ' + val + '</td>' +\n    '<td style=\"padding:7px 10px;border-bottom:1px solid #eee;color:#666;\">' +\n      Math.abs(r.gap_pct) + '% of cushion</td></tr>';\n}).join('');\n\nconst html =\n'<div style=\"font-family:-apple-system,Segoe UI,Helvetica,Arial,sans-serif;max-width:640px;color:#111;\">' +\n  '<div style=\"background:#0d3d1e;color:#fff;padding:14px 16px;\">' +\n    '<div style=\"font-size:12px;letter-spacing:.08em;opacity:.7;\">DAILY EXECUTIVE LOOP · ' + d.report_date + '</div>' +\n    '<div style=\"font-size:20px;font-weight:700;margin-top:4px;\">All clear · health ' + d.health_score + '/100</div>' +\n  '</div>' +\n  '<table style=\"width:100%;border-collapse:collapse;font-size:14px;margin-top:12px;\">' + rows + '</table>' +\n  '<div style=\"margin-top:14px;padding:12px 14px;background:#fafafa;font-size:13px;\">' +\n    'Projecting ' + money(d.projected_month_revenue) + ' vs ' + money(d.month_target) +\n    ' (' + d.pace_pct + '%). Thinnest cushion: <b>' + tightest.label + '</b> at ' +\n    tightest.cushion_pct + '% — watch it tomorrow.' +\n  '</div>' +\n  '<div style=\"margin-top:12px;font-size:12px;color:#777;\">' +\n    'Orders ' + d.orders + ' · AOV ' + money(d.aov) + ' · CAC ' + money(d.cac) +\n    ' · LTV:CAC ' + d.ltv_to_cac + 'x · contribution ' + money(d.contribution_profit) + '.' +\n  '</div>' +\n'</div>';\n\nreturn [{ json: Object.assign({}, d, {\n  html,\n  subject: '[OK] ' + d.report_date + ' · all metrics on target · health ' + d.health_score + '/100',\n  tightest_metric: tightest.label,\n  status: 'clear'\n}) }];\n"}},{"id":"8f1a2b3c-1000-4a00-8a00-00000000000e","name":"Critical Breach?","type":"n8n-nodes-base.if","typeVersion":2,"position":[2080,120],"parameters":{"conditions":{"options":{"caseSensitive":true,"version":2},"combinator":"and","conditions":[{"id":"c1","leftValue":"={{ $json.critical_count }}","rightValue":0,"operator":{"type":"number","operation":"gt"}}]},"options":{}}},{"id":"8f1a2b3c-1000-4a00-8a00-00000000000f","name":"Page Founders In Slack","type":"n8n-nodes-base.slack","typeVersion":2.2,"position":[2340,0],"parameters":{"select":"channel","channelId":{"__rl":true,"value":"#exec-daily","mode":"name"},"text":"={{ $json.slack_text }}","otherOptions":{}}},{"id":"8f1a2b3c-1000-4a00-8a00-000000000010","name":"Email Executive Summary","type":"n8n-nodes-base.gmail","typeVersion":2.1,"position":[2600,300],"parameters":{"sendTo":"founders@brand.com","subject":"={{ $json.subject }}","message":"={{ $json.html }}","options":{}}},{"id":"8f1a2b3c-1000-4a00-8a00-000000000011","name":"Log KPI History","type":"n8n-nodes-base.googleSheets","typeVersion":4.5,"position":[2860,300],"parameters":{"operation":"append","documentId":{"__rl":true,"value":"SHEET_ID","mode":"id"},"sheetName":{"__rl":true,"value":"gid=0","mode":"list","cachedResultName":"Daily KPIs"},"columns":{"mappingMode":"autoMapInputData","value":{},"matchingColumns":[]},"options":{}}},{"id":"8f1a2b3c-1000-4a00-8a00-000000000012","name":"Note Sources","type":"n8n-nodes-base.stickyNote","typeVersion":1,"position":[200,-140],"parameters":{"content":"## 1 · PULL YESTERDAY\nShopify orders + refunds, Meta insights, Google Ads cost.\nAll four windows are pinned to yesterday 00:00–24:00 so the numbers reconcile.","height":280,"width":420,"color":4}},{"id":"8f1a2b3c-1000-4a00-8a00-000000000013","name":"Note Maths","type":"n8n-nodes-base.stickyNote","typeVersion":1,"position":[1020,-140],"parameters":{"content":"## 2 · KPI MATHS + TARGETS\nRevenue, gross margin, CAC on NEW customers, LTV from contribution x expected orders,\nrefund rate. Each is scored against target; gap % x weight drives severity + health score.","height":280,"width":480,"color":5}},{"id":"8f1a2b3c-1000-4a00-8a00-000000000014","name":"Note Output","type":"n8n-nodes-base.stickyNote","typeVersion":1,"position":[1800,-140],"parameters":{"content":"## 3 · ONE-SCREEN SUMMARY\nBreach day: red email + per-metric playbook, Slack page if anything is >20% off.\nClean day: green email flagging the thinnest cushion. Both log to Sheets.","height":280,"width":460,"color":3}}],"connections":{"Every Morning 07:00":{"main":[[{"node":"Fetch Shopify Orders","type":"main","index":0},{"node":"Fetch Shopify Refunds","type":"main","index":0},{"node":"Fetch Meta Ad Spend","type":"main","index":0},{"node":"Fetch Google Ads Cost","type":"main","index":0}]]},"Fetch Shopify Orders":{"main":[[{"node":"Merge Store Financials","type":"main","index":0}]]},"Fetch Shopify Refunds":{"main":[[{"node":"Merge Store Financials","type":"main","index":1}]]},"Fetch Meta Ad Spend":{"main":[[{"node":"Merge Paid Spend","type":"main","index":0}]]},"Fetch Google Ads Cost":{"main":[[{"node":"Merge Paid Spend","type":"main","index":1}]]},"Merge Store Financials":{"main":[[{"node":"Merge Store And Paid","type":"main","index":0}]]},"Merge Paid Spend":{"main":[[{"node":"Merge Store And Paid","type":"main","index":1}]]},"Merge Store And Paid":{"main":[[{"node":"Compute Executive KPIs","type":"main","index":0}]]},"Compute Executive KPIs":{"main":[[{"node":"Score Against Targets","type":"main","index":0}]]},"Score Against Targets":{"main":[[{"node":"Any Metric Breached?","type":"main","index":0}]]},"Any Metric Breached?":{"main":[[{"node":"Build Breach Summary Email","type":"main","index":0}],[{"node":"Build All Clear Email","type":"main","index":0}]]},"Build Breach Summary Email":{"main":[[{"node":"Critical Breach?","type":"main","index":0}]]},"Critical Breach?":{"main":[[{"node":"Page Founders In Slack","type":"main","index":0}],[{"node":"Email Executive Summary","type":"main","index":0}]]},"Page Founders In Slack":{"main":[[{"node":"Email Executive Summary","type":"main","index":0}]]},"Build All Clear Email":{"main":[[{"node":"Email Executive Summary","type":"main","index":0}]]},"Email Executive Summary":{"main":[[{"node":"Log KPI History","type":"main","index":0}]]}},"settings":{"executionOrder":"v1"},"pinData":{}}