{"name":"Test 10 products a day workflow","nodes":[{"id":"a1b2c3d4-0001-4222-8333-444455556600","name":"Every Morning 07:00","type":"n8n-nodes-base.scheduleTrigger","typeVersion":1.2,"position":[-320,300],"parameters":{"rule":{"interval":[{"field":"days","daysInterval":1}]}}},{"id":"a1b2c3d4-0002-4222-8333-444455556600","name":"Fetch Product Candidates","type":"n8n-nodes-base.httpRequest","typeVersion":4.2,"position":[-60,300],"parameters":{"url":"https://api.catalog.internal/v1/products/candidates","sendQuery":true,"queryParameters":{"parameters":[{"name":"status","value":"sourceable"},{"name":"min_orders_last_30d","value":"25"},{"name":"fields","value":"product_id,title,image_url,retail_price,unit_cost,ship_cost,ship_days,orders_last_7d,orders_last_30d,active_ads_running,supplier_rating"},{"name":"limit","value":"250"}]},"options":{}}},{"id":"a1b2c3d4-0003-4222-8333-444455556600","name":"Read Tested Products Log","type":"n8n-nodes-base.googleSheets","typeVersion":4.5,"position":[200,300],"parameters":{"operation":"read","documentId":{"__rl":true,"value":"SHEET_ID","mode":"id"},"sheetName":{"__rl":true,"value":"gid=0","mode":"list","cachedResultName":"TestLog"},"options":{}}},{"id":"a1b2c3d4-0004-4222-8333-444455556600","name":"Pick Today's 10 Test Products","type":"n8n-nodes-base.code","typeVersion":2,"position":[460,300],"parameters":{"jsCode":"// ---- PICK TODAY'S 10 TEST CANDIDATES ----------------------------------------\n// Inputs: catalog candidates (this node's input) + the historical test log sheet.\nconst DAILY_SLOTS      = 10;   // we only ever launch 10 tests per day\nconst TEST_BUDGET      = 30;   // USD/day per test campaign\nconst KILL_MULTIPLIER  = 1.5;  // kill once spend > 1.5x the target CPA with 0 purchases\nconst COOLDOWN_DAYS    = 45;   // don't re-test a product we already tested recently\n\nconst candidates = $input.all().map(i => i.json);\n\n// history rows come from the Google Sheet: product_id, tested_on, verdict\nlet history = [];\ntry { history = $('Read Tested Products Log').all().map(i => i.json); } catch (e) { history = []; }\n\nconst now = Date.now();\nconst DAY = 86400000;\n\nconst blocked = new Map();\nfor (const h of history) {\n  const id = String(h.product_id || '').trim();\n  if (!id) continue;\n  const testedOn = Date.parse(h.tested_on || '') || 0;\n  const ageDays  = testedOn ? (now - testedOn) / DAY : 999;\n  const prev     = blocked.get(id);\n  const rec      = { ageDays, verdict: String(h.verdict || '').toLowerCase() };\n  if (!prev || rec.ageDays < prev.ageDays) blocked.set(id, rec);\n}\n\nconst seenThisRun = new Set();\nconst scored = [];\n\nfor (const c of candidates) {\n  const id = String(c.product_id || c.id || '').trim();\n  if (!id || seenThisRun.has(id)) continue;      // dedupe inside the feed itself\n  seenThisRun.add(id);\n\n  const hist = blocked.get(id);\n  // hard winners can be re-tested, failures + anything inside cooldown cannot\n  if (hist && hist.verdict !== 'pass' && hist.ageDays < COOLDOWN_DAYS) continue;\n  if (hist && hist.verdict === 'pass' && hist.ageDays < 14) continue;\n\n  const price   = Number(c.retail_price) || 0;\n  const cogs    = Number(c.unit_cost) || 0;\n  const ship    = Number(c.ship_cost) || 0;\n  const margin  = price - cogs - ship;\n  if (price <= 0 || margin <= 0) continue;\n\n  const marginPct   = margin / price;                       // 0..1\n  const orders30    = Number(c.orders_last_30d) || 0;\n  const orders7     = Number(c.orders_last_7d) || 0;\n  const trend       = orders30 > 0 ? (orders7 * 30 / 7) / orders30 : 1;   // >1 = accelerating\n  const saturation  = Number(c.active_ads_running) || 0;    // competing advertisers\n  const rating      = Number(c.supplier_rating) || 0;       // 0..5\n  const shipDays    = Number(c.ship_days) || 30;\n\n  // target CPA the product can actually afford at a 2.0x contribution ratio\n  const targetCpa   = +(margin / 2).toFixed(2);\n  if (targetCpa < 4) continue;                              // unfundable at test budget\n\n  // ---- score: 0..100, weighted on what actually predicts a test surviving ----\n  const marginScore = Math.min(marginPct / 0.65, 1) * 35;                 // 35 pts\n  const trendScore  = Math.max(0, Math.min((trend - 0.7) / 0.8, 1)) * 25; // 25 pts\n  const satScore    = Math.max(0, 1 - saturation / 120) * 15;             // 15 pts\n  const trustScore  = Math.max(0, (rating - 3.5) / 1.5) * 15;             // 15 pts\n  const logiScore   = Math.max(0, 1 - Math.max(0, shipDays - 5) / 20) * 10; // 10 pts\n  const score = +(marginScore + trendScore + satScore + trustScore + logiScore).toFixed(1);\n\n  scored.push({\n    product_id: id,\n    product_name: c.title || c.product_name || id,\n    image_url: c.image_url || '',\n    retail_price: +price.toFixed(2),\n    unit_margin: +margin.toFixed(2),\n    margin_pct: +(marginPct * 100).toFixed(1),\n    trend_ratio: +trend.toFixed(2),\n    active_ads_running: saturation,\n    supplier_rating: rating,\n    ship_days: shipDays,\n    score,\n    daily_budget: TEST_BUDGET,\n    target_cpa: targetCpa,\n    kill_threshold: +(targetCpa * KILL_MULTIPLIER).toFixed(2),\n    breakeven_roas: +(price / margin).toFixed(2),\n    test_date: new Date().toISOString().slice(0, 10)\n  });\n}\n\nscored.sort((a, b) => b.score - a.score);\nconst picked = scored.slice(0, DAILY_SLOTS).map((p, i) => ({ ...p, slot: i + 1 }));\n\nreturn picked.map(json => ({ json }));"}},{"id":"a1b2c3d4-0005-4222-8333-444455556600","name":"Loop Over 10 Products","type":"n8n-nodes-base.splitInBatches","typeVersion":3,"position":[720,300],"parameters":{"batchSize":1,"options":{}}},{"id":"a1b2c3d4-0006-4222-8333-444455556600","name":"Write Test Campaign Brief","type":"@n8n/n8n-nodes-langchain.openAi","typeVersion":1.8,"position":[980,100],"parameters":{"modelId":{"__rl":true,"value":"gpt-4o-mini","mode":"list"},"messages":{"values":[{"role":"system","content":"You are a direct response media buyer. Return a tight test brief: 1 angle, 1 hook line, 3 primary texts, 1 headline. No preamble."},{"content":"=Product: {{ $json.product_name }}\nPrice: ${{ $json.retail_price }} | unit margin ${{ $json.unit_margin }} ({{ $json.margin_pct }}%)\nBreakeven ROAS: {{ $json.breakeven_roas }}x | target CPA ${{ $json.target_cpa }} | kill at ${{ $json.kill_threshold }}\nMarket saturation: {{ $json.active_ads_running }} active ads. Trend ratio {{ $json.trend_ratio }}.\nWrite the test brief."}]},"options":{}}},{"id":"a1b2c3d4-0007-4222-8333-444455556600","name":"Launch $30 Test Campaign","type":"n8n-nodes-base.httpRequest","typeVersion":4.2,"position":[1240,100],"parameters":{"method":"POST","url":"https://graph.facebook.com/v21.0/act_ID/campaigns","sendBody":true,"specifyBody":"json","jsonBody":"={{ JSON.stringify({ name: 'TEST-' + $('Loop Over 10 Products').item.json.product_id + '-' + $('Loop Over 10 Products').item.json.test_date, objective: 'OUTCOME_SALES', status: 'ACTIVE', special_ad_categories: [], daily_budget: Math.round($('Loop Over 10 Products').item.json.daily_budget * 100), bid_strategy: 'LOWEST_COST_WITHOUT_CAP' }) }}","options":{}}},{"id":"a1b2c3d4-0008-4222-8333-444455556600","name":"Log Brief To Sheet","type":"n8n-nodes-base.googleSheets","typeVersion":4.5,"position":[1500,100],"parameters":{"operation":"append","documentId":{"__rl":true,"value":"SHEET_ID","mode":"id"},"sheetName":{"__rl":true,"value":"gid=0","mode":"list","cachedResultName":"TestLog"},"columns":{"mappingMode":"autoMapInputData","value":{},"matchingColumns":[]},"options":{}}},{"id":"a1b2c3d4-0009-4222-8333-444455556600","name":"Hold 24h Then Grade","type":"n8n-nodes-base.wait","typeVersion":1.1,"position":[980,300],"parameters":{"amount":24,"unit":"hours"}},{"id":"a1b2c3d4-000a-4222-8333-444455556600","name":"Fetch Test Campaign Spend","type":"n8n-nodes-base.httpRequest","typeVersion":4.2,"position":[1240,300],"parameters":{"url":"https://graph.facebook.com/v21.0/act_ID/insights","sendQuery":true,"queryParameters":{"parameters":[{"name":"level","value":"campaign"},{"name":"fields","value":"campaign_id,campaign_name,spend,impressions,clicks,frequency,cpm,ctr,actions,action_values"},{"name":"filtering","value":"[{\"field\":\"campaign.name\",\"operator\":\"CONTAIN\",\"value\":\"TEST-\"}]"},{"name":"date_preset","value":"yesterday"}]},"options":{}}},{"id":"a1b2c3d4-000b-4222-8333-444455556600","name":"Grade Spend vs Kill Threshold","type":"n8n-nodes-base.code","typeVersion":2,"position":[1500,300],"parameters":{"jsCode":"// ---- SPEND PACING + PASS/FAIL VERDICT PER TEST ------------------------------\n// Reads yesterday's Meta insights for the test campaigns and grades each one.\nconst rows = $input.all().map(i => i.json);\n\n// pull the briefs we generated so we know each product's economics\nlet briefs = [];\ntry { briefs = $('Pick Today\\'s 10 Test Products').all().map(i => i.json); } catch (e) { briefs = []; }\nconst briefById = new Map(briefs.map(b => [String(b.product_id), b]));\n\nconst HOURS_ELAPSED   = 24;   // tests run a full day before grading\nconst MIN_IMPRESSIONS = 800;  // below this the data is noise, keep it alive\nconst out = [];\n\n// Meta returns conversions as actions:[{action_type,value}] / action_values:[...]\nconst PURCHASE_TYPES = ['purchase', 'omni_purchase', 'offsite_conversion.fb_pixel_purchase'];\nconst sumActions = (arr) => (Array.isArray(arr) ? arr : [])\n  .filter(a => PURCHASE_TYPES.includes(String(a.action_type)))\n  .reduce((s, a) => s + (Number(a.value) || 0), 0);\n\nfor (const r of rows) {\n  // campaign_name is \"TEST-<product_id>-<yyyy-mm-dd>\"; product_id may contain dashes\n  const m = String(r.campaign_name || '').match(/^TEST-(.+)-\\d{4}-\\d{2}-\\d{2}$/);\n  const productId = (m && m[1]) || String(r.product_id || '');\n  const b = briefById.get(productId) || {};\n\n  const spend       = Number(r.spend) || 0;\n  const impressions = Number(r.impressions) || 0;\n  const clicks      = Number(r.clicks) || 0;\n  const frequency   = Number(r.frequency) || 0;\n  const purchases   = sumActions(r.actions) || Number(r.purchases) || 0;\n  const revenue     = sumActions(r.action_values) || Number(r.purchase_value) || 0;\n  const roas        = spend > 0 ? revenue / spend : 0;\n  const cpa         = purchases > 0 ? spend / purchases : null;\n  const cpm         = impressions > 0 ? (spend / impressions) * 1000 : 0;\n  const ctr         = impressions > 0 ? (clicks / impressions) * 100 : 0;\n  const cpc         = clicks > 0 ? spend / clicks : 0;\n\n  const budget      = Number(b.daily_budget) || 30;\n  const targetCpa   = Number(b.target_cpa) || 12;\n  const killAt      = Number(b.kill_threshold) || targetCpa * 1.5;\n  const breakeven   = Number(b.breakeven_roas) || 2;\n\n  // pacing: at this burn rate, where does spend land by end of day?\n  const burnPerHour   = spend / HOURS_ELAPSED;\n  const projected24h  = +(burnPerHour * 24).toFixed(2);\n  const pacingPct     = budget > 0 ? +((projected24h / budget) * 100).toFixed(0) : 0;\n  const underDelivering = pacingPct < 70;\n\n  // how far into the kill budget are we with nothing to show for it\n  const killProgress  = killAt > 0 ? +(spend / killAt).toFixed(2) : 0;\n\n  let verdict, reason;\n  if (impressions < MIN_IMPRESSIONS && spend < killAt * 0.5) {\n    verdict = 'extend';\n    reason  = `only ${impressions} impressions on $${spend.toFixed(2)} - not enough data yet`;\n  } else if (purchases === 0 && spend >= killAt) {\n    verdict = 'fail';\n    reason  = `$${spend.toFixed(2)} spent, 0 purchases, past $${killAt} kill threshold`;\n  } else if (purchases > 0 && roas >= breakeven) {\n    verdict = 'pass';\n    reason  = `${purchases} purchases at ${roas.toFixed(2)}x vs ${breakeven}x breakeven`;\n  } else if (purchases > 0 && cpa !== null && cpa <= killAt) {\n    verdict = 'extend';\n    reason  = `CPA $${cpa.toFixed(2)} under kill $${killAt} but ROAS ${roas.toFixed(2)}x below breakeven`;\n  } else {\n    verdict = 'fail';\n    reason  = `ROAS ${roas.toFixed(2)}x with ${purchases} purchases - below breakeven ${breakeven}x`;\n  }\n\n  // creative-level warning flags a buyer would actually want\n  const flags = [];\n  if (frequency > 1.6) flags.push('frequency>1.6 on day one - audience too narrow');\n  if (ctr < 0.8 && impressions >= MIN_IMPRESSIONS) flags.push('CTR under 0.8% - hook is weak');\n  if (cpm > 45) flags.push('CPM over $45 - expensive placement mix');\n  if (underDelivering) flags.push(`under-delivering, pacing at ${pacingPct}% of budget`);\n\n  out.push({\n    product_id: productId,\n    product_name: b.product_name || r.campaign_name || productId,\n    campaign_id: r.campaign_id || '',\n    campaign_name: r.campaign_name || '',\n    spend: +spend.toFixed(2),\n    impressions, clicks, purchases,\n    revenue: +revenue.toFixed(2),\n    roas: +roas.toFixed(2),\n    cpa: cpa === null ? null : +cpa.toFixed(2),\n    cpc: +cpc.toFixed(2),\n    cpm: +cpm.toFixed(2),\n    ctr: +ctr.toFixed(2),\n    frequency: +frequency.toFixed(2),\n    target_cpa: targetCpa,\n    kill_threshold: killAt,\n    kill_progress: killProgress,\n    breakeven_roas: breakeven,\n    projected_24h_spend: projected24h,\n    pacing_pct: pacingPct,\n    verdict,\n    is_pass: verdict === 'pass',\n    reason,\n    flags: flags.join(' | '),\n    graded_on: new Date().toISOString().slice(0, 10)\n  });\n}\n\n// best performers first so the digest reads top-down\nout.sort((a, b) => b.roas - a.roas || b.purchases - a.purchases);\nreturn out.map(json => ({ json }));"}},{"id":"a1b2c3d4-000c-4222-8333-444455556600","name":"Verdict = PASS?","type":"n8n-nodes-base.if","typeVersion":2,"position":[1760,300],"parameters":{"conditions":{"options":{"caseSensitive":true,"version":2},"combinator":"and","conditions":[{"id":"c1","leftValue":"={{ $json.is_pass }}","rightValue":true,"operator":{"type":"boolean","operation":"true"}}]},"options":{}}},{"id":"a1b2c3d4-000d-4222-8333-444455556600","name":"Mark Product As Winner","type":"n8n-nodes-base.set","typeVersion":3.3,"position":[2020,120],"parameters":{"assignments":{"assignments":[{"id":"a1","name":"product_id","value":"={{ $json.product_id }}","type":"string"},{"id":"a2","name":"product_name","value":"={{ $json.product_name }}","type":"string"},{"id":"a3","name":"verdict","value":"pass","type":"string"},{"id":"a4","name":"roas","value":"={{ $json.roas }}","type":"number"},{"id":"a5","name":"spend","value":"={{ $json.spend }}","type":"number"},{"id":"a6","name":"next_action","value":"=scale to {{ $json.spend * 3 }}/day CBO","type":"string"}]},"options":{}}},{"id":"a1b2c3d4-000e-4222-8333-444455556600","name":"Alert Winner In Slack","type":"n8n-nodes-base.slack","typeVersion":2.2,"position":[2280,120],"parameters":{"select":"channel","channelId":{"__rl":true,"value":"#product-testing","mode":"name"},"text":"=✅ PASS: {{ $json.product_name }} — {{ $json.roas }}x on ${{ $json.spend }} test spend. Next: {{ $json.next_action }}","otherOptions":{}}},{"id":"a1b2c3d4-000f-4222-8333-444455556600","name":"Kill Failing Test Campaign","type":"n8n-nodes-base.httpRequest","typeVersion":4.2,"position":[2020,500],"parameters":{"method":"POST","url":"=https://graph.facebook.com/v21.0/{{ $json.campaign_id }}","sendBody":true,"specifyBody":"json","jsonBody":"={{ JSON.stringify({ status: $json.verdict === 'fail' ? 'PAUSED' : 'ACTIVE' }) }}","options":{}}},{"id":"a1b2c3d4-0010-4222-8333-444455556600","name":"Log Verdict Row","type":"n8n-nodes-base.googleSheets","typeVersion":4.5,"position":[2280,500],"parameters":{"operation":"append","documentId":{"__rl":true,"value":"SHEET_ID","mode":"id"},"sheetName":{"__rl":true,"value":"gid=1","mode":"list","cachedResultName":"Verdicts"},"columns":{"mappingMode":"autoMapInputData","value":{},"matchingColumns":[]},"options":{}}},{"id":"a1b2c3d4-0011-4222-8333-444455556600","name":"Build Batch Digest","type":"n8n-nodes-base.code","typeVersion":2,"position":[2540,300],"parameters":{"jsCode":"// ---- DAILY TEST BATCH DIGEST -------------------------------------------------\nconst rows = $input.all().map(i => i.json);\n\nconst passes   = rows.filter(r => r.verdict === 'pass');\nconst fails    = rows.filter(r => r.verdict === 'fail');\nconst extends_ = rows.filter(r => r.verdict === 'extend');\n\nconst totalSpend   = rows.reduce((s, r) => s + (Number(r.spend) || 0), 0);\nconst totalRevenue = rows.reduce((s, r) => s + (Number(r.revenue) || 0), 0);\nconst totalPurch   = rows.reduce((s, r) => s + (Number(r.purchases) || 0), 0);\nconst batchRoas    = totalSpend > 0 ? totalRevenue / totalSpend : 0;\nconst wastedSpend  = fails.reduce((s, r) => s + (Number(r.spend) || 0), 0);\nconst hitRate      = rows.length ? (passes.length / rows.length) * 100 : 0;\n\nconst line = r =>\n  `<tr><td>${r.product_name}</td><td>${r.verdict.toUpperCase()}</td><td>$${r.spend}</td>` +\n  `<td>${r.purchases}</td><td>${r.roas}x</td><td>${r.cpa === null ? '-' : '$' + r.cpa}</td>` +\n  `<td>${r.reason}${r.flags ? '<br><small>' + r.flags + '</small>' : ''}</td></tr>`;\n\nconst html = [\n  `<h2>Daily test batch - ${new Date().toISOString().slice(0, 10)}</h2>`,\n  `<p><b>${rows.length}</b> products tested &middot; <b>${passes.length}</b> pass &middot; `,\n  `<b>${extends_.length}</b> extend &middot; <b>${fails.length}</b> killed<br>`,\n  `Spend $${totalSpend.toFixed(2)} &rarr; revenue $${totalRevenue.toFixed(2)} `,\n  `(<b>${batchRoas.toFixed(2)}x</b>, ${totalPurch} purchases)<br>`,\n  `Wasted on killed tests: $${wastedSpend.toFixed(2)} &middot; hit rate ${hitRate.toFixed(0)}%</p>`,\n  '<table border=\"1\" cellpadding=\"6\" cellspacing=\"0\">',\n  '<tr><th>Product</th><th>Verdict</th><th>Spend</th><th>Purch</th><th>ROAS</th><th>CPA</th><th>Why</th></tr>',\n  ...rows.map(line),\n  '</table>'\n].join('');\n\nreturn [{ json: {\n  html,\n  tested: rows.length,\n  passed: passes.length,\n  extended: extends_.length,\n  killed: fails.length,\n  total_spend: +totalSpend.toFixed(2),\n  total_revenue: +totalRevenue.toFixed(2),\n  batch_roas: +batchRoas.toFixed(2),\n  wasted_spend: +wastedSpend.toFixed(2),\n  hit_rate_pct: +hitRate.toFixed(1),\n  winners: passes.map(p => p.product_name).join(', ')\n} }];"}},{"id":"a1b2c3d4-0012-4222-8333-444455556600","name":"Email Daily Test Report","type":"n8n-nodes-base.gmail","typeVersion":2.1,"position":[2800,300],"parameters":{"sendTo":"buying@brand.com","subject":"=Test batch {{ $now.format('yyyy-LL-dd') }} — {{ $json.passed }}/{{ $json.tested }} passed, {{ $json.batch_roas }}x","message":"={{ $json.html }}","options":{}}},{"id":"a1b2c3d4-0013-4222-8333-444455556600","name":"Note Sourcing","type":"n8n-nodes-base.stickyNote","typeVersion":1,"position":[-360,40],"parameters":{"content":"## 1. SOURCE & PICK 10\nPull sourceable catalog candidates, drop anything tested in the last 45 days, score on margin / demand trend / saturation / supplier trust, take the top 10 slots.","height":220,"width":760,"color":4}},{"id":"a1b2c3d4-0014-4222-8333-444455556600","name":"Note Launch","type":"n8n-nodes-base.stickyNote","typeVersion":1,"position":[960,-140],"parameters":{"content":"## 2. BRIEF & LAUNCH\nOne loop pass per product: LLM writes the test brief, a $30/day OUTCOME_SALES campaign goes live as TEST-<product_id>-<date>, brief is logged.","height":200,"width":700,"color":5}},{"id":"a1b2c3d4-0015-4222-8333-444455556600","name":"Note Verdict","type":"n8n-nodes-base.stickyNote","typeVersion":1,"position":[1480,640],"parameters":{"content":"## 3. GRADE & VERDICT\nAfter 24h, pull spend/purchases, project pacing, compare against the per-product kill threshold (1.5x target CPA). PASS scales, FAIL pauses, EXTEND stays live. Everything lands in the verdict sheet + morning email.","height":240,"width":800,"color":3}}],"connections":{"Every Morning 07:00":{"main":[[{"node":"Fetch Product Candidates","type":"main","index":0}]]},"Fetch Product Candidates":{"main":[[{"node":"Read Tested Products Log","type":"main","index":0}]]},"Read Tested Products Log":{"main":[[{"node":"Pick Today's 10 Test Products","type":"main","index":0}]]},"Pick Today's 10 Test Products":{"main":[[{"node":"Loop Over 10 Products","type":"main","index":0}]]},"Loop Over 10 Products":{"main":[[{"node":"Hold 24h Then Grade","type":"main","index":0}],[{"node":"Write Test Campaign Brief","type":"main","index":0}]]},"Write Test Campaign Brief":{"main":[[{"node":"Launch $30 Test Campaign","type":"main","index":0}]]},"Launch $30 Test Campaign":{"main":[[{"node":"Log Brief To Sheet","type":"main","index":0}]]},"Log Brief To Sheet":{"main":[[{"node":"Loop Over 10 Products","type":"main","index":0}]]},"Hold 24h Then Grade":{"main":[[{"node":"Fetch Test Campaign Spend","type":"main","index":0}]]},"Fetch Test Campaign Spend":{"main":[[{"node":"Grade Spend vs Kill Threshold","type":"main","index":0}]]},"Grade Spend vs Kill Threshold":{"main":[[{"node":"Verdict = PASS?","type":"main","index":0}]]},"Verdict = PASS?":{"main":[[{"node":"Mark Product As Winner","type":"main","index":0}],[{"node":"Kill Failing Test Campaign","type":"main","index":0}]]},"Mark Product As Winner":{"main":[[{"node":"Alert Winner In Slack","type":"main","index":0}]]},"Alert Winner In Slack":{"main":[[{"node":"Log Verdict Row","type":"main","index":0}]]},"Kill Failing Test Campaign":{"main":[[{"node":"Log Verdict Row","type":"main","index":0}]]},"Log Verdict Row":{"main":[[{"node":"Build Batch Digest","type":"main","index":0}]]},"Build Batch Digest":{"main":[[{"node":"Email Daily Test Report","type":"main","index":0}]]}},"settings":{"executionOrder":"v1"},"pinData":{}}