{
  "name": "Aumadi \u2014 Daily Product Pulse Email",
  "tags": [],
  "nodes": [
    {
      "id": "8b820e94-5fce-47fb-8a3e-ad85d5ba73f4",
      "name": "Section \u2014 Understand the DB",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -2540,
        300
      ],
      "parameters": {
        "color": 7,
        "width": 800,
        "height": 440,
        "content": "## \u2460 Read your requirement + live schema\n\nThe schedule fires. **Discover Schema** reads your Supabase OpenAPI (every table + column). **Read Schema** condenses it so Claude can only reference what truly exists."
      },
      "typeVersion": 1
    },
    {
      "id": "1b77df16-2b61-447d-8f6f-5405a0c8b81b",
      "name": "Section \u2014 Plan + guard",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1680,
        300
      ],
      "parameters": {
        "color": 7,
        "width": 440,
        "height": 440,
        "content": "## \u2461 Plan the queries (then guard them)\n\n**Plan Queries**: Claude builds PostgREST queries using only your real tables/columns + placeholder dates. **Validate & Resolve** drops anything whose table is not in your DB and fills in real timestamps \u2014 so a wrong query never runs."
      },
      "typeVersion": 1
    },
    {
      "id": "d7d68d9e-036b-479a-a564-f10237f1f169",
      "name": "Section \u2014 Run + report",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1180,
        300
      ],
      "parameters": {
        "color": 7,
        "width": 1620,
        "height": 560,
        "content": "## \u2462 Run \u2192 count \u2192 guard \u2192 report\n\n**Run Query** executes each query; **Collect Results** counts/sums/groups. **Found requirement data?** routes: matched \u2192 **Write Report** \u2192 email; matched nothing \u2192 a clear \"not found\" email instead of a blank report."
      },
      "typeVersion": 1
    },
    {
      "id": "3c74caca-26e0-4522-8c35-975e1b76b0e6",
      "name": "\ud83d\udcd8 Overview",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -3120,
        300
      ],
      "parameters": {
        "width": 540,
        "height": 480,
        "content": "## \ud83e\udde0 Adaptive Daily Pulse (you write the requirement)\n\n### Who's it for\nAnyone on Supabase who wants a morning report \u2014 but defined in **plain English**, not hardcoded. Point it at any Supabase project and it adapts.\n\n### How it works\n1. You write what you want in **\u2699\ufe0f Config \u2192 requirements** (free text).\n2. The workflow **reads your live Supabase schema** (which tables/columns exist).\n3. **Claude** plans the exact queries \u2014 **using only tables/columns that exist**, with safe PostgREST syntax + placeholder dates.\n4. A **validator** drops any query whose table isn't in your DB (never a wrong query).\n5. Queries run over the **Supabase API**, results are computed in n8n, **Claude writes the report**, and it's emailed.\n\n### Zero setup, simple credential\nNo functions, no Postgres connection \u2014 just your normal **Supabase API** credential."
      },
      "typeVersion": 1
    },
    {
      "id": "db8395ed-43ea-46d3-9dd5-46f688c23161",
      "name": "\ud83d\udd27 Setup",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -3120,
        820
      ],
      "parameters": {
        "width": 540,
        "height": 320,
        "content": "## \ud83d\udd27 Setup\n1. Credentials: **Supabase API** (host + service key), **Anthropic** (Header Auth `x-api-key`), **SMTP**.\n2. In **\u2699\ufe0f Config**: set `supabaseUrl`, `reportEmail`, `fromEmail`, `brandName`, `windowHours`, and your **requirements** (plain English \u2014 e.g. *how many orders in the last 24h and revenue, new signups, top restaurants, and how many users are now inactive 30+ days*).\n3. Set the **schedule** to your hour/timezone, then Activate (or 'Test workflow').\n\nChange the **requirements** text anytime \u2192 the report changes. Things your DB doesn't have are listed as 'skipped', never guessed."
      },
      "typeVersion": 1
    },
    {
      "id": "f710cb5f-cb1a-4285-98aa-894bfedd4fb4",
      "name": "Every Morning 8am",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        -2480,
        590
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "triggerAtHour": 8
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "4f89cf38-74f4-4c2d-91c8-60f6e153ca73",
      "name": "\u2699\ufe0f Config",
      "type": "n8n-nodes-base.set",
      "position": [
        -2280,
        590
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "c_url",
              "name": "supabaseUrl",
              "type": "string",
              "value": "https://YOUR_PROJECT.supabase.co"
            },
            {
              "id": "c_to",
              "name": "reportEmail",
              "type": "string",
              "value": "user@example.com"
            },
            {
              "id": "c_from",
              "name": "fromEmail",
              "type": "string",
              "value": "user@example.com"
            },
            {
              "id": "c_brand",
              "name": "brandName",
              "type": "string",
              "value": "Your Company"
            },
            {
              "id": "c_win",
              "name": "windowHours",
              "type": "number",
              "value": 24
            },
            {
              "id": "c_model",
              "name": "textModel",
              "type": "string",
              "value": "claude-haiku-4-5-20251001"
            },
            {
              "id": "c_req",
              "name": "requirements",
              "type": "string",
              "value": "For the last 1 week, using the demo_ SaaS tables: how many new signups (demo_users), total revenue from successful payments and how many payments failed (demo_payments), how many users are active vs inactive with no activity for 30+ days (demo_users.last_active_at), signups broken down by country (demo_users.country), how many subscriptions are currently canceled (demo_subscriptions), and how many support tickets are open by priority (demo_tickets)."
            },
            {
              "id": "da810d60-4f42-43a1-b197-4589e1c86c76",
              "name": "planSystem",
              "type": "string",
              "value": "You are a careful analytics QUERY PLANNER. You convert a plain-English reporting REQUIREMENT into safe, correct Supabase PostgREST read queries, using ONLY the tables and columns in the SCHEMA you are given.  RULES \u2014 follow exactly: 1. ONLY use table names and column names that appear verbatim in SCHEMA. NEVER invent a table or column. If a requested metric needs something not in SCHEMA, do NOT query it \u2014 list it under \"skipped\" with a short reason. 2. For any date/time, use ONLY these placeholder tokens (replaced later): NOW, SINCE_WINDOW, SINCE_24H, SINCE_48H, SINCE_72H, SINCE_7D, SINCE_30D, SINCE_31D, SINCE_90D. Never write a literal date. 3. Each \"query\" is a PostgREST query string (everything AFTER the table name): filters like column=eq.value | =gte.value | =lt.value | =is.null ; combine with & ; OR via or=(column.lt.SINCE_30D,column.is.null) ; select=col1,col2 ; embed: select=col1,related_table(col) ; a timestamp uses a placeholder e.g. created_at=gte.SINCE_WINDOW. 4. Pick an \"op\": \"count\" (count rows; select one small real column), \"sum\" (set value_column to a numeric column), or \"group\" (set group_column to a column or embedded like restaurants.branch_name; top 5). 5. Only build metrics the REQUIREMENT asks for. Use SINCE_WINDOW for the main window unless a specific window is named (e.g. 30 days -> SINCE_30D). 6. Output ONLY a raw JSON object \u2014 no markdown, no code fences.  OUTPUT SHAPE (example \u2014 use the REAL schema): { \"metrics\": [ { \"id\": \"new_signups\", \"label\": \"New signups (24h)\", \"table\": \"demo_users\", \"query\": \"select=created_at&created_at=gte.SINCE_WINDOW\", \"op\": \"count\" }, { \"id\": \"revenue\", \"label\": \"Revenue (24h)\", \"table\": \"demo_payments\", \"query\": \"select=amount&status=eq.succeeded&created_at=gte.SINCE_WINDOW\", \"op\": \"sum\", \"value_column\": \"amount\" } ], \"skipped\": [ { \"requested\": \"refunds\", \"reason\": \"no refunds table\" } ] }  When unsure, SKIP rather than guess."
            },
            {
              "id": "8830437d-c09d-4ede-91b7-97977ce3bb55",
              "name": "reportSystem",
              "type": "string",
              "value": "You write a SHORT daily pulse email from already-computed metrics. You are given the original requirement and a results array (each item has label, op, value). Reply with ONLY a raw JSON object (no markdown) with keys: headline (one upbeat line), summary (2-3 plain sentences citing the actual numbers), insights (array of 2-3 short flags). Warm, concrete, no jargon."
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "40d7ec84-837c-4da3-9382-5a8ecc8f7187",
      "name": "\ud83d\uddc2\ufe0f Discover Schema",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -2080,
        590
      ],
      "parameters": {
        "url": "={{ $('\u2699\ufe0f Config').first().json.supabaseUrl }}/rest/v1/",
        "options": {
          "timeout": 20000
        },
        "sendHeaders": true,
        "authentication": "predefinedCredentialType",
        "headerParameters": {
          "parameters": [
            {
              "name": "Accept",
              "value": "application/openapi+json"
            }
          ]
        },
        "nodeCredentialType": "supabaseApi"
      },
      "credentials": {
        "supabaseApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "26ae3824-967b-4f8a-bd13-a6720e732f42",
      "name": "\ud83e\udde9 Read Schema",
      "type": "n8n-nodes-base.code",
      "position": [
        -1860,
        590
      ],
      "parameters": {
        "jsCode": "let raw = $input.first().json;\nlet body = raw;\nif (raw && raw.body !== undefined) body = raw.body;\nelse if (raw && raw.data !== undefined) body = raw.data;\nif (typeof body === 'string') { try { body = JSON.parse(body); } catch (e) {} }\nconst defs = (body && (body.definitions || (body.components && body.components.schemas))) || {};\nconst tableMap = {}; const lines = [];\nfor (const t of Object.keys(defs)) {\n  const props = (defs[t] && defs[t].properties) ? Object.keys(defs[t].properties) : [];\n  tableMap[t] = props;\n  lines.push(t + '(' + props.join(', ') + ')');\n}\nreturn [{ json: { schemaText: lines.join('\\n'), tableMap: tableMap, tableCount: lines.length } }];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "e2c12f85-4348-4b72-b26f-d7960410b900",
      "name": "\ud83e\udde0 Plan Queries",
      "type": "n8n-nodes-base.httpRequest",
      "onError": "continueRegularOutput",
      "position": [
        -1620,
        590
      ],
      "parameters": {
        "url": "https://api.anthropic.com/v1/messages",
        "method": "POST",
        "options": {
          "timeout": 40000
        },
        "jsonBody": "={{ JSON.stringify({ model: $('\u2699\ufe0f Config').first().json.textModel, max_tokens: 1500, system: $('\u2699\ufe0f Config').first().json.planSystem, messages: [ { role: 'user', content: 'REQUIREMENT:\\n' + $('\u2699\ufe0f Config').first().json.requirements + '\\n\\nMain window: last ' + $('\u2699\ufe0f Config').first().json.windowHours + ' hours (use SINCE_WINDOW).\\n\\nSCHEMA (only these tables/columns exist):\\n' + $json.schemaText + '\\n\\nPlan the metrics as JSON now.' } ] }) }}\n",
        "sendBody": true,
        "sendHeaders": true,
        "specifyBody": "json",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "headerParameters": {
          "parameters": [
            {
              "name": "content-type",
              "value": "application/json"
            },
            {
              "name": "anthropic-version",
              "value": "2023-06-01"
            }
          ]
        }
      },
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "4a0faed7-98ee-4813-aa04-7a61ad612311",
      "name": "\u2705 Validate & Resolve",
      "type": "n8n-nodes-base.code",
      "position": [
        -1400,
        590
      ],
      "parameters": {
        "jsCode": "const c = $input.first().json;\nlet txt = '';\ntry { if (Array.isArray(c.content)) txt = c.content.map(x => x.text || '').join(''); } catch (e) {}\ntxt = String(txt).trim().replace(/^\\x60{3}(?:json)?/i, '').replace(/\\x60{3}$/, '').trim();\nlet plan = { metrics: [], skipped: [] };\ntry { plan = JSON.parse(txt); } catch (e) {}\n\nconst cfg = $('\u2699\ufe0f Config').first().json;\nconst tableMap = $('\ud83e\udde9 Read Schema').first().json.tableMap || {};\nconst base = String(cfg.supabaseUrl).replace(/\\/$/, '') + '/rest/v1/';\nconst now = Date.now(); const H = 3600 * 1000; const D = 24 * H;\nconst iso = ms => new Date(ms).toISOString();\nconst ph = {\n  SINCE_WINDOW: iso(now - (Number(cfg.windowHours || 24)) * H),\n  SINCE_24H: iso(now-24*H), SINCE_48H: iso(now-48*H), SINCE_72H: iso(now-72*H),\n  SINCE_7D: iso(now-7*D), SINCE_30D: iso(now-30*D), SINCE_31D: iso(now-31*D), SINCE_90D: iso(now-90*D), NOW: iso(now)\n};\nconst resolve = s => { let out = String(s || ''); for (const k of Object.keys(ph)) { out = out.split(k).join(ph[k]); } return out; };\n\nconst skipped = Array.isArray(plan.skipped) ? plan.skipped.slice() : [];\nconst out = [];\nfor (const m of (plan.metrics || [])) {\n  if (!m || !m.table || !tableMap[m.table]) { skipped.push({ requested: (m && m.label) || '(unknown)', reason: 'table not in your database' }); continue; }\n  let q = resolve(m.query || '');\n  if (q && q[0] === '?') q = q.slice(1);\n  const url = base + m.table + '?' + (q ? q + '&' : '') + 'limit=10000';\n  out.push({ json: { id: m.id || m.label, label: m.label || m.id || m.table, op: m.op || 'count', value_column: m.value_column || null, group_column: m.group_column || null, url: url, skipped: skipped } });\n}\nif (out.length === 0) { out.push({ json: { dummy: true, url: base, op: 'count', label: 'none', skipped: skipped } }); }\nreturn out;\n"
      },
      "typeVersion": 2
    },
    {
      "id": "6645ef3b-301e-4f06-b6dd-5d89aa935ea8",
      "name": "\ud83d\udd0e Run Query",
      "type": "n8n-nodes-base.httpRequest",
      "onError": "continueRegularOutput",
      "position": [
        -1120,
        590
      ],
      "parameters": {
        "url": "={{ $json.url }}",
        "options": {
          "timeout": 20000,
          "response": {
            "response": {
              "fullResponse": true
            }
          }
        },
        "sendHeaders": true,
        "authentication": "predefinedCredentialType",
        "headerParameters": {
          "parameters": [
            {
              "name": "Accept",
              "value": "application/json"
            }
          ]
        },
        "nodeCredentialType": "supabaseApi"
      },
      "credentials": {
        "supabaseApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.2,
      "alwaysOutputData": true
    },
    {
      "id": "497287b6-f93c-4b3d-ab6c-c768a58a85e9",
      "name": "\ud83e\uddee Collect Results",
      "type": "n8n-nodes-base.code",
      "position": [
        -900,
        590
      ],
      "parameters": {
        "jsCode": "const metrics = $('\u2705 Validate & Resolve').all().map(i => i.json);\nconst resp = $('\ud83d\udd0e Run Query').all().map(i => i.json);\nconst results = [];\nfor (let i = 0; i < metrics.length; i++) {\n  const m = metrics[i];\n  if (m.dummy) continue;\n  const r = resp[i] || {};\n  const rows = (r && Array.isArray(r.body)) ? r.body : (Array.isArray(r) ? r : []);\n  let value;\n  if (m.op === 'sum') {\n    value = Math.round(rows.reduce(function (s, x) { return s + Number((x && x[m.value_column]) || 0); }, 0));\n  } else if (m.op === 'group') {\n    const pm = {}; const gc = String(m.group_column || '');\n    for (const x of rows) { let v = x; for (const part of gc.split('.')) { v = (v && typeof v === 'object') ? v[part] : undefined; } const key = (v == null ? 'Unknown' : String(v)); pm[key] = (pm[key] || 0) + 1; }\n    value = Object.keys(pm).map(function (k) { return { key: k, count: pm[k] }; }).sort(function (a, b) { return b.count - a.count; }).slice(0, 5);\n  } else { value = rows.length; }\n  results.push({ id: m.id, label: m.label, op: m.op, value: value });\n}\nconst skipped = (metrics[0] && metrics[0].skipped) || [];\nreturn [{ json: { requirements: $('\u2699\ufe0f Config').first().json.requirements, results: results, skipped: skipped, resultCount: results.length } }];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "885a30fd-0b17-41ce-b82f-71815098d875",
      "name": "\ud83e\udde0 Write Report",
      "type": "n8n-nodes-base.httpRequest",
      "onError": "continueRegularOutput",
      "position": [
        -440,
        470
      ],
      "parameters": {
        "url": "https://api.anthropic.com/v1/messages",
        "method": "POST",
        "options": {
          "timeout": 30000
        },
        "jsonBody": "={{ JSON.stringify({ model: $('\u2699\ufe0f Config').first().json.textModel, max_tokens: 500, system: $('\u2699\ufe0f Config').first().json.reportSystem, messages: [ { role: 'user', content: 'Brand: ' + $('\u2699\ufe0f Config').first().json.brandName + '\\nRequirement: ' + $json.requirements + '\\nResults JSON: ' + JSON.stringify($json.results) + '\\nSkipped: ' + JSON.stringify($json.skipped) + '\\n\\nWrite the report JSON.' } ] }) }}\n",
        "sendBody": true,
        "sendHeaders": true,
        "specifyBody": "json",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "headerParameters": {
          "parameters": [
            {
              "name": "content-type",
              "value": "application/json"
            },
            {
              "name": "anthropic-version",
              "value": "2023-06-01"
            }
          ]
        }
      },
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "5471ff0f-6c2c-4fd2-93f0-714c6a759e05",
      "name": "Extract Report",
      "type": "n8n-nodes-base.code",
      "position": [
        -200,
        470
      ],
      "parameters": {
        "jsCode": "const r = $input.first().json;\nlet t = '';\ntry { if (Array.isArray(r.content)) t = r.content.map(c => c.text || '').join(''); } catch (e) {}\nt = String(t).trim().replace(/^\\x60{3}(?:json)?/i, '').replace(/\\x60{3}$/, '').trim();\nlet o = { headline: 'Your daily pulse', summary: '', insights: [] };\ntry { const j = JSON.parse(t); o.headline = j.headline || o.headline; o.summary = j.summary || ''; o.insights = Array.isArray(j.insights) ? j.insights : []; } catch (e) {}\nreturn [{ json: o }];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "230aa2b0-1598-47ca-bc63-144e782386b1",
      "name": "Build Email",
      "type": "n8n-nodes-base.code",
      "position": [
        40,
        470
      ],
      "parameters": {
        "jsCode": "const data = $('\ud83e\uddee Collect Results').first().json;\nconst rep = $input.first().json;\nconst cfg = $('\u2699\ufe0f Config').first().json;\nconst fmt = function (v) { return (v == null ? '0' : String(v)); };\nlet cards = '';\nfor (const m of (data.results || [])) {\n  let val = (m.op === 'group')\n    ? (Array.isArray(m.value) ? m.value.map(function (g) { return g.key + ': <b>' + g.count + '</b>'; }).join('<br>') : '')\n    : '<b>' + fmt(m.value) + '</b>';\n  cards += '<tr><td style=\"padding:10px 0;border-bottom:1px solid #eee;color:#374151;font-size:14px;vertical-align:top\">' + m.label + '</td><td style=\"padding:10px 0;border-bottom:1px solid #eee;text-align:right;color:#111827;font-size:14px\">' + val + '</td></tr>';\n}\nconst skipped = (data.skipped || []).map(function (s) { return '<li>' + s.requested + ' \u2014 ' + s.reason + '</li>'; }).join('');\nconst insights = (rep.insights || []).map(function (s) { return '<li style=\"margin-bottom:4px\">' + s + '</li>'; }).join('');\nconst html = '<!DOCTYPE html><html><body style=\"margin:0;background:#f4f4f5;font-family:Arial,Helvetica,sans-serif\"><table width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" style=\"padding:24px 0\"><tr><td align=\"center\"><table width=\"600\" cellpadding=\"0\" cellspacing=\"0\" style=\"max-width:600px;background:#fff;border-radius:12px;overflow:hidden\"><tr><td style=\"background:#111827;padding:20px 28px;color:#fff;font-size:18px;font-weight:bold\">' + cfg.brandName + ' \u2014 Daily Pulse</td></tr><tr><td style=\"padding:24px 28px\"><h2 style=\"margin:0 0 6px;color:#111827;font-size:18px\">' + (rep.headline || '') + '</h2><p style=\"margin:0 0 16px;color:#374151;line-height:1.6;font-size:14px\">' + (rep.summary || '') + '</p><table width=\"100%\" cellspacing=\"0\">' + cards + '</table>' + (insights ? ('<h3 style=\"margin:20px 0 6px;font-size:14px;color:#111827\">Things to note</h3><ul style=\"margin:0;padding-left:18px;color:#374151;font-size:14px\">' + insights + '</ul>') : '') + (skipped ? ('<p style=\"margin:16px 0 4px;font-size:12px;color:#9ca3af\">Asked for but not in your database (skipped):</p><ul style=\"margin:0;padding-left:18px;color:#9ca3af;font-size:12px\">' + skipped + '</ul>') : '') + '</td></tr><tr><td style=\"padding:16px 28px;background:#f9fafb;color:#9ca3af;font-size:12px\">Generated by ' + cfg.brandName + '</td></tr></table></td></tr></table></body></html>';\nlet lines = '';\nfor (const m of (data.results || [])) { lines += m.label + ': ' + (m.op === 'group' ? (Array.isArray(m.value) ? m.value.map(function (g) { return g.key + '=' + g.count; }).join(', ') : '') : fmt(m.value)) + '\\n'; }\nreturn [{ json: { to: cfg.reportEmail, subject: cfg.brandName + ' \u2014 Daily Pulse', html: html, text: (rep.headline || 'Daily Pulse') + '\\n\\n' + (rep.summary || '') + '\\n\\n' + lines } }];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "fb662269-71a1-4de7-9eb0-5a2c9a831834",
      "name": "\u2709\ufe0f Send Report",
      "type": "n8n-nodes-base.emailSend",
      "onError": "continueRegularOutput",
      "position": [
        280,
        590
      ],
      "parameters": {
        "html": "={{ $json.html }}",
        "text": "={{ $json.text }}",
        "options": {},
        "subject": "={{ $json.subject }}",
        "toEmail": "={{ $json.to }}",
        "fromEmail": "={{ $('\u2699\ufe0f Config').first().json.fromEmail }}",
        "emailFormat": "both"
      },
      "credentials": {
        "smtp": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.1
    },
    {
      "id": "48f3b2d6-0ff1-43ad-882c-ca63278ba36b",
      "name": "Found requirement data?",
      "type": "n8n-nodes-base.if",
      "position": [
        -680,
        590
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 3,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "6f108f33-395b-4e21-aa1e-94d057a0a58c",
              "operator": {
                "type": "number",
                "operation": "gt"
              },
              "leftValue": "={{ $json.resultCount }}",
              "rightValue": 0
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "79986302-5df4-4a9a-8feb-a6fde794e25a",
      "name": "\ud83d\udedf Build Not-Found Email",
      "type": "n8n-nodes-base.code",
      "position": [
        -440,
        730
      ],
      "parameters": {
        "jsCode": "const data = $('\ud83e\uddee Collect Results').first().json;\nconst cfg = $('\u2699\ufe0f Config').first().json;\nconst skipped = (data.skipped || []).map(function (s) { return '<li>' + s.requested + ' \u2014 ' + s.reason + '</li>'; }).join('') || '<li>(nothing matched your requirement)</li>';\nconst text = \"The Daily Pulse ran, but none of the tables your requirement asked for were found, so there's nothing to report.\\n\\nLikely causes:\\n1) Table names in your requirement don't match your schema.\\n2) Tables exist but the API can't see them - run  NOTIFY pgrst, 'reload schema';  in Supabase, then retry.\\n3) Wrong Supabase API credential / project URL.\";\nconst html = \"<!DOCTYPE html><html><body style='font-family:Arial,Helvetica,sans-serif;background:#f4f4f5;margin:0;padding:24px'><table width='600' align='center' cellpadding='0' cellspacing='0' style='background:#fff;border-radius:12px;overflow:hidden'><tr><td style='background:#b45309;color:#fff;padding:20px 28px;font-size:18px;font-weight:bold'>\" + cfg.brandName + \" \u2014 no matching data for your requirement</td></tr><tr><td style='padding:24px 28px;color:#374151;font-size:14px;line-height:1.6'>The Pulse ran, but <b>none of the tables your requirement asked for were found</b>.<br><br><b>Asked for but not found:</b><ul>\" + skipped + \"</ul><b>How to fix:</b><ul><li>Match the table names in your requirement to your schema</li><li>If the tables exist, run <code>NOTIFY pgrst, 'reload schema';</code> then retry</li><li>Check the Supabase API credential + project URL</li></ul></td></tr></table></body></html>\";\nreturn [{ json: { to: cfg.reportEmail, subject: cfg.brandName + \" \u2014 no matching data for your requirement\", html: html, text: text } }];\n"
      },
      "typeVersion": 2
    }
  ],
  "active": false,
  "settings": {
    "binaryMode": "separate",
    "executionOrder": "v1"
  },
  "connections": {
    "Build Email": {
      "main": [
        [
          {
            "node": "\u2709\ufe0f Send Report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\u2699\ufe0f Config": {
      "main": [
        [
          {
            "node": "\ud83d\uddc2\ufe0f Discover Schema",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Report": {
      "main": [
        [
          {
            "node": "Build Email",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\ud83d\udd0e Run Query": {
      "main": [
        [
          {
            "node": "\ud83e\uddee Collect Results",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\ud83e\udde9 Read Schema": {
      "main": [
        [
          {
            "node": "\ud83e\udde0 Plan Queries",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Every Morning 8am": {
      "main": [
        [
          {
            "node": "\u2699\ufe0f Config",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\ud83e\udde0 Plan Queries": {
      "main": [
        [
          {
            "node": "\u2705 Validate & Resolve",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\ud83e\udde0 Write Report": {
      "main": [
        [
          {
            "node": "Extract Report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\ud83e\uddee Collect Results": {
      "main": [
        [
          {
            "node": "Found requirement data?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\u2705 Validate & Resolve": {
      "main": [
        [
          {
            "node": "\ud83d\udd0e Run Query",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Found requirement data?": {
      "main": [
        [
          {
            "node": "\ud83e\udde0 Write Report",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "\ud83d\udedf Build Not-Found Email",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\ud83d\uddc2\ufe0f Discover Schema": {
      "main": [
        [
          {
            "node": "\ud83e\udde9 Read Schema",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\ud83d\udedf Build Not-Found Email": {
      "main": [
        [
          {
            "node": "\u2709\ufe0f Send Report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}