{
  "updatedAt": "2025-11-23T18:52:50.000Z",
  "createdAt": "2025-11-21T18:29:25.700Z",
  "id": "FsE5BMhYYzTYRTwM",
  "name": "CMA Automation",
  "active": false,
  "isArchived": false,
  "nodes": [
    {
      "parameters": {
        "pollTimes": {
          "item": [
            {
              "mode": "everyMinute"
            }
          ]
        },
        "triggerOn": "specificFolder",
        "folderToWatch": {
          "__rl": true,
          "value": "1CNXRwP4cKQTRwfA4ytZsb6TenJR_mY0K",
          "mode": "list",
          "cachedResultName": "Quick CMA",
          "cachedResultUrl": "https://drive.google.com/drive/folders/1CNXRwP4cKQTRwfA4ytZsb6TenJR_mY0K"
        },
        "event": "fileCreated",
        "options": {}
      },
      "type": "n8n-nodes-base.googleDriveTrigger",
      "typeVersion": 1,
      "position": [
        464,
        504
      ],
      "id": "25630b52-ebb1-4935-a3f8-578b63ebe423",
      "name": "Google Drive Trigger",
      "credentials": {
        "googleDriveOAuth2Api": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "operation": "pdf",
        "options": {}
      },
      "type": "n8n-nodes-base.extractFromFile",
      "typeVersion": 1,
      "position": [
        912,
        504
      ],
      "id": "efe97e3b-b059-4c23-bf3d-11bd10c81eb1",
      "name": "Extract from File"
    },
    {
      "parameters": {
        "operation": "download",
        "fileId": {
          "__rl": true,
          "value": "={{ $json.id }}",
          "mode": "id"
        },
        "options": {}
      },
      "type": "n8n-nodes-base.googleDrive",
      "typeVersion": 3,
      "position": [
        688,
        504
      ],
      "id": "6080b227-93f6-4feb-9aed-c1737c49a51f",
      "name": "Download file",
      "credentials": {
        "googleDriveOAuth2Api": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "text": "={{ $json.text }}",
        "attributes": {
          "attributes": [
            {
              "name": "Address",
              "description": "Address of the comparable property",
              "required": true
            },
            {
              "name": "H SqFt",
              "description": "Square feet of the comparable property.",
              "required": true
            },
            {
              "name": "BR",
              "description": "How many bedrooms the comparable property has.",
              "required": true
            },
            {
              "name": "FB",
              "description": "How many full bathrooms the comparable property has",
              "required": true
            },
            {
              "name": "HB",
              "description": "How many half bathrooms the comparable property has.",
              "required": true
            },
            {
              "name": "YrBlt",
              "description": "The year the property was built",
              "required": true
            },
            {
              "name": "Pool",
              "description": "If it has a pool, the type of pool",
              "required": true
            },
            {
              "name": "List Price",
              "description": "The price the property was listed for",
              "required": true
            },
            {
              "name": "CDOM",
              "description": "The amount of days on the market the property was listed.",
              "required": true
            },
            {
              "name": "Sale Price",
              "description": "The price of the property was sold at.",
              "required": true
            },
            {
              "name": "Sale Date",
              "description": "The date the property was sold.",
              "required": true
            }
          ]
        },
        "options": {}
      },
      "type": "@n8n/n8n-nodes-langchain.informationExtractor",
      "typeVersion": 1.2,
      "position": [
        464,
        936
      ],
      "id": "0d96f76d-eb14-4211-90fc-e0b727d0a025",
      "name": "Information Extractor"
    },
    {
      "parameters": {
        "content": "### Sale $ / foot\n**Means sale $ / foot\nSale Price / H SqFt = P/H SqFt\n\n### List $ /foot\n** Means \n(listing price / H sqft)",
        "height": 192,
        "width": 272
      },
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1088,
        48
      ],
      "typeVersion": 1,
      "id": "2eb847a4-5939-4d3b-8006-fea4e81aaec4",
      "name": "Sticky Note"
    },
    {
      "parameters": {
        "options": {}
      },
      "type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
      "typeVersion": 1,
      "position": [
        536,
        1160
      ],
      "id": "1b92fd80-f76d-405b-a9fe-9ec60531ce6e",
      "name": "Google Gemini Chat Model",
      "credentials": {
        "googlePalmApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "options": {}
      },
      "type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
      "typeVersion": 1,
      "position": [
        1208,
        728
      ],
      "id": "e98c4649-dcf3-4719-ba4b-cbc8f021dbf8",
      "name": "Google Gemini Chat Model1",
      "credentials": {
        "googlePalmApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "text": "={{ $json.text }}",
        "schemaType": "fromJson",
        "jsonSchemaExample": "[\n  {\n    \"Active Listings\": [\n      {\n      \"Address\": \"5701 AINSWORTH CT\",\n      \"H SqFt\": \"2,680\",\n      \"BR\": \"3\",\n      \"FB\": \"1\",\n      \"HB\": \"0\",\n      \"YrBlt\": \"1987\",\n      \"Pool\": \"Yes\",\n      \"List Price\": \"$549,900\",\n      \"CDOM\": \"166\",\n      \"Sale Price\": \"\",\n      \"Sale Date\": \"\",\n      \"Listing\": \"Active\"\n    },\n    {\n      \"Address\": \"5701 AINSWORTH CT\",\n      \"H SqFt\": \"2,680\",\n      \"BR\": \"3\",\n      \"FB\": \"1\",\n      \"HB\": \"0\",\n      \"YrBlt\": \"1987\",\n      \"Pool\": \"Yes\",\n      \"List Price\": \"$549,900\",\n      \"CDOM\": \"166\",\n      \"Sale Price\": \"\",\n      \"Sale Date\": \"\",\n      \"Listing\": \"Active\"\n    }\n    ],\n    \"Pending Listings\": [\n    {\n      \"Address\": \"5701 AINSWORTH CT\",\n      \"H SqFt\": \"2,680\",\n      \"BR\": \"3\",\n      \"FB\": \"1\",\n      \"HB\": \"0\",\n      \"YrBlt\": \"1987\",\n      \"Pool\": \"Yes\",\n      \"List Price\": \"$549,900\",\n      \"CDOM\": \"166\",\n      \"Sale Price\": \"\",\n      \"Sale Date\": \"\",\n      \"Listing\": \"Pending\"\n    },\n    {\n      \"Address\": \"5701 AINSWORTH CT\",\n      \"H SqFt\": \"2,680\",\n      \"BR\": \"3\",\n      \"FB\": \"1\",\n      \"HB\": \"0\",\n      \"YrBlt\": \"1987\",\n      \"Pool\": \"Yes\",\n      \"List Price\": \"$549,900\",\n      \"CDOM\": \"166\",\n      \"Sale Price\": \"\",\n      \"Sale Date\": \"\",\n      \"Listing\": \"Pending\"\n    }\n    ],\n    \"Sold Listings\": [\n    {\n      \"Address\": \"5701 AINSWORTH CT\",\n      \"H SqFt\": \"2,680\",\n      \"BR\": \"3\",\n      \"FB\": \"1\",\n      \"HB\": \"0\",\n      \"YrBlt\": \"1987\",\n      \"Pool\": \"Yes\",\n      \"List Price\": \"$549,900\",\n      \"CDOM\": \"166\",\n      \"Sale Price\": \"\",\n      \"Sale Date\": \"\",\n      \"Listing\": \"Sold\"\n    },\n    {\n      \"Address\": \"5701 AINSWORTH CT\",\n      \"H SqFt\": \"2,680\",\n      \"BR\": \"3\",\n      \"FB\": \"1\",\n      \"HB\": \"0\",\n      \"YrBlt\": \"1987\",\n      \"Pool\": \"Yes\",\n      \"List Price\": \"$549,900\",\n      \"CDOM\": \"166\",\n      \"Sale Price\": \"\",\n      \"Sale Date\": \"\",\n      \"Listing\": \"Sold\"\n    }\n    ]\n  }\n]",
        "options": {
          "systemPromptTemplate": "You are an expert extraction algorithm.\nOnly extract relevant information from the text.\nIf you do not know the value of an attribute asked to extract, you may omit the attribute's value. "
        }
      },
      "type": "@n8n/n8n-nodes-langchain.informationExtractor",
      "typeVersion": 1.2,
      "position": [
        1136,
        504
      ],
      "id": "81f3a9a6-d6c6-4e69-8e83-3deb5209e413",
      "name": "Information Extractor1"
    },
    {
      "parameters": {
        "jsCode": "const fs = require(\"fs\");\nconst path = require(\"path\");\nconst pptxgen = require(\"pptxgenjs\");\nconst { html2pptx } = require(\"./html2pptx\");\n\n// Read JSON data\nconst jsonPath = process.argv[2] || \"/mnt/user-data/uploads/cma_data.json\";\nconst outputPath = process.argv[3] || \"CMA_Report.pptx\";\n\nconst rawData = JSON.parse(fs.readFileSync(jsonPath, \"utf8\"));\nconst data = rawData[0].output[0];\n\n// Helper function to parse price string to number\nfunction parsePrice(priceStr) {\n  if (!priceStr) return 0;\n  return parseFloat(priceStr.replace(/[$,]/g, \"\")) || 0;\n}\n\n// Helper function to parse sqft string to number\nfunction parseSqft(sqftStr) {\n  if (!sqftStr) return 0;\n  return parseFloat(sqftStr.replace(/,/g, \"\")) || 0;\n}\n\n// Calculate price per sqft\nfunction calcPricePerSqft(price, sqft) {\n  const p = parsePrice(price);\n  const s = parseSqft(sqft);\n  if (s === 0) return \"$0\";\n  return \"$\" + Math.round(p / s).toLocaleString();\n}\n\n// Format baths (FB + HB)\nfunction formatBaths(fb, hb) {\n  const full = parseInt(fb) || 0;\n  const half = parseInt(hb) || 0;\n  if (half > 0) return `${full}.${half}`;\n  return `${full}`;\n}\n\n// Generate title slide HTML\nfunction generateTitleSlide() {\n  return `<!DOCTYPE html>\n<html>\n<head><link rel=\"stylesheet\" href=\"cma-style.css\"></head>\n<body class=\"col bg-surface\" style=\"width: 960px; height: 540px; position: relative;\">\n  <!-- Header bar -->\n  <div style=\"width: 100%; height: 80px; background: #2c3e5c;\" class=\"fit\"></div>\n  <div style=\"width: 100%; height: 12px; background: #808080;\" class=\"fit\"></div>\n  \n  <!-- Main content -->\n  <div class=\"fill-height col center\" style=\"padding: 20px;\">\n    <h1 style=\"font-size: 42px; color: #2c3e5c; margin: 0 0 30px 0; text-align: center;\">Comparative Market Analysis</h1>\n    <p style=\"font-size: 24px; color: #555; margin: 0 0 40px 0; text-align: center;\">Prepared for Your Property</p>\n    \n    <div style=\"display: flex; gap: 60px; margin-top: 20px;\">\n      <div style=\"text-align: left;\">\n        <p style=\"font-size: 14px; color: #2c3e5c; font-weight: bold; margin: 0 0 8px 0;\">Active Listings</p>\n        <p style=\"font-size: 28px; color: #2c3e5c; font-weight: bold; margin: 0;\">${data[\"Active Listings\"].length}</p>\n      </div>\n      <div style=\"text-align: left;\">\n        <p style=\"font-size: 14px; color: #2c3e5c; font-weight: bold; margin: 0 0 8px 0;\">Pending Listings</p>\n        <p style=\"font-size: 28px; color: #2c3e5c; font-weight: bold; margin: 0;\">${data[\"Pending Listings\"].length}</p>\n      </div>\n      <div style=\"text-align: left;\">\n        <p style=\"font-size: 14px; color: #2c3e5c; font-weight: bold; margin: 0 0 8px 0;\">Sold Listings</p>\n        <p style=\"font-size: 28px; color: #2c3e5c; font-weight: bold; margin: 0;\">${data[\"Sold Listings\"].length}</p>\n      </div>\n    </div>\n  </div>\n</body>\n</html>`;\n}\n\n// Generate listing card HTML\nfunction generateListingCard(listing, isSold = false) {\n  const pricePerSqft = calcPricePerSqft(listing[\"List Price\"], listing[\"H SqFt\"]);\n  const baths = formatBaths(listing[\"FB\"], listing[\"HB\"]);\n  \n  let soldInfo = \"\";\n  if (isSold && listing[\"Sale Price\"]) {\n    const salePerSqft = calcPricePerSqft(listing[\"Sale Price\"], listing[\"H SqFt\"]);\n    soldInfo = `\n      <p style=\"font-size: 10px; margin: 0 0 2px 0;\"><b style=\"color: #2c3e5c;\">Sale Price:</b> ${listing[\"Sale Price\"]}</p>\n      <p style=\"font-size: 10px; margin: 0 0 2px 0;\"><b style=\"color: #2c3e5c;\">Sale $/Ft:</b> ${salePerSqft}</p>\n      <p style=\"font-size: 10px; margin: 0;\"><b style=\"color: #2c3e5c;\">Sale Date:</b> ${listing[\"Sale Date\"]}</p>`;\n  }\n  \n  return `\n    <div style=\"width: 280px; background: #fff; border: 1px solid #e0e0e0; border-radius: 4px;\">\n      <!-- Image placeholder -->\n      <div style=\"width: 280px; height: 100px; background: #f0f0f0; border-radius: 4px 4px 0 0; border-bottom: 1px solid #e0e0e0;\">\n        <p style=\"font-size: 11px; color: #999; text-align: center; margin: 0; padding-top: 40px;\">Insert Property Image</p>\n      </div>\n      \n      <!-- Address header -->\n      <div style=\"background: #2c3e5c; padding: 6px;\">\n        <p style=\"font-size: 11px; font-weight: bold; text-align: center; margin: 0; color: white;\">${listing[\"Address\"]}</p>\n      </div>\n      \n      <!-- Property details -->\n      <div class=\"col\" style=\"padding: 8px;\">\n        <p style=\"font-size: 10px; margin: 0 0 2px 0;\"><b style=\"color: #2c3e5c;\">SqFt:</b> ${listing[\"H SqFt\"]}  |  <b style=\"color: #2c3e5c;\">BR:</b> ${listing[\"BR\"]}  |  <b style=\"color: #2c3e5c;\">BA:</b> ${baths}</p>\n        <p style=\"font-size: 10px; margin: 0 0 2px 0;\"><b style=\"color: #2c3e5c;\">Year:</b> ${listing[\"YrBlt\"]}  |  <b style=\"color: #2c3e5c;\">Pool:</b> ${listing[\"Pool\"]}</p>\n        \n        <!-- Divider -->\n        <div style=\"height: 1px; background: #e0e0e0; margin: 4px 0;\"></div>\n        \n        <!-- Pricing details -->\n        <p style=\"font-size: 10px; margin: 0 0 2px 0;\"><b style=\"color: #2c3e5c;\">List Price:</b> ${listing[\"List Price\"]}  |  <b style=\"color: #2c3e5c;\">$/Ft:</b> ${pricePerSqft}</p>\n        <p style=\"font-size: 10px; margin: 0 0 2px 0;\"><b style=\"color: #2c3e5c;\">D.O.M.:</b> ${listing[\"CDOM\"]}</p>\n        ${soldInfo}\n      </div>\n    </div>`;\n}\n\n// Generate listings page HTML\nfunction generateListingsPage(title, listings, startIdx, count, isSold = false) {\n  const pageListings = listings.slice(startIdx, startIdx + count);\n  const cards = pageListings.map(l => generateListingCard(l, isSold)).join(\"\\n\");\n  \n  return `<!DOCTYPE html>\n<html>\n<head><link rel=\"stylesheet\" href=\"cma-style.css\"></head>\n<body class=\"col bg-surface\" style=\"width: 960px; height: 540px;\">\n  <!-- Wrapper with margins -->\n  <div class=\"col fill-height\" style=\"margin: 24px 24px 20px 24px;\">\n    <!-- Title -->\n    <h1 style=\"font-size: 34px; color: #1d1d1d; margin: 0 0 10px 0; font-weight: bold;\">${title}</h1>\n    \n    <!-- Header bars -->\n    <div class=\"row fit\">\n      <div style=\"flex: 1; height: 8px; background: #2c3e5c;\"></div>\n      <div style=\"flex: 1; height: 8px; background: #808080;\"></div>\n    </div>\n    \n    <!-- Content area with cards -->\n    <div class=\"fill-height row\" style=\"padding-top: 20px; gap: 20px; justify-content: center; align-items: flex-start;\">\n      ${cards}\n    </div>\n  </div>\n</body>\n</html>`;\n}\n\n// Generate totals page HTML\nfunction generateTotalsPage() {\n  // Calculate Active stats\n  const activeListings = data[\"Active Listings\"];\n  const activeQty = activeListings.length;\n  const activeAvgDOM = Math.round(activeListings.reduce((sum, l) => sum + parseInt(l[\"CDOM\"] || 0), 0) / activeQty);\n  const activeAvgPrice = Math.round(activeListings.reduce((sum, l) => sum + parsePrice(l[\"List Price\"]), 0) / activeQty);\n  const activeAvgPriceSqft = Math.round(activeListings.reduce((sum, l) => {\n    return sum + (parsePrice(l[\"List Price\"]) / parseSqft(l[\"H SqFt\"]));\n  }, 0) / activeQty);\n  \n  // Calculate Pending stats\n  const pendingListings = data[\"Pending Listings\"];\n  const pendingQty = pendingListings.length;\n  const pendingAvgDOM = Math.round(pendingListings.reduce((sum, l) => sum + parseInt(l[\"CDOM\"] || 0), 0) / pendingQty);\n  const pendingAvgPrice = Math.round(pendingListings.reduce((sum, l) => sum + parsePrice(l[\"List Price\"]), 0) / pendingQty);\n  const pendingAvgPriceSqft = Math.round(pendingListings.reduce((sum, l) => {\n    return sum + (parsePrice(l[\"List Price\"]) / parseSqft(l[\"H SqFt\"]));\n  }, 0) / pendingQty);\n  \n  // Calculate Sold stats\n  const soldListings = data[\"Sold Listings\"];\n  const soldQty = soldListings.length;\n  const soldAvgDOM = Math.round(soldListings.reduce((sum, l) => sum + parseInt(l[\"CDOM\"] || 0), 0) / soldQty);\n  const soldAvgListPrice = Math.round(soldListings.reduce((sum, l) => sum + parsePrice(l[\"List Price\"]), 0) / soldQty);\n  const soldAvgListPriceSqft = Math.round(soldListings.reduce((sum, l) => {\n    return sum + (parsePrice(l[\"List Price\"]) / parseSqft(l[\"H SqFt\"]));\n  }, 0) / soldQty);\n  const soldAvgSalePrice = Math.round(soldListings.reduce((sum, l) => sum + parsePrice(l[\"Sale Price\"]), 0) / soldQty);\n  const soldAvgSalePriceSqft = Math.round(soldListings.reduce((sum, l) => {\n    return sum + (parsePrice(l[\"Sale Price\"]) / parseSqft(l[\"H SqFt\"]));\n  }, 0) / soldQty);\n  \n  return `<!DOCTYPE html>\n<html>\n<head><link rel=\"stylesheet\" href=\"cma-style.css\"></head>\n<body class=\"col bg-surface\" style=\"width: 960px; height: 540px;\">\n  <!-- Wrapper with margins -->\n  <div class=\"col fill-height\" style=\"margin: 24px 24px 20px 24px;\">\n    <!-- Title -->\n    <h1 style=\"font-size: 34px; color: #1d1d1d; margin: 0 0 10px 0; font-weight: bold;\">Totals</h1>\n    \n    <!-- Header bars -->\n    <div class=\"row fit\">\n      <div style=\"flex: 1; height: 8px; background: #2c3e5c;\"></div>\n      <div style=\"flex: 1; height: 8px; background: #808080;\"></div>\n    </div>\n    \n    <!-- Content -->\n    <div class=\"fill-height col\" style=\"padding-top: 20px; gap: 16px;\">\n    \n    <!-- ACTIVE Section -->\n    <div class=\"col\" style=\"gap: 4px;\">\n      <h2 style=\"font-size: 22px; color: #1d1d1d; margin: 0;\">ACTIVE</h2>\n      <div style=\"width: 80px; height: 4px; background: #808080; margin-bottom: 4px;\"></div>\n      <p style=\"font-size: 14px; margin: 0;\"><b>Quantity:</b> ${activeQty}  |  <b>Average D.O.M.:</b> ${activeAvgDOM}</p>\n      <p style=\"font-size: 14px; margin: 0;\"><b>Average List Price:</b> $${activeAvgPrice.toLocaleString()}  |  <b>Average List $/SqFt:</b> $${activeAvgPriceSqft}</p>\n    </div>\n    \n    <!-- PENDING Section -->\n    <div class=\"col\" style=\"gap: 4px;\">\n      <h2 style=\"font-size: 22px; color: #1d1d1d; margin: 0;\">PENDING</h2>\n      <div style=\"width: 80px; height: 4px; background: #808080; margin-bottom: 4px;\"></div>\n      <p style=\"font-size: 14px; margin: 0;\"><b>Quantity:</b> ${pendingQty}  |  <b>Average D.O.M.:</b> ${pendingAvgDOM}</p>\n      <p style=\"font-size: 14px; margin: 0;\"><b>Average List Price:</b> $${pendingAvgPrice.toLocaleString()}  |  <b>Average List $/SqFt:</b> $${pendingAvgPriceSqft}</p>\n    </div>\n    \n    <!-- SOLD Section -->\n    <div class=\"col\" style=\"gap: 4px;\">\n      <h2 style=\"font-size: 22px; color: #1d1d1d; margin: 0;\">SOLD</h2>\n      <div style=\"width: 80px; height: 4px; background: #808080; margin-bottom: 4px;\"></div>\n      <p style=\"font-size: 14px; margin: 0;\"><b>Quantity:</b> ${soldQty}  |  <b>Average D.O.M.:</b> ${soldAvgDOM}</p>\n      <p style=\"font-size: 14px; margin: 0;\"><b>Average List Price:</b> $${soldAvgListPrice.toLocaleString()}  |  <b>Average List $/SqFt:</b> $${soldAvgListPriceSqft}</p>\n      <p style=\"font-size: 14px; margin: 0;\"><b>Average Sold Price:</b> $${soldAvgSalePrice.toLocaleString()}  |  <b>Average Sold $/SqFt:</b> $${soldAvgSalePriceSqft}</p>\n    </div>\n    \n  </div>\n  </div>\n</body>\n</html>`;\n}\n\n// Main generation function\nasync function generatePresentation() {\n  const pptx = new pptxgen();\n  pptx.layout = \"LAYOUT_16x9\";\n  pptx.title = \"Comparative Market Analysis\";\n  pptx.author = \"CMA Generator\";\n  \n  // Create HTML files\n  const slides = [];\n  \n  // Slide 1: Title\n  fs.writeFileSync(\"slide-title.html\", generateTitleSlide());\n  slides.push(\"slide-title.html\");\n  \n  // Slide 2: Active Listings (all 3 on one page)\n  fs.writeFileSync(\"slide-active.html\", generateListingsPage(\"Active Listings\", data[\"Active Listings\"], 0, 3, false));\n  slides.push(\"slide-active.html\");\n  \n  // Slide 3: Pending Listings (2 on one page)\n  fs.writeFileSync(\"slide-pending.html\", generateListingsPage(\"Pending Listings\", data[\"Pending Listings\"], 0, 3, false));\n  slides.push(\"slide-pending.html\");\n  \n  // Slide 4-5: Sold Listings (3 per page)\n  fs.writeFileSync(\"slide-sold1.html\", generateListingsPage(\"Sold Listings\", data[\"Sold Listings\"], 0, 3, true));\n  slides.push(\"slide-sold1.html\");\n  \n  fs.writeFileSync(\"slide-sold2.html\", generateListingsPage(\"Sold Listings\", data[\"Sold Listings\"], 3, 3, true));\n  slides.push(\"slide-sold2.html\");\n  \n  // Slide 6: Totals\n  fs.writeFileSync(\"slide-totals.html\", generateTotalsPage());\n  slides.push(\"slide-totals.html\");\n  \n  // Convert all HTML slides to PowerPoint\n  for (const slideFile of slides) {\n    await html2pptx(slideFile, pptx);\n  }\n  \n  // Save the presentation\n  await pptx.writeFile(outputPath);\n  console.log(`Presentation saved to: ${outputPath}`);\n}\n\ngeneratePresentation().catch(console.error);\n"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1488,
        504
      ],
      "id": "ca8fa22b-bf8e-4a54-a0e0-33703ef20276",
      "name": "Code in JavaScript"
    },
    {
      "parameters": {},
      "type": "n8n-nodes-pdfmonkey.pdfMonkey",
      "typeVersion": 1,
      "position": [
        464,
        176
      ],
      "id": "73125177-5242-4cd3-a2e1-211ab03b807a",
      "name": "PDFMonkey",
      "credentials": {
        "pdfMonkeyApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.3,
      "position": [
        1840,
        640
      ],
      "id": "8053e5d7-cbef-432e-93e3-be7c808d9111",
      "name": "HTTP Request"
    }
  ],
  "connections": {
    "Google Drive Trigger": {
      "main": [
        [
          {
            "node": "Download file",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Download file": {
      "main": [
        [
          {
            "node": "Extract from File",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract from File": {
      "main": [
        [
          {
            "node": "Information Extractor1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Google Gemini Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "Information Extractor",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Google Gemini Chat Model1": {
      "ai_languageModel": [
        [
          {
            "node": "Information Extractor1",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Information Extractor1": {
      "main": [
        [
          {
            "node": "Code in JavaScript",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1"
  },
  "staticData": null,
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "versionId": "b01c4d3c-ebc3-4774-a0e1-b598bc41f8da",
  "activeVersionId": null,
  "triggerCount": 0,
  "shared": [
    {
      "updatedAt": "2025-11-21T18:29:25.702Z",
      "createdAt": "2025-11-21T18:29:25.702Z",
      "role": "workflow:owner",
      "workflowId": "FsE5BMhYYzTYRTwM",
      "projectId": "Jb2DFpuDeQFonpJH"
    }
  ],
  "activeVersion": null,
  "tags": []
}