{
  "nodes": [
    {
      "id": "1ae5c053-ffd3-45fd-b339-6a3889992361",
      "name": "04 | Code \u2013 Parse XML + Extract Last 24h Entries",
      "type": "n8n-nodes-base.code",
      "position": [
        832,
        320
      ],
      "parameters": {
        "jsCode": "// \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// NODE 04 | Code \u2013 Parse XML + Extract Last 24h Entries\n// FIX: Removed $('02 | Code...') reference \u2014 node 02 may not be\n//      executed when testing node 04 in isolation.\n//      Source is passed through automatically by the HTTP Request\n//      node from its input item, so it lives directly on $json.source\n// \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n// Step 1: Read source directly from current item's json\n// The HTTP Request node forwards all input fields to its output\nconst source = $json.source || 'unknown_source';\n\n// Step 2: Get raw XML \u2014 HTTP Request v4 puts response body under $json.data\nconst rawXml = $json.data || '';\n\nif (!rawXml || rawXml.trim().length === 0) {\n  return [{ json: { skip: 'true', source } }];\n}\n\n// Step 3: Extract content between XML tags (handles CDATA too)\nfunction extractTag(xml, tag) {\n  const patterns = [\n    new RegExp(`<${tag}[^>]*><!\\\\[CDATA\\\\[([\\\\s\\\\S]*?)\\\\]\\\\]><\\\\/${tag}>`, 'i'),\n    new RegExp(`<${tag}[^>]*>([\\\\s\\\\S]*?)<\\\\/${tag}>`, 'i')\n  ];\n  for (const p of patterns) {\n    const m = xml.match(p);\n    if (m) return m[1].trim();\n  }\n  return '';\n}\n\n// Step 4: Extract href attribute (for Atom <link href=\"...\"/>)\nfunction extractHref(xmlChunk) {\n  const m = xmlChunk.match(/href=[\"']([^\"']+)[\"']/);\n  return m ? m[1] : '';\n}\n\n// Step 5: Decode HTML entities and strip all HTML tags\nfunction cleanText(text) {\n  return text\n    .replace(/&lt;/g, '<')\n    .replace(/&gt;/g, '>')\n    .replace(/&amp;/g, '&')\n    .replace(/&quot;/g, '\"')\n    .replace(/&#39;/g, \"'\")\n    .replace(/&apos;/g, \"'\")\n    .replace(/&nbsp;/g, ' ')\n    .replace(/<[^>]+>/g, ' ')\n    .replace(/\\s{2,}/g, ' ')\n    .trim();\n}\n\n// Step 6: Split XML into individual entry/item blocks\nlet blocks = [];\nif (rawXml.includes('<entry')) {\n  // Atom format\n  blocks = rawXml\n    .split('<entry')\n    .slice(1)\n    .map(b => '<entry' + b.split('</entry>')[0] + '</entry>');\n} else if (/<item[\\s>]/.test(rawXml)) {\n  // RSS 2.0 format\n  blocks = rawXml\n    .split(/<item[\\s>]/)\n    .slice(1)\n    .map(b => '<item>' + b.split('</item>')[0] + '</item>');\n}\n\nif (blocks.length === 0) {\n  return [{ json: { skip: 'true', source } }];\n}\n\n// Step 7: 24h cutoff timestamp\nconst cutoff = new Date();\ncutoff.setHours(cutoff.getHours() - 24);\n\n// Step 8: Parse each block and filter to last 24h only\nconst items = [];\n\nfor (const block of blocks) {\n  const title = cleanText(extractTag(block, 'title')) || 'No title';\n\n  // Link extraction: Atom uses href attr, RSS uses tag text or guid\n  const linkTagMatch = block.match(/<link[^>]*\\/?>/i);\n  const linkTag = linkTagMatch ? linkTagMatch[0] : '';\n  const link =\n    extractHref(linkTag) ||\n    cleanText(extractTag(block, 'link')) ||\n    cleanText(extractTag(block, 'guid')) ||\n    '';\n\n  // Summary: try several common tag names\n  const rawSummary =\n    extractTag(block, 'summary') ||\n    extractTag(block, 'description') ||\n    extractTag(block, 'content') ||\n    '';\n  const summary = cleanText(rawSummary).substring(0, 800);\n\n  // Published date: try several common tag names\n  const publishedRaw =\n    extractTag(block, 'published') ||\n    extractTag(block, 'updated') ||\n    extractTag(block, 'pubDate') ||\n    new Date().toISOString();\n\n  const pubDate = new Date(publishedRaw);\n\n  if (!isNaN(pubDate.getTime()) && pubDate >= cutoff) {\n    items.push({\n      json: { source, title, link, summary, published: publishedRaw }\n    });\n  }\n}\n\n// Step 9: Return parsed items or skip sentinel\nreturn items.length > 0\n  ? items\n  : [{ json: { skip: 'true', source } }];"
      },
      "typeVersion": 2
    },
    {
      "id": "2ce5ff9f-764f-4c88-883a-2eca077784a4",
      "name": "05 | Filter \u2013 Skip Empty Feed Results",
      "type": "n8n-nodes-base.filter",
      "position": [
        1056,
        320
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 1,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "filter_skip_condition",
              "operator": {
                "type": "boolean",
                "operation": "notEquals"
              },
              "leftValue": "={{ $json.skip === true }}",
              "rightValue": true
            }
          ]
        }
      },
      "typeVersion": 2
    },
    {
      "id": "34cf8403-83c0-4185-8ef0-b977d154bcc5",
      "name": "06 | OpenAI \u2013 Analyze for Opportunity Signal",
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "position": [
        1280,
        320
      ],
      "parameters": {
        "modelId": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4o-mini",
          "cachedResultName": "GPT-4O-MINI"
        },
        "options": {},
        "messages": {
          "values": [
            {
              "content": "=Source: {{ $json.source }}\nTitle: {{ $json.title }}\nSummary: {{ $json.summary }}\nLink: {{ $json.link }}\nPublished: {{ $json.published }}"
            },
            {
              "content": "You are a B2B sales and marketing intelligence analyst. Analyze social/web posts and return ONLY valid JSON with no markdown and no preamble.\n\nDetermine if the post represents a business opportunity (someone looking for a tool, service, recommendation, or solution).\n\nReturn exactly this structure:\n{\n  \"is_opportunity\": true or false,\n  \"intent\": \"seeking_tool | seeking_service | complaint_about_competitor | general_discussion | news | irrelevant\",\n  \"relevance_score\": a number from 1 to 10,\n  \"pain_point\": \"brief description of problem they have, or null\",\n  \"opportunity_summary\": \"one sentence on why this is or is not an opportunity\",\n  \"suggested_action\": \"engage | monitor | ignore\"\n}"
            }
          ]
        }
      },
      "credentials": {},
      "typeVersion": 1
    },
    {
      "id": "301943dc-928c-4dc9-b29c-d9b61ef65f57",
      "name": "07 | Code \u2013 Parse AI + Merge Post Data",
      "type": "n8n-nodes-base.code",
      "position": [
        1632,
        320
      ],
      "parameters": {
        "jsCode": "// FIX: Changed from 'functionCode' to 'jsCode'\n// FIX: Node reference uses .first().json (not .item.json which is invalid)\n// FIX: OpenAI response in n8n-nodes-langchain is under $json.message.content\n\nconst raw = $json.message?.content ||\n            $json.choices?.[0]?.message?.content ||\n            '{}';\n\nlet ai;\ntry {\n  // Strip any accidental markdown code fences the model may add\n  const cleaned = raw.replace(/```json\\n?|```\\n?/g, '').trim();\n  ai = JSON.parse(cleaned);\n} catch (e) {\n  ai = {\n    is_opportunity: false,\n    intent: 'irrelevant',\n    relevance_score: 0,\n    pain_point: null,\n    opportunity_summary: 'JSON parse error: ' + e.message,\n    suggested_action: 'ignore'\n  };\n}\n\n// FIX: Use $('node name').first().json instead of .item.json\nconst prev = $('05 | Filter \u2013 Skip Empty Feed Results').first().json;\n\nreturn [{\n  json: {\n    source: prev.source,\n    title: prev.title,\n    link: prev.link,\n    summary: prev.summary,\n    published: prev.published,\n    is_opportunity: ai.is_opportunity ?? false,\n    intent: ai.intent ?? 'irrelevant',\n    relevance_score: Number(ai.relevance_score) || 0,\n    pain_point: ai.pain_point ?? null,\n    opportunity_summary: ai.opportunity_summary ?? '',\n    suggested_action: ai.suggested_action ?? 'ignore',\n    analyzed_at: new Date().toISOString()\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "ccaa4658-5075-426d-9ed0-bcf95bccf180",
      "name": "08 | Filter \u2013 High-Relevance Opportunities Only",
      "type": "n8n-nodes-base.filter",
      "position": [
        1920,
        320
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 1,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "loose"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "filter_score_condition",
              "operator": {
                "type": "number",
                "operation": "gte"
              },
              "leftValue": "={{ $json.relevance_score }}",
              "rightValue": 6
            },
            {
              "id": "filter_opportunity_condition",
              "operator": {
                "type": "boolean",
                "operation": "equals"
              },
              "leftValue": "={{ $json.is_opportunity }}",
              "rightValue": true
            }
          ]
        }
      },
      "typeVersion": 2
    },
    {
      "id": "9a7f90b0-a231-4309-9749-74af2d7f58d0",
      "name": "10 | Filter \u2013 Engage-Worthy Posts Only",
      "type": "n8n-nodes-base.filter",
      "position": [
        2080,
        320
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 1,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "loose"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "filter_engage_condition",
              "operator": {
                "type": "string",
                "operation": "equals"
              },
              "leftValue": "={{ $json.suggested_action }}",
              "rightValue": "engage"
            }
          ]
        }
      },
      "typeVersion": 2
    },
    {
      "id": "a8879c6d-962a-4e2d-96b5-e383395b4870",
      "name": "11 | Slack \u2013 Alert Growth Team",
      "type": "n8n-nodes-base.slack",
      "position": [
        2304,
        320
      ],
      "parameters": {
        "text": "=:dart: *New Opportunity Signal \u2013 Score {{ $json.relevance_score }}/10*\n\n*Source:* {{ $json.source }}\n*Intent:* {{ $json.intent }}\n*Pain Point:* {{ $json.pain_point }}\n\n*What is happening:*\n{{ $json.opportunity_summary }}\n\n*Post Title:* {{ $json.title }}\n*Link:* {{ $json.link }}\n*Published:* {{ $json.published }}\n\n*Suggested Action:* :point_right: {{ $json.suggested_action.toUpperCase() }}",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "list",
          "value": "C0AK1R8LT3M",
          "cachedResultName": "all-general-all-department"
        },
        "otherOptions": {},
        "authentication": "oAuth2"
      },
      "credentials": {
        "slackOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2
    },
    {
      "id": "f87f4483-a8fa-4989-adc7-304f0222c351",
      "name": "12 | Aggregate \u2013 Collect All Opportunities",
      "type": "n8n-nodes-base.aggregate",
      "position": [
        2080,
        512
      ],
      "parameters": {
        "options": {},
        "aggregate": "aggregateAllItemData",
        "destinationFieldName": "opportunities"
      },
      "typeVersion": 1
    },
    {
      "id": "688f1d0c-a70b-42d5-86f2-973be9fe9e1b",
      "name": "01 | Schedule \u2013 Daily at 9AM1",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        192,
        320
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "0 9 * * *"
            }
          ]
        }
      },
      "typeVersion": 1
    },
    {
      "id": "03318745-4171-40aa-8fec-58f43c6da493",
      "name": "03 | HTTP Request \u2013 Fetch RSS Feed1",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        608,
        320
      ],
      "parameters": {
        "url": "={{ $json['Competition URL'] }}",
        "options": {}
      },
      "typeVersion": 4
    },
    {
      "id": "fc2a09bd-4214-4436-96cf-5161bcabeffe",
      "name": "13 | Code \u2013 Build Daily Digest Summary1",
      "type": "n8n-nodes-base.code",
      "position": [
        2304,
        512
      ],
      "parameters": {
        "jsCode": "// FIX: Changed from 'functionCode' to 'jsCode'\n// FIX: This node now receives a SINGLE aggregated item from node 12 (Aggregate node)\n//      containing all opportunities in $json.opportunities array.\n//      Previously it received items one by one, which made per-source grouping impossible.\n\nconst all = $json.opportunities || [];\n\nif (all.length === 0) {\n  return [{ json: { digest: 'No opportunities found today.', total: 0, date: new Date().toISOString().split('T')[0] } }];\n}\n\nconst bySource = {};\nfor (const post of all) {\n  const s = post.source || 'unknown';\n  if (!bySource[s]) bySource[s] = [];\n  bySource[s].push(post);\n}\n\nconst dateStr = new Date().toISOString().split('T')[0];\nlet digest = `*Daily Social Listening Digest \u2013 ${dateStr}*\\n\\n`;\n\nfor (const [source, posts] of Object.entries(bySource)) {\n  digest += `*${source.toUpperCase()}* (${posts.length} opportunities)\\n`;\n  for (const p of posts.slice(0, 5)) {\n    digest += `  \u2022 [Score ${p.relevance_score}] ${p.title}\\n    ${p.link}\\n`;\n  }\n  digest += '\\n';\n}\n\nreturn [{ json: { digest, total: all.length, date: dateStr } }];"
      },
      "typeVersion": 2
    },
    {
      "id": "1baeb1f0-9761-4eb8-b78a-65ef19e68ce9",
      "name": "14 | Slack \u2013 Post Daily Digest1",
      "type": "n8n-nodes-base.slack",
      "position": [
        2528,
        528
      ],
      "parameters": {
        "text": "={{ $json.digest }}",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "list",
          "value": "C0AS2PE4Q6L",
          "cachedResultName": "inventory_summary"
        },
        "otherOptions": {},
        "authentication": "oAuth2"
      },
      "credentials": {
        "slackOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2
    },
    {
      "id": "832524b7-5fae-4554-bec9-3a9b29073668",
      "name": "Edit Fields",
      "type": "n8n-nodes-base.set",
      "position": [
        384,
        320
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "60bfca03-b71f-40f0-a334-3725593d7226",
              "name": "Competition URL",
              "type": "string",
              "value": "https://www.producthunt.com/feed"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "73bbbee1-18cf-4249-8911-acbf1d261590",
      "name": "Create a database page",
      "type": "n8n-nodes-base.notion",
      "position": [
        2160,
        112
      ],
      "parameters": {
        "options": {},
        "resource": "databasePage",
        "databaseId": {
          "__rl": true,
          "mode": "list",
          "value": "35a02106-fce7-8038-9e5e-d62d89b4fd0e",
          "cachedResultUrl": "https://www.notion.so/35a02106fce780389e5ed62d89b4fd0e",
          "cachedResultName": "Sales Data"
        },
        "propertiesUi": {
          "propertyValues": [
            {
              "key": "link|rich_text",
              "textContent": "={{ $json.link }}"
            },
            {
              "key": "opportunity Summary|rich_text",
              "textContent": "={{ $json.opportunity_summary }}"
            },
            {
              "key": "Relevance Score|number",
              "numberValue": "={{ $json.relevance_score }}"
            },
            {
              "key": "Source|title",
              "title": "={{ $json.source }}"
            }
          ]
        },
        "authentication": "oAuth2"
      },
      "credentials": {
        "notionOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "0a59a4cd-c32f-421e-992a-650e49454163",
      "name": "Main Overview",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -544,
        -128
      ],
      "parameters": {
        "color": 7,
        "width": 560,
        "height": 1224,
        "content": "## \ud83d\udce1 Social Listening & Opportunity Alert\n\nPulls the last 24 hours of posts from an RSS feed every morning, uses AI to score each item for sales or growth opportunity, then routes engage worthy posts to Slack, logs everything in Notion, and ships a Daily Digest summary to the team.\n\n**Perfect for:** Growth marketers, founders, and SDRs who want to spot buying signals and conversation starters across communities without scrolling feeds all day.\n\n***\n\n## How it works\n\n1. **01 | Schedule \u00b7 Daily at 9AM1** \u00b7 Cron trigger that fires the workflow every morning at 9 AM.\n2. **Edit Fields** \u00b7 Sets the RSS feed URL and any static config values used downstream.\n3. **03 | HTTP Request \u00b7 Fetch RSS Feed1** \u00b7 Downloads the raw RSS XML from the configured feed.\n4. **04 | Code \u00b7 Parse XML + Extract Last 24h Entries** \u00b7 Parses the XML and filters to entries published in the last 24 hours.\n5. **05 | Filter \u00b7 Skip Empty Feed Results** \u00b7 Stops the run if there are no fresh entries to process.\n6. **06 | OpenAI \u00b7 Analyze for Opportunity Signal** \u00b7 Scores each post for relevance, intent, and engagement potential, returning a structured JSON verdict.\n7. **07 | Code \u00b7 Parse AI + Merge Post Data** \u00b7 Merges the AI scores back onto the original post metadata.\n8. **08 | Filter \u00b7 High Relevance Opportunities Only** \u00b7 Drops everything below the relevance threshold. *(branches to three paths)*\n9. **10 | Filter \u00b7 Engage Worthy Posts Only** \u00b7 Narrows to posts where active engagement is the right move.\n10. **11 | Slack \u00b7 Alert Growth Team** \u00b7 Posts each engage worthy item to the Growth channel with a one click reply button.\n11. **Create a database page** \u00b7 Logs every high relevance opportunity to a Notion database as a row of record. *(parallel branch)*\n12. **12 | Aggregate \u00b7 Collect All Opportunities** \u00b7 Bundles every high relevance item from the run into a single list.\n13. **13 | Code \u00b7 Build Daily Digest Summary1** \u00b7 Formats the bundle into a readable digest message.\n14. **14 | Slack \u00b7 Post Daily Digest1** \u00b7 Posts the formatted digest to the team channel.\n\n***\n\n## Setup (~15 minutes)\n\n1. **RSS Feed URL** \u00b7 Set your target feed URL in the *Edit Fields* node. Any standard RSS or Atom feed works.\n2. **OpenAI API** \u00b7 Add your key in the *06 | OpenAI \u00b7 Analyze for Opportunity Signal* node. The model decides the relevance and engagement scores, so tune the prompt to match your ICP.\n3. **Slack OAuth** \u00b7 Authorize the workspace and pick the alerts channel in *11 | Slack \u00b7 Alert Growth Team* and the digest channel in *14 | Slack \u00b7 Post Daily Digest1*.\n4. **Notion** \u00b7 Connect your Notion integration in *Create a database page* and point it at the database you want to log opportunities into. Match property names to the AI output schema.\n5. **Schedule** \u00b7 Confirm the *01 | Schedule \u00b7 Daily at 9AM1* node fires at your team's local 9 AM and adjust the timezone if needed.\n\n> Tune the relevance threshold in *08 | Filter \u00b7 High Relevance Opportunities Only* to control alert volume. Run once manually with a real feed to check token cost before letting the daily schedule loose."
      },
      "typeVersion": 1
    },
    {
      "id": "47120908-7248-4ac8-a781-18ef9cf8e889",
      "name": "Section 1 Fetch",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        96,
        144
      ],
      "parameters": {
        "color": 5,
        "width": 1108,
        "height": 360,
        "content": "## 1\ufe0f\u20e3 Fetch & Validate \u00b7 Daily RSS Pull\n\nEvery morning **01 | Schedule \u00b7 Daily at 9AM1** triggers the run and **Edit Fields** sets the target feed URL. **03 | HTTP Request \u00b7 Fetch RSS Feed1** downloads the raw XML, **04 | Code \u00b7 Parse XML + Extract Last 24h Entries** parses it and keeps only entries from the last 24 hours, and **05 | Filter \u00b7 Skip Empty Feed Results** short circuits the workflow when there's nothing new to process."
      },
      "typeVersion": 1
    },
    {
      "id": "3fa77271-c7e7-4350-baf9-8888b70b2be8",
      "name": "Section 2 AI Analysis",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1232,
        128
      ],
      "parameters": {
        "color": 3,
        "width": 600,
        "height": 360,
        "content": "## 2\ufe0f\u20e3 AI Analysis \u00b7 Opportunity Scoring\n\nThe **06 | OpenAI \u00b7 Analyze for Opportunity Signal** node reads each fresh post and returns a structured JSON verdict scoring relevance, buying intent, and engagement potential against your ICP. The **07 | Code \u00b7 Parse AI + Merge Post Data** node merges those scores back onto the original post payload so downstream filters and outputs have everything they need in one item."
      },
      "typeVersion": 1
    },
    {
      "id": "fc2a607d-e2f6-428b-9bc9-b51176c52831",
      "name": "Section 3 Route and Output",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1856,
        -64
      ],
      "parameters": {
        "color": 6,
        "width": 1036,
        "height": 836,
        "content": "## 3\ufe0f\u20e3 Route & Output \u00b7 Multi Channel Distribution\n\nThe **08 | Filter \u00b7 High Relevance Opportunities Only** node drops low signal items and fans the survivors out to three parallel paths. **Create a database page** logs every relevant post to your Notion opportunities database. The **10 | Filter \u00b7 Engage Worthy Posts Only** node narrows further and **11 | Slack \u00b7 Alert Growth Team** posts each engage worthy item as a real time alert. In parallel, **12 | Aggregate \u00b7 Collect All Opportunities** bundles the full batch which **13 | Code \u00b7 Build Daily Digest Summary1** formats and **14 | Slack \u00b7 Post Daily Digest1** ships as a rolled up team digest."
      },
      "typeVersion": 1
    }
  ],
  "connections": {
    "Edit Fields": {
      "main": [
        [
          {
            "node": "03 | HTTP Request \u2013 Fetch RSS Feed1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "01 | Schedule \u2013 Daily at 9AM1": {
      "main": [
        [
          {
            "node": "Edit Fields",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "03 | HTTP Request \u2013 Fetch RSS Feed1": {
      "main": [
        [
          {
            "node": "04 | Code \u2013 Parse XML + Extract Last 24h Entries",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "05 | Filter \u2013 Skip Empty Feed Results": {
      "main": [
        [
          {
            "node": "06 | OpenAI \u2013 Analyze for Opportunity Signal",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "07 | Code \u2013 Parse AI + Merge Post Data": {
      "main": [
        [
          {
            "node": "08 | Filter \u2013 High-Relevance Opportunities Only",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "10 | Filter \u2013 Engage-Worthy Posts Only": {
      "main": [
        [
          {
            "node": "11 | Slack \u2013 Alert Growth Team",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "13 | Code \u2013 Build Daily Digest Summary1": {
      "main": [
        [
          {
            "node": "14 | Slack \u2013 Post Daily Digest1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "12 | Aggregate \u2013 Collect All Opportunities": {
      "main": [
        [
          {
            "node": "13 | Code \u2013 Build Daily Digest Summary1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "06 | OpenAI \u2013 Analyze for Opportunity Signal": {
      "main": [
        [
          {
            "node": "07 | Code \u2013 Parse AI + Merge Post Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "08 | Filter \u2013 High-Relevance Opportunities Only": {
      "main": [
        [
          {
            "node": "10 | Filter \u2013 Engage-Worthy Posts Only",
            "type": "main",
            "index": 0
          },
          {
            "node": "12 | Aggregate \u2013 Collect All Opportunities",
            "type": "main",
            "index": 0
          },
          {
            "node": "Create a database page",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "04 | Code \u2013 Parse XML + Extract Last 24h Entries": {
      "main": [
        [
          {
            "node": "05 | Filter \u2013 Skip Empty Feed Results",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}