{"name":"$10k/day Meta ads workflow","nodes":[{"id":"a1b2c3d4-1111-4222-8333-000000000001","name":"Hourly Pacing Check","type":"n8n-nodes-base.scheduleTrigger","typeVersion":1.2,"position":[-280,300],"parameters":{"rule":{"interval":[{"field":"hours","hoursInterval":1}]}}},{"id":"a1b2c3d4-1111-4222-8333-000000000002","name":"Fetch Account Spend Today","type":"n8n-nodes-base.httpRequest","typeVersion":4.2,"position":[-20,140],"parameters":{"url":"https://graph.facebook.com/v21.0/act_ID/insights","sendQuery":true,"queryParameters":{"parameters":[{"name":"fields","value":"account_id,spend,impressions,purchase_roas,actions"},{"name":"date_preset","value":"today"},{"name":"level","value":"account"}]},"options":{}}},{"id":"a1b2c3d4-1111-4222-8333-000000000003","name":"Fetch Ad Set Insights Today","type":"n8n-nodes-base.httpRequest","typeVersion":4.2,"position":[-20,460],"parameters":{"url":"https://graph.facebook.com/v21.0/act_ID/insights","sendQuery":true,"queryParameters":{"parameters":[{"name":"fields","value":"adset_id,adset_name,spend,impressions,frequency,purchase_roas,actions,daily_budget"},{"name":"date_preset","value":"today"},{"name":"level","value":"adset"},{"name":"filtering","value":"[{\"field\":\"adset.effective_status\",\"operator\":\"IN\",\"value\":[\"ACTIVE\"]}]"},{"name":"limit","value":"200"}]},"options":{}}},{"id":"a1b2c3d4-1111-4222-8333-000000000014","name":"Shape Account Rollup","type":"n8n-nodes-base.set","typeVersion":3.3,"position":[240,140],"parameters":{"assignments":{"assignments":[{"id":"ar1","name":"account_row","value":"={{ ($json.data && $json.data[0]) || {} }}","type":"object"}]},"options":{}}},{"id":"a1b2c3d4-1111-4222-8333-000000000015","name":"Shape Ad Set Rows","type":"n8n-nodes-base.set","typeVersion":3.3,"position":[240,460],"parameters":{"assignments":{"assignments":[{"id":"as1","name":"adset_rows","value":"={{ $json.data || [] }}","type":"array"}]},"options":{}}},{"id":"a1b2c3d4-1111-4222-8333-000000000004","name":"Merge Account + Ad Set Data","type":"n8n-nodes-base.merge","typeVersion":3,"position":[500,300],"parameters":{"mode":"combine","combineBy":"combineByPosition","options":{}}},{"id":"a1b2c3d4-1111-4222-8333-000000000005","name":"Project End Of Day Spend","type":"n8n-nodes-base.code","typeVersion":2,"position":[760,300],"parameters":{"jsCode":"// ---- PACING ENGINE -------------------------------------------------------\n// Target: land the account on $10,000 spend by end of day (account timezone).\n// Meta does not spend linearly, so we project against a real intraday curve\n// instead of a flat hours/24 assumption.\nconst DAILY_TARGET = 10000;          // USD\nconst ACCOUNT_TZ_OFFSET = -8;        // account timezone vs UTC (PST)\n\n// Share of a normal day's spend that lands in each hour (sums to 1.0).\n// Derived from 90d of hourly account delivery - nights are cheap, 6-10pm is peak.\nconst HOUR_CURVE = [\n  0.012, 0.009, 0.008, 0.008, 0.010, 0.016, 0.026, 0.038,\n  0.046, 0.048, 0.049, 0.050, 0.052, 0.053, 0.054, 0.056,\n  0.060, 0.066, 0.072, 0.074, 0.068, 0.052, 0.036, 0.023\n];\n\nconst merged = $input.first().json;\n\n// Meta returns purchase_roas / actions as arrays of {action_type, value}.\nconst pickAction = (arr, type) => {\n  if (!Array.isArray(arr)) return 0;\n  const hit = arr.find(a => a.action_type === type);\n  return hit ? Number(hit.value) : 0;\n};\n\n// The two Set nodes upstream unwrap each Graph API \"data\" array into its own\n// key, so the position-merge cannot clobber one response with the other.\nconst accountRow = merged.account_row || {};\nconst adsetRows = Array.isArray(merged.adset_rows) ? merged.adset_rows : [];\n\nconst spendSoFar = Number(accountRow.spend || 0);\n\n// Hour elapsed in the ACCOUNT's day, with fractional minutes.\nconst nowUtc = new Date();\nconst localMs = nowUtc.getTime() + ACCOUNT_TZ_OFFSET * 3600000;\nconst local = new Date(localMs);\nconst hour = local.getUTCHours();\nconst minuteFrac = local.getUTCMinutes() / 60;\n\n// Fraction of the daily curve that should already be consumed.\nlet curveElapsed = 0;\nfor (let h = 0; h < hour; h++) curveElapsed += HOUR_CURVE[h];\ncurveElapsed += HOUR_CURVE[hour] * minuteFrac;\ncurveElapsed = Math.max(curveElapsed, 0.01);   // guard against a 00:05 divide-by-tiny\n\nconst expectedSpend = DAILY_TARGET * curveElapsed;\nconst projectedEod = spendSoFar / curveElapsed;\nconst paceIndex = projectedEod / DAILY_TARGET;        // 1.0 = perfectly on target\nconst driftPct = (paceIndex - 1) * 100;\nconst remainingTarget = Math.max(DAILY_TARGET - spendSoFar, 0);\nconst remainingCurve = Math.max(1 - curveElapsed, 0.02);\nconst requiredHourlyRate = remainingTarget / (remainingCurve * 24);\nconst currentHourlyRate = spendSoFar / Math.max(hour + minuteFrac, 0.5);\n\n// Account-level efficiency, used later to decide whether we are allowed to boost.\nconst revenue = adsetRows.reduce((s, r) => s + Number(r.spend || 0) * pickAction(r.purchase_roas, 'omni_purchase'), 0);\nconst accountRoas = spendSoFar > 0 ? revenue / spendSoFar : 0;\n\n// Bands: inside +-4% we do nothing (Meta's own pacing noise is ~3%).\nlet action = 'hold';\nif (driftPct > 12) action = 'throttle_hard';\nelse if (driftPct > 4) action = 'throttle';\nelse if (driftPct < -12) action = 'boost_hard';\nelse if (driftPct < -4) action = 'boost';\n\nconst adsets = adsetRows.map(r => {\n  const spend = Number(r.spend || 0);\n  const roas = pickAction(r.purchase_roas, 'omni_purchase');\n  const purchases = pickAction(r.actions, 'omni_purchase');\n  const budget = Number(r.daily_budget || 0) / 100;   // Meta returns cents\n  const freq = Number(r.frequency || 0);\n  const impressions = Number(r.impressions || 0);\n  const cpa = purchases > 0 ? spend / purchases : null;\n  const budgetUsed = budget > 0 ? spend / budget : 0;\n\n  // Efficiency score decides who gets the extra dollars / who gets cut first.\n  // ROAS dominates, volume proves it is not a 1-purchase fluke,\n  // frequency above 2.6 is fatigue and gets punished.\n  const roasScore = Math.min(roas / 3, 1.5) * 60;\n  const volumeScore = Math.min(purchases / 12, 1) * 25;\n  const fatiguePenalty = Math.max(0, freq - 2.6) * 18;\n  const deliveryScore = Math.min(budgetUsed / Math.max(curveElapsed, 0.05), 1) * 15;\n  const score = Math.round(Math.max(0, roasScore + volumeScore + deliveryScore - fatiguePenalty));\n\n  return {\n    adset_id: r.adset_id,\n    adset_name: r.adset_name,\n    spend: Number(spend.toFixed(2)),\n    purchase_roas: Number(roas.toFixed(2)),\n    purchases,\n    impressions,\n    frequency: Number(freq.toFixed(2)),\n    cpa: cpa ? Number(cpa.toFixed(2)) : null,\n    daily_budget: Number(budget.toFixed(2)),\n    budget_used_pct: Number((budgetUsed * 100).toFixed(1)),\n    score\n  };\n}).sort((a, b) => b.score - a.score);\n\nreturn [{ json: {\n  account_id: accountRow.account_id || 'act_ID',\n  local_hour: Number((hour + minuteFrac).toFixed(2)),\n  curve_elapsed_pct: Number((curveElapsed * 100).toFixed(1)),\n  daily_target: DAILY_TARGET,\n  spend_so_far: Number(spendSoFar.toFixed(2)),\n  expected_spend: Number(expectedSpend.toFixed(2)),\n  projected_eod_spend: Number(projectedEod.toFixed(2)),\n  pace_index: Number(paceIndex.toFixed(3)),\n  drift_pct: Number(driftPct.toFixed(2)),\n  abs_drift_pct: Number(Math.abs(driftPct).toFixed(2)),\n  current_hourly_rate: Number(currentHourlyRate.toFixed(2)),\n  required_hourly_rate: Number(requiredHourlyRate.toFixed(2)),\n  remaining_target: Number(remainingTarget.toFixed(2)),\n  account_roas: Number(accountRoas.toFixed(2)),\n  action,\n  adsets\n} }];"}},{"id":"a1b2c3d4-1111-4222-8333-000000000006","name":"Off Pace By More Than 4%?","type":"n8n-nodes-base.if","typeVersion":2,"position":[1020,300],"parameters":{"conditions":{"options":{"caseSensitive":true,"version":2},"combinator":"and","conditions":[{"id":"c1","leftValue":"={{ $json.abs_drift_pct }}","rightValue":4,"operator":{"type":"number","operation":"gt"}}]},"options":{}}},{"id":"a1b2c3d4-1111-4222-8333-000000000007","name":"Plan Budget Adjustments","type":"n8n-nodes-base.code","typeVersion":2,"position":[1280,120],"parameters":{"jsCode":"// ---- BUDGET ALLOCATOR ----------------------------------------------------\n// Turn \"we will land at $X instead of $10k\" into concrete per-ad-set budget\n// writes, respecting Meta's practical constraints:\n//   * never move a budget more than 25% in one hop (resets the learning phase)\n//   * never drop an ad set below the $75 floor (it stops delivering entirely)\n//   * only 6 writes per run, biggest levers first (rate limits + stability)\n//   * never boost a losing ad set just to hit a spend number\nconst p = $input.first().json;\n\nconst MAX_STEP = 0.25;       // +-25% per adjustment\nconst MIN_BUDGET = 75;       // USD floor per ad set\nconst MAX_BUDGET = 2500;     // USD ceiling per ad set\nconst MIN_ROAS_TO_BOOST = 1.8;\nconst MAX_WRITES = 6;\n\nconst boosting = p.action === 'boost' || p.action === 'boost_hard';\nconst aggression = (p.action === 'boost_hard' || p.action === 'throttle_hard') ? 1.0 : 0.6;\n\n// Dollars we need to add to / remove from the remaining day.\nconst gap = (p.daily_target - p.projected_eod_spend) * aggression;\n\n// Candidates: boosting climbs the score list, throttling starts at the bottom.\nconst eligible = boosting\n  ? p.adsets.filter(a => a.purchase_roas >= MIN_ROAS_TO_BOOST && a.daily_budget < MAX_BUDGET)\n  : p.adsets.filter(a => a.daily_budget > MIN_BUDGET).slice().reverse();\n\nif (!eligible.length) {\n  return [{ json: {\n    ...p,\n    no_lever: true,\n    note: boosting\n      ? 'Underpacing but no ad set clears the ' + MIN_ROAS_TO_BOOST + 'x ROAS floor - refusing to buy junk spend.'\n      : 'Overpacing but every ad set is already at the $' + MIN_BUDGET + ' floor.',\n    updates: []\n  } }];\n}\n\n// Weight the gap by score so the best ad sets absorb most of the change when\n// boosting, and the worst absorb most of the cut when throttling.\nconst pool = eligible.slice(0, MAX_WRITES);\nconst weights = pool.map((a, i) => boosting ? (a.score + 10) : (110 - a.score) + i * 5);\nconst weightSum = weights.reduce((s, w) => s + w, 0) || 1;\n\nconst updates = [];\nlet allocated = 0;\n\npool.forEach((a, i) => {\n  const share = (weights[i] / weightSum) * gap;\n  const rawNew = a.daily_budget + share;\n\n  const capped = Math.min(\n    a.daily_budget * (1 + MAX_STEP),\n    Math.max(a.daily_budget * (1 - MAX_STEP), rawNew)\n  );\n  const bounded = Math.min(MAX_BUDGET, Math.max(MIN_BUDGET, capped));\n  const newBudget = Math.round(bounded);\n  const delta = newBudget - a.daily_budget;\n\n  if (Math.abs(delta) < 5) return;   // not worth a learning-phase reset\n  allocated += delta;\n\n  updates.push({\n    adset_id: a.adset_id,\n    adset_name: a.adset_name,\n    old_daily_budget: a.daily_budget,\n    new_daily_budget: newBudget,\n    new_daily_budget_cents: newBudget * 100,\n    delta_usd: Number(delta.toFixed(2)),\n    delta_pct: Number(((delta / a.daily_budget) * 100).toFixed(1)),\n    purchase_roas: a.purchase_roas,\n    frequency: a.frequency,\n    score: a.score,\n    direction: delta > 0 ? 'boost' : 'throttle',\n    reason: delta > 0\n      ? `${a.purchase_roas}x ROAS, score ${a.score} - absorbing underpace`\n      : `${a.purchase_roas}x ROAS, score ${a.score} - trimming to protect target`\n  });\n});\n\nreturn updates.map(u => ({ json: {\n  ...u,\n  account_id: p.account_id,\n  pace_index: p.pace_index,\n  drift_pct: p.drift_pct,\n  projected_eod_spend: p.projected_eod_spend,\n  spend_so_far: p.spend_so_far,\n  daily_target: p.daily_target,\n  gap_usd: Number(gap.toFixed(2)),\n  allocated_usd: Number(allocated.toFixed(2)),\n  action: p.action\n} }));"}},{"id":"a1b2c3d4-1111-4222-8333-000000000008","name":"Mark Account On Pace","type":"n8n-nodes-base.set","typeVersion":3.3,"position":[1280,520],"parameters":{"assignments":{"assignments":[{"id":"s1","name":"account_id","value":"={{ $json.account_id }}","type":"string"},{"id":"s2","name":"status","value":"on_pace","type":"string"},{"id":"s3","name":"spend_so_far","value":"={{ $json.spend_so_far }}","type":"number"},{"id":"s4","name":"projected_eod_spend","value":"={{ $json.projected_eod_spend }}","type":"number"},{"id":"s5","name":"drift_pct","value":"={{ $json.drift_pct }}","type":"number"},{"id":"s6","name":"note","value":"=Within tolerance at hour {{ $json.local_hour }} — no budget writes.","type":"string"}]},"options":{}}},{"id":"a1b2c3d4-1111-4222-8333-000000000009","name":"Loop Ad Set Budget Writes","type":"n8n-nodes-base.splitInBatches","typeVersion":3,"position":[1540,120],"parameters":{"batchSize":1,"options":{}}},{"id":"a1b2c3d4-1111-4222-8333-00000000000a","name":"Apply New Daily Budget","type":"n8n-nodes-base.httpRequest","typeVersion":4.2,"position":[1800,-60],"parameters":{"method":"POST","url":"=https://graph.facebook.com/v21.0/{{ $json.adset_id }}","sendBody":true,"specifyBody":"json","jsonBody":"={{ JSON.stringify({ daily_budget: $json.new_daily_budget_cents, status: \"ACTIVE\" }) }}","options":{}}},{"id":"a1b2c3d4-1111-4222-8333-00000000000b","name":"Throttle Graph API Writes","type":"n8n-nodes-base.wait","typeVersion":1.1,"position":[2060,-60],"parameters":{"amount":20,"unit":"seconds"}},{"id":"a1b2c3d4-1111-4222-8333-00000000000c","name":"Build Pacing Digest","type":"n8n-nodes-base.code","typeVersion":2,"position":[1800,120],"parameters":{"jsCode":"// ---- DIGEST + DRIFT CHECK ------------------------------------------------\n// Re-project the day AFTER the writes land, dedupe by ad set (the loop can\n// re-emit an ad set if a retry fired), and decide whether a human needs paging.\nconst rows = $input.all().map(i => i.json).filter(r => r && r.adset_id);\n\nconst seen = new Set();\nconst applied = [];\nfor (const r of rows) {\n  if (seen.has(r.adset_id)) continue;\n  seen.add(r.adset_id);\n  applied.push(r);\n}\n\nconst head = applied[0] || $input.first().json || {};\nconst target = head.daily_target || 10000;\nconst spendSoFar = head.spend_so_far || 0;\nconst projectedBefore = head.projected_eod_spend || target;\n\n// Each dollar of daily budget only converts into remaining-day spend in\n// proportion to the day still ahead of us.\nconst remainingShare = Math.max(0, Math.min(1, (target - spendSoFar) / target));\nconst netDelta = applied.reduce((s, r) => s + Number(r.delta_usd || 0), 0);\nconst projectedAfter = projectedBefore + netDelta * remainingShare;\nconst residualDriftPct = ((projectedAfter - target) / target) * 100;\n\nconst boosted = applied.filter(r => r.direction === 'boost');\nconst throttled = applied.filter(r => r.direction === 'throttle');\n\nconst lines = applied.map(r =>\n  `${r.direction === 'boost' ? '▲' : '▼'} ${r.adset_name}  $${r.old_daily_budget} → $${r.new_daily_budget} (${r.delta_pct}%)  ${r.purchase_roas}x`\n);\n\nreturn [{ json: {\n  account_id: head.account_id,\n  daily_target: target,\n  spend_so_far: Number(spendSoFar.toFixed(2)),\n  projected_before: Number(projectedBefore.toFixed(2)),\n  projected_after: Number(projectedAfter.toFixed(2)),\n  residual_drift_pct: Number(residualDriftPct.toFixed(2)),\n  abs_residual_drift: Number(Math.abs(residualDriftPct).toFixed(2)),\n  net_budget_delta: Number(netDelta.toFixed(2)),\n  updates_applied: applied.length,\n  boosted_count: boosted.length,\n  throttled_count: throttled.length,\n  action: head.action,\n  summary: lines.join('\\n') || 'No budget writes were needed.',\n  html: `<h3>Meta pacing — $${target}/day</h3><p>Spent so far: <b>$${spendSoFar.toFixed(2)}</b><br>Projected EOD before: $${projectedBefore.toFixed(2)}<br>Projected EOD after: <b>$${projectedAfter.toFixed(2)}</b> (${residualDriftPct.toFixed(1)}% drift)</p><pre>${lines.join('\\n')}</pre>`\n} }];"}},{"id":"a1b2c3d4-1111-4222-8333-00000000000d","name":"Residual Drift Over 8%?","type":"n8n-nodes-base.if","typeVersion":2,"position":[2060,120],"parameters":{"conditions":{"options":{"caseSensitive":true,"version":2},"combinator":"and","conditions":[{"id":"d1","leftValue":"={{ $json.abs_residual_drift }}","rightValue":8,"operator":{"type":"number","operation":"gt"}}]},"options":{}}},{"id":"a1b2c3d4-1111-4222-8333-00000000000e","name":"Page Media Buyer","type":"n8n-nodes-base.slack","typeVersion":2.2,"position":[2320,-40],"parameters":{"select":"channel","channelId":{"__rl":true,"value":"#ads-warroom","mode":"name"},"text":"=🚨 PACING DRIFT — {{ $json.account_id }}\nTarget $10,000 · spent ${{ $json.spend_so_far }}\nProjected EOD after writes: ${{ $json.projected_after }} ({{ $json.residual_drift_pct }}%)\nBudgets moved: {{ $json.net_budget_delta }} across {{ $json.updates_applied }} ad sets\n{{ $json.summary }}\nBudget levers were not enough — needs a human.","otherOptions":{}}},{"id":"a1b2c3d4-1111-4222-8333-00000000000f","name":"Post Pacing Update","type":"n8n-nodes-base.slack","typeVersion":2.2,"position":[2320,280],"parameters":{"select":"channel","channelId":{"__rl":true,"value":"#ads-pacing","mode":"name"},"text":"=✅ Pacing corrected — {{ $json.account_id }}\nSpent ${{ $json.spend_so_far }} · projected EOD ${{ $json.projected_after }} vs $10,000 ({{ $json.residual_drift_pct }}%)\n▲ {{ $json.boosted_count }} boosted · ▼ {{ $json.throttled_count }} throttled\n{{ $json.summary }}","otherOptions":{}}},{"id":"a1b2c3d4-1111-4222-8333-000000000010","name":"Log Pacing Snapshot","type":"n8n-nodes-base.googleSheets","typeVersion":4.5,"position":[2580,300],"parameters":{"operation":"append","documentId":{"__rl":true,"value":"SHEET_ID","mode":"id"},"sheetName":{"__rl":true,"value":"gid=0","mode":"list","cachedResultName":"Pacing Log"},"columns":{"mappingMode":"autoMapInputData","value":{},"matchingColumns":[]},"options":{}}},{"id":"a1b2c3d4-1111-4222-8333-000000000011","name":"Pacing Section Note","type":"n8n-nodes-base.stickyNote","typeVersion":1,"position":[-320,-100],"parameters":{"content":"## 1. MEASURE THE PACE\nEvery hour, pull today's account spend and every active ad set.\nThe Code node projects end-of-day spend against a real intraday delivery\ncurve (nights are cheap, 6-10pm is peak) instead of a flat hours/24 split,\nthen scores each ad set on ROAS, purchase volume and frequency fatigue.","height":340,"width":500,"color":4}},{"id":"a1b2c3d4-1111-4222-8333-000000000012","name":"Correction Section Note","type":"n8n-nodes-base.stickyNote","typeVersion":1,"position":[1260,-300],"parameters":{"content":"## 2. CORRECT THE PACE\nOutside a ±4% dead band we reallocate budget: boosts go to the highest\nscoring ad sets that clear 1.8x ROAS, cuts start from the worst.\nGuardrails: max ±25% per hop (learning phase), $75 floor, $2,500 ceiling,\n6 writes per run, 20s between Graph API calls.","height":300,"width":480,"color":3}},{"id":"a1b2c3d4-1111-4222-8333-000000000013","name":"Alert Section Note","type":"n8n-nodes-base.stickyNote","typeVersion":1,"position":[2280,460],"parameters":{"content":"## 3. ALERT + LOG\nRe-project after the writes. If more than 8% drift survives, the budget\nlevers ran out — page the buyer in #ads-warroom. Otherwise post the routine\nupdate. Both paths append the snapshot to the pacing log.","height":280,"width":460,"color":5}}],"connections":{"Hourly Pacing Check":{"main":[[{"node":"Fetch Account Spend Today","type":"main","index":0},{"node":"Fetch Ad Set Insights Today","type":"main","index":0}]]},"Fetch Account Spend Today":{"main":[[{"node":"Shape Account Rollup","type":"main","index":0}]]},"Fetch Ad Set Insights Today":{"main":[[{"node":"Shape Ad Set Rows","type":"main","index":0}]]},"Merge Account + Ad Set Data":{"main":[[{"node":"Project End Of Day Spend","type":"main","index":0}]]},"Project End Of Day Spend":{"main":[[{"node":"Off Pace By More Than 4%?","type":"main","index":0}]]},"Off Pace By More Than 4%?":{"main":[[{"node":"Plan Budget Adjustments","type":"main","index":0}],[{"node":"Mark Account On Pace","type":"main","index":0}]]},"Plan Budget Adjustments":{"main":[[{"node":"Loop Ad Set Budget Writes","type":"main","index":0}]]},"Loop Ad Set Budget Writes":{"main":[[{"node":"Build Pacing Digest","type":"main","index":0}],[{"node":"Apply New Daily Budget","type":"main","index":0}]]},"Apply New Daily Budget":{"main":[[{"node":"Throttle Graph API Writes","type":"main","index":0}]]},"Throttle Graph API Writes":{"main":[[{"node":"Loop Ad Set Budget Writes","type":"main","index":0}]]},"Build Pacing Digest":{"main":[[{"node":"Residual Drift Over 8%?","type":"main","index":0}]]},"Residual Drift Over 8%?":{"main":[[{"node":"Page Media Buyer","type":"main","index":0}],[{"node":"Post Pacing Update","type":"main","index":0}]]},"Page Media Buyer":{"main":[[{"node":"Log Pacing Snapshot","type":"main","index":0}]]},"Post Pacing Update":{"main":[[{"node":"Log Pacing Snapshot","type":"main","index":0}]]},"Mark Account On Pace":{"main":[[{"node":"Log Pacing Snapshot","type":"main","index":0}]]},"Shape Account Rollup":{"main":[[{"node":"Merge Account + Ad Set Data","type":"main","index":0}]]},"Shape Ad Set Rows":{"main":[[{"node":"Merge Account + Ad Set Data","type":"main","index":1}]]}},"settings":{"executionOrder":"v1"},"pinData":{}}