{
  "id": "9qaAJYL2GGTvDyYu",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "Autonomous Multi-Supplier Price Comparison Agent (Agent Q)",
  "tags": [],
  "nodes": [
    {
      "id": "558bedee-9497-45e9-90af-b08d78f9c115",
      "name": "\ud83d\udccb Trigger \u2014 New Comparison Request",
      "type": "n8n-nodes-base.googleSheetsTrigger",
      "notes": "Triggers when a new purchase request row is added to Google Sheets",
      "position": [
        -1040,
        784
      ],
      "parameters": {
        "event": "rowAdded",
        "options": {},
        "pollTimes": {
          "item": [
            {
              "mode": "everyMinute"
            }
          ]
        },
        "sheetName": {
          "value": "Sheet1",
          "cachedResultName": "Sheet1"
        },
        "documentId": {
          "value": "YOUR_GOOGLE_SHEET_ID",
          "cachedResultName": "Purchase Requests Sheet"
        }
      },
      "credentials": {
        "googleSheetsTriggerOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "be9342e7-7d3e-4cae-a059-08c5ef9b64e9",
      "name": "\ud83d\uddfa\ufe0f Parse Request & Plan MCTS Navigation",
      "type": "n8n-nodes-base.code",
      "notes": "Validates input and runs MCTS pre-planning for each supplier site navigation path",
      "position": [
        -816,
        784
      ],
      "parameters": {
        "jsCode": "// Extract and validate purchase request data\nconst row = $input.first().json;\n\nconst productName = row['Product Name'] || row['product_name'] || row['Product'];\nconst quantityNeeded = row['Quantity'] || row['quantity_needed'] || row['Qty'];\nconst requestedBy = row['Requested By'] || row['requestor'] || 'Unknown';\nconst requestId = row['Request ID'] || `REQ-${Date.now()}`;\nconst requestDate = row['Date'] || new Date().toISOString();\nconst budget = row['Budget'] || row['budget'] || 'Not specified';\nconst notes = row['Notes'] || row['notes'] || '';\n\nif (!productName || !quantityNeeded) {\n  throw new Error('Missing required fields: Product Name or Quantity');\n}\n\n// MCTS: Pre-plan navigation strategy for 5 supplier websites\n// Monte Carlo Tree Search approach: plan before acting\nconst supplierNavigationPlan = [\n  {\n    supplierId: 'supplier_1',\n    name: 'Global Industrial',\n    baseUrl: 'https://www.globalindustrial.com',\n    searchUrl: `https://www.globalindustrial.com/c/search?q=${encodeURIComponent(productName)}`,\n    navigationPath: ['search_bar', 'first_result', 'product_page'],\n    priceSelector: '.price, .product-price, [class*=\"price\"]',\n    moqSelector: '.min-order, .minimum-order, [class*=\"moq\"]',\n    deliverySelector: '.delivery, .shipping-time, [class*=\"delivery\"]',\n    stockSelector: '.stock, .availability, [class*=\"stock\"]',\n    planConfidence: 0.85\n  },\n  {\n    supplierId: 'supplier_2',\n    name: 'Alibaba',\n    baseUrl: 'https://www.alibaba.com',\n    searchUrl: `https://www.alibaba.com/trade/search?SearchText=${encodeURIComponent(productName)}`,\n    navigationPath: ['search_results', 'filter_verified', 'product_page'],\n    priceSelector: '.price-range, .product-price',\n    moqSelector: '.min-order',\n    deliverySelector: '.delivery-time',\n    stockSelector: '.stock-info',\n    planConfidence: 0.80\n  },\n  {\n    supplierId: 'supplier_3',\n    name: 'Grainger',\n    baseUrl: 'https://www.grainger.com',\n    searchUrl: `https://www.grainger.com/search?searchQuery=${encodeURIComponent(productName)}`,\n    navigationPath: ['search_bar', 'product_listing', 'product_detail'],\n    priceSelector: '.price, .unit-price',\n    moqSelector: '.min-quantity, .pack-size',\n    deliverySelector: '.ship-date, .availability-message',\n    stockSelector: '.in-stock, .availability',\n    planConfidence: 0.88\n  },\n  {\n    supplierId: 'supplier_4',\n    name: 'Amazon Business',\n    baseUrl: 'https://www.amazon.com',\n    searchUrl: `https://www.amazon.com/s?k=${encodeURIComponent(productName)}&i=industrial`,\n    navigationPath: ['search_results', 'filter_business', 'product_page'],\n    priceSelector: '.a-price-whole, #priceblock_ourprice',\n    moqSelector: '.quantity-selector, .min-order-qty',\n    deliverySelector: '.delivery-message, #deliveryMessageMirId',\n    stockSelector: '.availability, #availability',\n    planConfidence: 0.90\n  },\n  {\n    supplierId: 'supplier_5',\n    name: 'McMaster-Carr',\n    baseUrl: 'https://www.mcmaster.com',\n    searchUrl: `https://www.mcmaster.com/search/${encodeURIComponent(productName)}`,\n    navigationPath: ['search_results', 'product_listing', 'price_table'],\n    priceSelector: '.price, .PriceDisplay',\n    moqSelector: '.min-qty, .PackageSize',\n    deliverySelector: '.availability, .DeliveryDate',\n    stockSelector: '.InStock, .StockStatus',\n    planConfidence: 0.87\n  }\n];\n\nreturn {\n  requestId,\n  productName,\n  quantityNeeded: parseInt(quantityNeeded),\n  requestedBy,\n  requestDate,\n  budget,\n  notes,\n  supplierNavigationPlan,\n  status: 'initialized',\n  timestamp: new Date().toISOString()\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "338de0e6-0f9b-4e72-a1e6-8dee85136dde",
      "name": "\ud83e\udd16 Agent Q \u2014 Supplier Research Orchestrator",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "notes": "Agent Q with MCTS planning - orchestrates all supplier research",
      "position": [
        -592,
        784
      ],
      "parameters": {
        "options": {
          "systemMessage": "You are Agent Q \u2014 an autonomous procurement intelligence agent. You operate with two cognitive modes: MCTS (Monte Carlo Tree Search) for pre-planned navigation, and Self-Critique for data validation. You never hallucinate prices. If you cannot retrieve real data, you say so explicitly.\n\n\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\nIDENTITY & CONSTRAINTS\n\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n- You are a research-only agent. You do not place orders or contact suppliers.\n- You always return a single, valid JSON object \u2014 no preamble, no markdown fences, no explanation outside the JSON.\n- If a supplier page is unreachable or returns no usable data, set navigationSuccess: false and unitPrice: null for that supplier. Never fabricate a price.\n- All prices must be in USD. If a price is in another currency, convert using approximate rates and note it in agentNotes.\n\n\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\nPHASE 1 \u2014 MCTS NAVIGATION PLANNING\n\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\nBefore opening any supplier URL, reason through the navigation tree for each supplier:\n1. Primary path: what URL to hit, what element to find, what selector to try\n2. Fallback path: if primary fails (404, CAPTCHA, no product found), what is the next best action?\n3. Confidence score: estimate 0.0\u20131.0 how likely this plan succeeds before executing\n4. If confidence < 0.65 for a supplier, note it in agentNotes and attempt anyway\n\nPlan all 5 suppliers before visiting any of them.\n\n\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\nPHASE 2 \u2014 SUPPLIER VISITS\n\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\nFor each of the 5 suppliers, execute in order:\n\nStep 1: Fetch the search results page using the provided searchUrl\nStep 2: Identify the product listing that best matches the product name and quantity context\nStep 3: Fetch that product's detail page\nStep 4: Extract:\n  - unitPrice (number, USD, price per single unit)\n  - priceAtQuantity (number, USD, total at the requested quantity \u2014 check for bulk discounts)\n  - moq (number, minimum order quantity)\n  - deliveryDays (number, estimated business days to ship)\n  - stockStatus: one of \"in_stock\" | \"low_stock\" | \"out_of_stock\" | \"unknown\"\n  - productUrl (the exact URL of the product page you extracted from)\n  - rawPriceText (the raw text you found before parsing, e.g. \"$1.24 / each\")\n  - dataConfidence (0.0\u20131.0, how certain you are this is the correct product and price)\n\nIf the product has tiered pricing, use the tier that applies to the requested quantity.\n\n\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\nPHASE 3 \u2014 SELF-CRITIQUE VALIDATION\n\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\nAfter collecting all supplier data, run the following checks:\n\n1. OUTLIER CHECK\n   - Calculate mean price from all suppliers with valid prices\n   - Calculate standard deviation\n   - Flag any price where |price - mean| > 1.5 \u00d7 stdDev as anomalous\n   - For flagged prices: set anomalyFlag: true and describe anomalyReason\n   - Do NOT exclude anomalous prices from the output \u2014 flag them and let the human decide\n\n2. SANITY CHECKS\n   - MOQ must be a positive integer. If extracted as a range (e.g. \"50\u2013100\"), use the lower bound.\n   - Delivery days must be a positive integer. If expressed as weeks, multiply by 7. If a range, use the midpoint.\n   - Unit price must be > 0. If a page shows \"$0.00\" or \"call for price\", set unitPrice: null and navigationSuccess: false.\n\n3. DATA QUALITY SCORE (0\u2013100)\n   - Start at 100\n   - Subtract 15 for each supplier with navigationSuccess: false\n   - Subtract 10 for each anomalyFlag: true\n   - Subtract 5 for each dataConfidence < 0.7\n   - Minimum score: 0\n\n4. CRITIQUE SUMMARY\n   Write 1\u20132 sentences summarizing data quality, any anomalies, and whether human review is recommended.\n\n\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\nPHASE 4 \u2014 RANKING\n\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\nRank suppliers using only suppliers where navigationSuccess: true and unitPrice is not null.\n\nBEST PRICE: Supplier with lowest unitPrice at the requested quantity\nBEST DELIVERY: Supplier with lowest deliveryDays and stockStatus !== \"out_of_stock\"\nBEST OVERALL: Weighted composite score per supplier:\n  score = (1 / unitPrice \u00d7 0.60) + (1 / deliveryDays \u00d7 0.25) + (dataConfidence \u00d7 0.15)\n  Normalize each component to 0\u20131 range before weighting.\n  bestOverallScore = the winning supplier's normalized composite (0\u2013100).\n\nIf fewer than 3 suppliers have valid data, note this in agentNotes and rank from available data only.\n\n\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\nOUTPUT FORMAT (return ONLY this JSON)\n\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n{\n  \"requestId\": \"string\",\n  \"productName\": \"string\",\n  \"quantityNeeded\": number,\n  \"researchTimestamp\": \"ISO 8601 string\",\n  \"suppliers\": [\n    {\n      \"supplierId\": \"string\",\n      \"supplierName\": \"string\",\n      \"productUrl\": \"string or null\",\n      \"unitPrice\": number or null,\n      \"currency\": \"USD\",\n      \"priceAtQuantity\": number or null,\n      \"moq\": number or null,\n      \"deliveryDays\": number or null,\n      \"stockStatus\": \"in_stock | low_stock | out_of_stock | unknown\",\n      \"anomalyFlag\": boolean,\n      \"anomalyReason\": \"string or null\",\n      \"dataConfidence\": number,\n      \"rawPriceText\": \"string or null\",\n      \"navigationSuccess\": boolean\n    }\n  ],\n  \"rankings\": {\n    \"bestPrice\": \"supplierName or null\",\n    \"bestDelivery\": \"supplierName or null\",\n    \"bestOverall\": \"supplierName or null\",\n    \"bestOverallScore\": number\n  },\n  \"selfCritiqueReport\": {\n    \"meanPrice\": number or null,\n    \"priceStdDev\": number or null,\n    \"anomaliesDetected\": number,\n    \"dataQualityScore\": number,\n    \"critiqueSummary\": \"string\"\n  },\n  \"agentNotes\": \"string\"\n}"
        }
      },
      "typeVersion": 1.7
    },
    {
      "id": "1df03776-c083-48ff-808a-279f82e46eaa",
      "name": "\ud83c\udf10 HTTP \u2014 Fetch Supplier Page",
      "type": "n8n-nodes-base.httpRequest",
      "notes": "Tool: Fetches supplier web pages for Agent Q",
      "position": [
        -16,
        784
      ],
      "parameters": {
        "url": "={{ $fromAI('url', 'The URL to fetch for supplier research') }}",
        "options": {
          "timeout": 15000,
          "response": {
            "response": {
              "responseFormat": "text"
            }
          }
        },
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "User-Agent",
              "value": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
            },
            {
              "name": "Accept",
              "value": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "a32be05b-177d-429d-95e8-4952cb155576",
      "name": "\ud83d\udcca Parse & Score Agent Q Research Output",
      "type": "n8n-nodes-base.code",
      "notes": "Parses and validates Agent Q's JSON research results",
      "position": [
        -240,
        784
      ],
      "parameters": {
        "jsCode": "// Parse Agent Q's JSON response from the LLM\nconst agentOutput = $input.first().json.output || $input.first().json.text || $input.first().json;\n\nlet researchData;\ntry {\n  // Handle string or object output\n  if (typeof agentOutput === 'string') {\n    // Strip markdown code fences if present\n    const cleaned = agentOutput.replace(/```json\\n?/g, '').replace(/```\\n?/g, '').trim();\n    researchData = JSON.parse(cleaned);\n  } else {\n    researchData = agentOutput;\n  }\n} catch (e) {\n  // Fallback: create a structured error response\n  researchData = {\n    requestId: $('Parse Request + MCTS Navigation Planning').first().json.requestId,\n    productName: $('Parse Request + MCTS Navigation Planning').first().json.productName,\n    quantityNeeded: $('Parse Request + MCTS Navigation Planning').first().json.quantityNeeded,\n    researchTimestamp: new Date().toISOString(),\n    error: 'Failed to parse agent output',\n    rawOutput: agentOutput.toString().substring(0, 500),\n    suppliers: [],\n    rankings: { bestPrice: 'N/A', bestDelivery: 'N/A', bestOverall: 'N/A', bestOverallScore: 0 },\n    selfCritiqueReport: { dataQualityScore: 0, critiqueSummary: 'Parse error occurred' },\n    agentNotes: 'Error in parsing'\n  };\n}\n\n// Enrich with original request metadata\nconst originalRequest = $('Parse Request + MCTS Navigation Planning').first().json;\nresearchData.requestedBy = originalRequest.requestedBy;\nresearchData.requestDate = originalRequest.requestDate;\nresearchData.budget = originalRequest.budget;\nresearchData.processingComplete = true;\n\n// Generate Notion-ready formatted blocks\nresearchData.notionFormatted = {\n  title: `Price Comparison: ${researchData.productName} \u2014 ${researchData.requestId}`,\n  subtitle: `Requested by ${researchData.requestedBy} \u2022 ${new Date(researchData.requestDate).toLocaleDateString()} \u2022 Qty: ${researchData.quantityNeeded}`,\n  qualityBadge: researchData.selfCritiqueReport?.dataQualityScore >= 80 ? '\u2705 High Quality Data' : researchData.selfCritiqueReport?.dataQualityScore >= 60 ? '\u26a0\ufe0f Medium Quality' : '\ud83d\udd34 Low Quality \u2014 Review Manually'\n};\n\nreturn researchData;"
      },
      "typeVersion": 2
    },
    {
      "id": "3b5bc79f-b3da-4071-a957-8a39139301c5",
      "name": "\u270d\ufe0f Generate Negotiation Message",
      "type": "n8n-nodes-base.code",
      "notes": "Auto-drafts opening negotiation message for top-ranked supplier",
      "position": [
        208,
        784
      ],
      "parameters": {
        "jsCode": "// Generate the negotiation message for the top-ranked supplier\nconst data = $input.first().json;\n\nconst bestOverallName = data.rankings?.bestOverall || 'N/A';\nconst bestSupplier = data.suppliers?.find(s => s.supplierName === bestOverallName);\n\nif (!bestSupplier) {\n  return {\n    ...data,\n    negotiationMessage: {\n      subject: `Price Inquiry \u2014 ${data.productName}`,\n      body: 'Unable to generate negotiation message \u2014 supplier data unavailable.',\n      generated: false\n    }\n  };\n}\n\n// Calculate target price (10% below best price)\nconst targetPrice = (bestSupplier.unitPrice * 0.90).toFixed(2);\nconst totalOrderValue = (bestSupplier.unitPrice * data.quantityNeeded).toFixed(2);\nconst targetTotal = (targetPrice * data.quantityNeeded).toFixed(2);\n\n// Determine competitor leverage\nconst competitors = data.suppliers\n  .filter(s => s.supplierName !== bestOverallName && s.navigationSuccess && !s.anomalyFlag)\n  .sort((a, b) => a.unitPrice - b.unitPrice)\n  .slice(0, 2)\n  .map(s => `${s.supplierName} at $${s.unitPrice?.toFixed(2)}/unit`)\n  .join(' and ');\n\nconst negotiationMessage = {\n  subject: `Bulk Order Inquiry \u2014 ${data.productName} (${data.quantityNeeded} units)`,\n  body: `Dear ${bestOverallName} Sales Team,\n\nI hope this message finds you well. We are evaluating suppliers for a purchase of ${data.quantityNeeded} units of ${data.productName} (Request ID: ${data.requestId}).\n\nYour current listed price of $${bestSupplier.unitPrice?.toFixed(2)}/unit represents a total order value of $${totalOrderValue}. We have also received comparable quotes from ${competitors || 'other suppliers'}, and are positioned to move forward quickly with the right partner.\n\nGiven our order volume and commitment to a prompt purchase decision, we would like to request your best pricing \u2014 ideally at or near $${targetPrice}/unit (approx. $${targetTotal} total).\n\nCould you also confirm:\n1. Delivery timeline to [YOUR LOCATION] for this quantity\n2. Whether bulk pricing tiers apply at this volume\n3. Payment terms available\n\nWe are ready to issue a purchase order within 48 hours for the right offer. Please respond at your earliest convenience.\n\nBest regards,\n${data.requestedBy}\nProcurement Team`,\n  targetUnitPrice: parseFloat(targetPrice),\n  potentialSaving: parseFloat((bestSupplier.unitPrice - targetPrice) * data.quantityNeeded).toFixed(2),\n  generated: true,\n  supplierContact: bestSupplier.supplierName\n};\n\nreturn {\n  ...data,\n  negotiationMessage\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "e26c096d-d67a-4329-b4a8-ceec5829914d",
      "name": "\ud83d\udcd2 Create Notion Comparison Page",
      "type": "n8n-nodes-base.notion",
      "notes": "Builds ranked comparison table in Notion with all supplier data and negotiation message",
      "position": [
        432,
        784
      ],
      "parameters": {
        "title": "=Price Comparison: {{ $json.productName }} \u2014 {{ $json.requestId }}",
        "pageId": {
          "__rl": true,
          "mode": "url",
          "value": "https://www.notion.so/Test-n8n-automation-350f839f118480fb9bddfb4278f2ae4e"
        },
        "blockUi": {
          "blockValues": [
            {
              "type": "heading_2",
              "textContent": "=\ud83d\udcca Supplier Price Comparison \u2014 {{ $json.productName }}"
            },
            {
              "textContent": "={{ $json.notionFormatted.subtitle }}"
            },
            {
              "textContent": "={{ $json.notionFormatted.qualityBadge }}"
            },
            {
              "type": "heading_3",
              "textContent": "\ud83c\udfc6 Rankings Summary"
            },
            {
              "textContent": "=\ud83e\udd47 Best Price: {{ $json.rankings.bestPrice }}\n\ud83d\ude9a Best Delivery: {{ $json.rankings.bestDelivery }}\n\u2b50 Best Overall: {{ $json.rankings.bestOverall }} (Score: {{ $json.rankings.bestOverallScore }})"
            },
            {
              "type": "heading_3",
              "textContent": "\ud83d\udccb Detailed Supplier Data"
            },
            {
              "textContent": "={{ $json.suppliers.map(s => `**${s.supplierName}**\\nUnit Price: $${s.unitPrice?.toFixed(2)} | MOQ: ${s.moq} | Delivery: ${s.deliveryDays} days | Stock: ${s.stockStatus} | Confidence: ${Math.round(s.dataConfidence * 100)}%${s.anomalyFlag ? ' \u26a0\ufe0f ANOMALY: ' + s.anomalyReason : ''}`).join('\\n\\n') }}"
            },
            {
              "type": "heading_3",
              "textContent": "\ud83d\udd0d Self-Critique Report"
            },
            {
              "textContent": "={{ `Mean Price: $${$json.selfCritiqueReport.meanPrice?.toFixed(2)}\\nStd Deviation: $${$json.selfCritiqueReport.priceStdDev?.toFixed(2)}\\nAnomalies Detected: ${$json.selfCritiqueReport.anomaliesDetected}\\nData Quality: ${$json.selfCritiqueReport.dataQualityScore}/100\\n\\n${$json.selfCritiqueReport.critiqueSummary}` }}"
            },
            {
              "type": "heading_3",
              "textContent": "\u2709\ufe0f Draft Negotiation Message"
            },
            {
              "textContent": "=**Subject:** {{ $json.negotiationMessage.subject }}"
            },
            {
              "textContent": "={{ $json.negotiationMessage.body }}"
            },
            {
              "textContent": "=\ud83d\udcb0 Potential Saving if Target Achieved: ${{ $json.negotiationMessage.potentialSaving }}"
            },
            {
              "type": "heading_3",
              "textContent": "\ud83e\udd16 Agent Notes"
            },
            {
              "textContent": "={{ $json.agentNotes }}"
            }
          ]
        },
        "options": {}
      },
      "credentials": {
        "notionApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "721d288c-d05f-46f3-baed-c572068f390e",
      "name": "\ud83d\udcdd Format Slack Summary",
      "type": "n8n-nodes-base.code",
      "notes": "Formats rich Slack message with comparison summary and Notion link",
      "position": [
        656,
        688
      ],
      "parameters": {
        "jsCode": "// Format the Slack message for procurement manager\nconst data = $input.first().json;\nconst notionPageId = $('Create Notion Comparison Page').first().json.id;\nconst notionUrl = `https://notion.so/${notionPageId?.replace(/-/g, '')}`;\n\nconst bestSupplier = data.suppliers?.find(s => s.supplierName === data.rankings?.bestOverall);\nconst bestPriceSupplier = data.suppliers?.find(s => s.supplierName === data.rankings?.bestPrice);\nconst bestDeliverySupplier = data.suppliers?.find(s => s.supplierName === data.rankings?.bestDelivery);\n\nconst supplierRows = data.suppliers\n  ?.filter(s => s.navigationSuccess)\n  .sort((a, b) => a.unitPrice - b.unitPrice)\n  .map((s, i) => {\n    const medal = i === 0 ? '\ud83e\udd47' : i === 1 ? '\ud83e\udd48' : i === 2 ? '\ud83e\udd49' : '  ';\n    const flag = s.anomalyFlag ? ' \u26a0\ufe0f' : '';\n    return `${medal} *${s.supplierName}*${flag} \u2014 $${s.unitPrice?.toFixed(2)}/unit | MOQ: ${s.moq} | Delivery: ${s.deliveryDays}d`;\n  })\n  .join('\\n') || 'No supplier data available';\n\nconst qualityEmoji = data.selfCritiqueReport?.dataQualityScore >= 80 ? '\u2705' : data.selfCritiqueReport?.dataQualityScore >= 60 ? '\u26a0\ufe0f' : '\ud83d\udd34';\n\nconst slackMessage = {\n  blocks: [\n    {\n      type: 'header',\n      text: {\n        type: 'plain_text',\n        text: `\ud83e\udd16 Agent Q \u2014 Price Comparison Complete`,\n        emoji: true\n      }\n    },\n    {\n      type: 'section',\n      fields: [\n        { type: 'mrkdwn', text: `*Product:*\\n${data.productName}` },\n        { type: 'mrkdwn', text: `*Request ID:*\\n${data.requestId}` },\n        { type: 'mrkdwn', text: `*Quantity:*\\n${data.quantityNeeded} units` },\n        { type: 'mrkdwn', text: `*Requested By:*\\n${data.requestedBy}` }\n      ]\n    },\n    { type: 'divider' },\n    {\n      type: 'section',\n      text: {\n        type: 'mrkdwn',\n        text: `*\ud83d\udcca Price Rankings (lowest \u2192 highest):*\\n${supplierRows}`\n      }\n    },\n    { type: 'divider' },\n    {\n      type: 'section',\n      fields: [\n        { type: 'mrkdwn', text: `*\ud83c\udfc6 Best Overall:*\\n${data.rankings?.bestOverall}` },\n        { type: 'mrkdwn', text: `*\ud83d\udcb0 Best Price:*\\n${data.rankings?.bestPrice} @ $${bestPriceSupplier?.unitPrice?.toFixed(2)}/unit` },\n        { type: 'mrkdwn', text: `*\ud83d\ude9a Best Delivery:*\\n${data.rankings?.bestDelivery} (${bestDeliverySupplier?.deliveryDays} days)` },\n        { type: 'mrkdwn', text: `*${qualityEmoji} Data Quality:*\\n${data.selfCritiqueReport?.dataQualityScore}/100` }\n      ]\n    },\n    {\n      type: 'section',\n      text: {\n        type: 'mrkdwn',\n        text: `*\u2709\ufe0f Negotiation Message:* Draft ready for *${data.negotiationMessage?.supplierContact}* (target: $${data.negotiationMessage?.targetUnitPrice}/unit \u2014 potential saving: *$${data.negotiationMessage?.potentialSaving}*)`\n      }\n    },\n    {\n      type: 'actions',\n      elements: [\n        {\n          type: 'button',\n          text: { type: 'plain_text', text: '\ud83d\udccb View Full Comparison in Notion', emoji: true },\n          url: notionUrl,\n          style: 'primary'\n        }\n      ]\n    },\n    {\n      type: 'context',\n      elements: [\n        {\n          type: 'mrkdwn',\n          text: `\u26a1 Research completed by Agent Q in under 3 minutes | ${data.selfCritiqueReport?.anomaliesDetected || 0} price anomalies detected & flagged | ${new Date().toLocaleTimeString()}`\n        }\n      ]\n    }\n  ]\n};\n\nreturn { slackPayload: slackMessage, ...data };"
      },
      "typeVersion": 2
    },
    {
      "id": "7a9e9121-1e97-44d8-a234-8843fd33c6bd",
      "name": "\ud83d\udd14 Slack \u2014 Send Comparison Summary",
      "type": "n8n-nodes-base.slack",
      "notes": "Sends comparison summary to procurement manager on Slack",
      "position": [
        880,
        688
      ],
      "parameters": {
        "select": "channel",
        "blocksUi": "={{ JSON.stringify($json.slackPayload.blocks) }}",
        "channelId": {
          "value": "YOUR_SLACK_CHANNEL_ID",
          "cachedResultName": "#procurement-team"
        },
        "messageType": "block",
        "otherOptions": {}
      },
      "credentials": {
        "slackApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "1ffb652c-4d28-45a8-8de1-ed9ddcaf2f92",
      "name": "\u2705 Update Google Sheet Status",
      "type": "n8n-nodes-base.googleSheets",
      "notes": "Updates the original Google Sheet row with completion status and results",
      "position": [
        1104,
        688
      ],
      "parameters": {
        "columns": {
          "value": {},
          "schema": [],
          "mappingMode": "autoMapInputData",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "update",
        "sheetName": {
          "value": "Sheet1",
          "cachedResultName": "Sheet1"
        },
        "documentId": {
          "value": "YOUR_GOOGLE_SHEET_ID",
          "cachedResultName": "Purchase Requests Sheet"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.4
    },
    {
      "id": "89a155c4-6930-415a-b14b-64282faa050b",
      "name": "\u26a0\ufe0f Anomalies Detected?",
      "type": "n8n-nodes-base.if",
      "notes": "Routes to anomaly alert if self-critique flagged suspicious prices",
      "position": [
        656,
        880
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "check-anomaly",
              "operator": {
                "type": "number",
                "operation": "gt"
              },
              "leftValue": "={{ $json.selfCritiqueReport.anomaliesDetected }}",
              "rightValue": 0
            }
          ]
        }
      },
      "typeVersion": 2.1
    },
    {
      "id": "292663d4-c28d-40f7-8201-99b01f8d4a5c",
      "name": "\ud83d\udea8 Slack \u2014 Send Anomaly Alert",
      "type": "n8n-nodes-base.slack",
      "notes": "Sends anomaly alert when self-critique flags suspicious prices",
      "position": [
        880,
        880
      ],
      "parameters": {
        "select": "channel",
        "blocksUi": "=[\n  {\n    \"type\": \"header\",\n    \"text\": { \"type\": \"plain_text\", \"text\": \"\u26a0\ufe0f Agent Q \u2014 Price Anomaly Alert\", \"emoji\": true }\n  },\n  {\n    \"type\": \"section\",\n    \"text\": {\n      \"type\": \"mrkdwn\",\n      \"text\": \"*{{ $json.selfCritiqueReport.anomaliesDetected }} anomalous price(s) detected* for *{{ $json.productName }}* ({{ $json.requestId }}).\\n\\nSelf-Critique Summary: {{ $json.selfCritiqueReport.critiqueSummary }}\\n\\n*Flagged suppliers:*\\n{{ $json.suppliers.filter(s => s.anomalyFlag).map(s => `\u2022 ${s.supplierName}: $${s.unitPrice?.toFixed(2)} \u2014 ${s.anomalyReason}`).join('\\n') }}\"\n    }\n  },\n  {\n    \"type\": \"section\",\n    \"text\": { \"type\": \"mrkdwn\", \"text\": \"Please manually verify flagged prices before approving the comparison.\" }\n  }\n]",
        "channelId": {
          "value": "YOUR_SLACK_CHANNEL_ID",
          "cachedResultName": "#procurement-alerts"
        },
        "messageType": "block",
        "otherOptions": {}
      },
      "credentials": {
        "slackApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "89b166a8-6518-43ff-917a-c14b5d22630f",
      "name": "\u26a1 LLM \u2014 Agent Q Brain (GPT-4o)",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "position": [
        -520,
        1008
      ],
      "parameters": {
        "model": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-5-mini"
        },
        "options": {},
        "builtInTools": {}
      },
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "69811092-66be-4b6e-a7d6-5d63e646d800",
      "name": "Sticky Note \u2014 Phase 1: Trigger & Planning",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1232,
        592
      ],
      "parameters": {
        "color": 3,
        "width": 500,
        "height": 175,
        "content": "## \ud83d\udce5 Phase 1: Trigger & Request Planning\nFired when a new product comparison request is added to Google Sheets.\nParses supplier URLs, product details, and uses **MCTS navigation planning** to map out the research strategy before handing off to Agent Q."
      },
      "typeVersion": 1
    },
    {
      "id": "cf4d49fe-dcaf-4587-b5e4-5d69c1eddfed",
      "name": "Sticky Note \u2014 Phase 2: Agent Q Research",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -592,
        560
      ],
      "parameters": {
        "color": 5,
        "width": 600,
        "height": 206,
        "content": "## \ud83e\udd16 Phase 2: Agent Q \u2014 Autonomous Supplier Research\n**Agent Q** orchestrates intelligent supplier research:\n- Uses GPT-4o as its reasoning brain\n- Fetches live supplier pages via HTTP\n- Parses & scores pricing, lead times, MOQ, and terms\n- Outputs a structured multi-supplier comparison dataset"
      },
      "typeVersion": 1
    },
    {
      "id": "bfa9ad15-6f3e-434d-8ce6-8cb242d750b0",
      "name": "Sticky Note \u2014 Phase 3: Negotiation Message Generation",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        112,
        592
      ],
      "parameters": {
        "color": 6,
        "width": 430,
        "height": 155,
        "content": "## \u270d\ufe0f Phase 3: Auto-Generate Negotiation Message\nUsing the best supplier identified from the comparison,\nan AI-crafted negotiation message is generated with targeted pricing asks and leverage points."
      },
      "typeVersion": 1
    },
    {
      "id": "c3134c14-0ebc-4e50-9975-bca99f5945da",
      "name": "Sticky Note \u2014 Phase 4: Logging & Notifications",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        736,
        480
      ],
      "parameters": {
        "color": 7,
        "width": 500,
        "content": "## \ud83d\udcca Phase 4: Logging & Team Notifications\n- Creates a full **Notion comparison page** with supplier breakdown\n- Formats and sends a **Slack summary** to the procurement team\n- Updates **Google Sheets** row with final status & best supplier"
      },
      "typeVersion": 1
    },
    {
      "id": "9de47f40-a819-4cc1-8f3d-4422b48a468e",
      "name": "Sticky Note \u2014 Phase 5: Anomaly Detection",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        832,
        1072
      ],
      "parameters": {
        "color": 2,
        "width": 470,
        "height": 165,
        "content": "## \u26a0\ufe0f Phase 5: Anomaly Detection\nChecks for price spikes, unusually long lead times, or MOQ outliers across suppliers.\n- \u2705 **No anomalies** \u2192 Flow ends normally\n- \ud83d\udea8 **Anomaly found** \u2192 Sends an urgent **Slack alert** to flag the issue for manual review"
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "binaryMode": "separate",
    "executionOrder": "v1"
  },
  "versionId": "7c4211f4-c384-46ec-a0af-3861babca5a1",
  "connections": {
    "\ud83d\udcdd Format Slack Summary": {
      "main": [
        [
          {
            "node": "\ud83d\udd14 Slack \u2014 Send Comparison Summary",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\u26a0\ufe0f Anomalies Detected?": {
      "main": [
        [
          {
            "node": "\ud83d\udea8 Slack \u2014 Send Anomaly Alert",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\ud83c\udf10 HTTP \u2014 Fetch Supplier Page": {
      "main": [
        [
          {
            "node": "\u270d\ufe0f Generate Negotiation Message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\u26a1 LLM \u2014 Agent Q Brain (GPT-4o)": {
      "ai_languageModel": [
        [
          {
            "node": "\ud83e\udd16 Agent Q \u2014 Supplier Research Orchestrator",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "\ud83d\udcd2 Create Notion Comparison Page": {
      "main": [
        [
          {
            "node": "\ud83d\udcdd Format Slack Summary",
            "type": "main",
            "index": 0
          },
          {
            "node": "\u26a0\ufe0f Anomalies Detected?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\u270d\ufe0f Generate Negotiation Message": {
      "main": [
        [
          {
            "node": "\ud83d\udcd2 Create Notion Comparison Page",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\ud83d\udd14 Slack \u2014 Send Comparison Summary": {
      "main": [
        [
          {
            "node": "\u2705 Update Google Sheet Status",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\ud83d\udccb Trigger \u2014 New Comparison Request": {
      "main": [
        [
          {
            "node": "\ud83d\uddfa\ufe0f Parse Request & Plan MCTS Navigation",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\ud83d\udcca Parse & Score Agent Q Research Output": {
      "main": [
        [
          {
            "node": "\ud83c\udf10 HTTP \u2014 Fetch Supplier Page",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\ud83d\uddfa\ufe0f Parse Request & Plan MCTS Navigation": {
      "main": [
        [
          {
            "node": "\ud83e\udd16 Agent Q \u2014 Supplier Research Orchestrator",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\ud83e\udd16 Agent Q \u2014 Supplier Research Orchestrator": {
      "main": [
        [
          {
            "node": "\ud83d\udcca Parse & Score Agent Q Research Output",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}