{
  "id": "EXVYD8hh4mxxOE7M",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "Zoho Deal Forecasting with External Market Factor",
  "tags": [],
  "nodes": [
    {
      "id": "3b868d38-174b-43f7-bfce-d5639864534a",
      "name": "Get Market Signal1",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -1040,
        -1296
      ],
      "parameters": {
        "url": "https://www.alphavantage.co/query",
        "options": {},
        "queryParametersUi": {
          "parameter": [
            {
              "name": "function",
              "value": "GLOBAL_QUOTE"
            },
            {
              "name": "symbol",
              "value": "SPY"
            },
            {
              "name": "apikey",
              "value": "{{Your_Alphavantage_API_key}}"
            }
          ]
        }
      },
      "typeVersion": 2
    },
    {
      "id": "ebebe4b3-0701-4471-9cce-ebbf45c03613",
      "name": "Update Deal Forecast1",
      "type": "n8n-nodes-base.zohoCrm",
      "position": [
        592,
        -1280
      ],
      "parameters": {
        "dealId": "={{ $json.id }}",
        "resource": "deal",
        "operation": "update",
        "updateFields": {
          "Amount": "={{ $json.Amount }}",
          "customFields": {
            "customFields": [
              {
                "value": "={{ $json.Market_Signal }}",
                "fieldId": "Market_Signal"
              },
              {
                "value": "={{ $json.Seasonal_Factor }}",
                "fieldId": "Seasonal_Factor"
              },
              {
                "value": "={{ $json.Market_Seasonal_Adjusted_Forecast }}",
                "fieldId": "Market_Signal_Adjust_forecast"
              },
              {
                "value": "={{ $json.Expected_Revenue }}",
                "fieldId": "Revenue"
              }
            ]
          }
        }
      },
      "credentials": {
        "zohoOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "f7238560-a165-47a0-8503-543f76ae3aaf",
      "name": "Send Forecast Summary1",
      "type": "n8n-nodes-base.slack",
      "position": [
        864,
        -1280
      ],
      "parameters": {
        "text": "=Deal Forecast Updated (AI-Enhanced)\n\nDeal: {{ $('Merge Forecast & AI data').item.json.Deal_Name }}\nStage: {{ $('Merge Forecast & AI data').item.json.Stage }}\nAmount: {{ $('Merge Forecast & AI data').item.json.Amount }}\n\nMarket Signal: {{ $('Merge Forecast & AI data').item.json.Market_Signal }}\nSeasonal Factor: {{ $('Merge Forecast & AI data').item.json.Seasonal_Factor }}\n\nAI Match Ratio: {{ $('Merge Forecast & AI data').item.json.match_ratio }}%\nConfidence: {{ $('Merge Forecast & AI data').item.json.confidence }}\nReason: {{ $('Merge Forecast & AI data').item.json.reason }}\n\nAdjusted Forecast: {{ $('Merge Forecast & AI data').item.json.Market_Seasonal_Adjusted_Forecast }}\n",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "list",
          "value": "C09S57E2JQ2",
          "cachedResultName": "n8n"
        },
        "otherOptions": {
          "includeLinkToWorkflow": false
        }
      },
      "credentials": {
        "slackApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "42ebde43-b13b-4a10-9c70-3ebc8b6f2662",
      "name": "Fetch open Deals1",
      "type": "n8n-nodes-base.zohoCrm",
      "position": [
        -1056,
        -1696
      ],
      "parameters": {
        "options": {},
        "resource": "deal",
        "operation": "getAll"
      },
      "credentials": {
        "zohoOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "9aa6b33e-884b-4953-8d35-8ddd6f7f3350",
      "name": "Combine Deal & Market Info1",
      "type": "n8n-nodes-base.merge",
      "position": [
        -688,
        -1504
      ],
      "parameters": {},
      "typeVersion": 3.2
    },
    {
      "id": "dd875384-23b8-421c-9d82-a8b35823914d",
      "name": "Generate Forecast Metrics1",
      "type": "n8n-nodes-base.function",
      "position": [
        -480,
        -1504
      ],
      "parameters": {
        "functionCode": "// ----------------------\n// AI-style Dynamic Forecast Function Node\n// ----------------------\n\n// Get all items from the Merge node\nconst allItems = $input.all();\n\n//  Separate current deals and historical deals\n// Assuming your \"Get Deals\" node fetches both historical and current deals\n// You can adjust historical vs current by date if needed\nconst deals = allItems\n    .map(item => item.json)\n    .filter(d => d.Amount && d.Probability); // basic filter\n\n//  Get Market Signal\nconst marketItem = allItems.find(item => item.json.marketSignal !== undefined);\nconst marketSignal = marketItem?.json?.marketSignal || 1;\n\n//  Helper function to safely parse numbers\nfunction safeNumber(value, fallback = 0) {\n    if (value === null || value === undefined || value === \"\") return fallback;\n    const num = parseFloat(value);\n    return isNaN(num) ? fallback : num;\n}\n\n// ----------------------\n//  Dynamic Seasonality Calculation\n// Using historical deal close dates to compute seasonal factors\n// ----------------------\nconst historicalDeals = deals.filter(d => d.Close_Date); // all deals with close date\nconst monthlyTotals = Array(12).fill(0);\nconst monthlyCounts = Array(12).fill(0);\n\nhistoricalDeals.forEach(d => {\n    const closeMonth = new Date(d.Close_Date).getMonth(); // 0 = Jan\n    const amount = safeNumber(d.Amount);\n    const probability = safeNumber(d.Probability) / 100;\n    monthlyTotals[closeMonth] += amount * probability;\n    monthlyCounts[closeMonth] += 1;\n});\n\n// Normalize to average\nconst totalRevenue = monthlyTotals.reduce((a, b) => a + b, 0);\nconst avgRevenue = totalRevenue / 12;\nconst seasonalityFactors = monthlyTotals.map(total => (avgRevenue ? total / avgRevenue : 1));\n\n// Current month factor\nconst currentMonth = new Date().getMonth();\nconst seasonalFactor = seasonalityFactors[currentMonth] || 1;\n\n// ----------------------\n//  Filter valid deals (Amount > 0 and Probability > 0)\n// ----------------------\nconst validDeals = deals.filter(d => safeNumber(d.Amount) > 0 && safeNumber(d.Probability) > 0);\n\n// ----------------------\n//  Compute Forecast with Market + Seasonal Factors\n// ----------------------\nreturn validDeals.map(d => {\n    const dealName = d.Deal_Name || \"Unnamed Deal\";\n    const amount = safeNumber(d.Amount);\n    const probability = safeNumber(d.Probability) / 100;\n\n    const pipelineWeighted = amount * probability;\n    const expectedRevenue = safeNumber(d.Expected_Revenue, pipelineWeighted);\n\n    // AI-style adjusted forecast\n    const adjustedForecast = pipelineWeighted * marketSignal * seasonalFactor;\n\n    return {\n        json: {\n            id: d.id,\n            Deal_Name: dealName,\n            Stage: d.Stage,\n            Amount: amount,\n            Probability: probability,\n            Expected_Revenue: expectedRevenue,\n            Market_Signal: marketSignal,\n            Seasonal_Factor: seasonalFactor,\n            Market_Seasonal_Adjusted_Forecast: adjustedForecast\n        }\n    };\n});\n"
      },
      "typeVersion": 1,
      "alwaysOutputData": false
    },
    {
      "id": "b0ec65c3-ae04-480f-b9c1-bff458a81baa",
      "name": "Weekly Trigger1",
      "type": "n8n-nodes-base.cron",
      "position": [
        -1424,
        -1488
      ],
      "parameters": {
        "triggerTimes": {
          "item": [
            {
              "hour": 2
            }
          ]
        }
      },
      "typeVersion": 1
    },
    {
      "id": "06e67635-d308-45b2-90a7-05fe1fa5dcd4",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -2016,
        -1952
      ],
      "parameters": {
        "width": 416,
        "height": 672,
        "content": "## How it works\n\nThis workflow automatically updates your Zoho CRM deals every week with AI-enhanced forecasts. It fetches all active deals and current market signals, then calculates expected revenue using deal amount, probability, seasonality, and market trends. An AI node evaluates how well each deal matches market conditions and assigns a match ratio, confidence level, and reasoning. The workflow then updates the forecast in Zoho, stores the results in Supabase for tracking, and sends a summary to Slack so your team can quickly see deal performance and insights.\n\n## Setup steps\n\n**1.** Create required custom fields in Zoho CRM deals.\n\n**2.** Add Zoho OAuth credentials to n8n.\n\n**3.** Configure \u201cFetch open Deals\u201d node to get all active deals.\n\n**4.** Add your market API key (e.g., AlphaVantage) to the HTTP node.\n\n**5.** Replace placeholder Zoho field IDs with API names.\n\n**6.** Map Slack messages to your preferred channel.\n\n**7.** Test the workflow manually, then activate the weekly cron trigger."
      },
      "typeVersion": 1
    },
    {
      "id": "6ed251fd-3d15-4dda-8d20-3d9b1b129987",
      "name": "Store Forecast",
      "type": "n8n-nodes-base.supabase",
      "position": [
        576,
        -1760
      ],
      "parameters": {
        "tableId": "deal_forecast",
        "fieldsUi": {
          "fieldValues": [
            {
              "fieldId": "id",
              "fieldValue": "={{ $json.id }}"
            },
            {
              "fieldId": "deal_name",
              "fieldValue": "={{ $json.Deal_Name }}"
            },
            {
              "fieldId": "stage",
              "fieldValue": "={{ $json.Stage }}"
            },
            {
              "fieldId": "amount",
              "fieldValue": "={{ $json.Amount }}"
            },
            {
              "fieldId": "match_ratio",
              "fieldValue": "={{ $json.match_ratio }}"
            },
            {
              "fieldId": "confidence",
              "fieldValue": "={{ $json.confidence }}"
            },
            {
              "fieldId": "reason",
              "fieldValue": "={{ $json.reason }}"
            }
          ]
        }
      },
      "credentials": {
        "supabaseApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "13f2aaef-7cba-4aba-9f00-67132e598c5f",
      "name": "Deal Match Evaluator",
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "position": [
        -272,
        -1504
      ],
      "parameters": {
        "modelId": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4-turbo",
          "cachedResultName": "GPT-4-TURBO"
        },
        "options": {},
        "responses": {
          "values": [
            {
              "content": "=You are a sales forecasting assistant.\n\nGiven the following deal data and market signal, calculate:\n\n1. Match Ratio (0\u2013100):\n   - How well this deal aligns with current market conditions\n\n2. Confidence Level:\n   - LOW / MEDIUM / HIGH\n\n3. One-line reasoning\n\nDeal Data:\n- Deal Amount: {{ $json.Amount }}\n- Stage: {{ $json.Stage }}\n- Probability: {{ $json.Probability }}\n- Expected Revenue: {{ $json.Expected_Revenue }}\n- Seasonal Factor: {{ $json.Seasonal_Factor }}\n\nMarket Signal:\n- Market Index Factor: {{ $json.Market_Signal }}\n\nRespond strictly in JSON:\n{\n  \"match_ratio\": number,\n  \"confidence\": string,\n  \"reason\": string\n}\n"
            }
          ]
        },
        "builtInTools": {}
      },
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2
    },
    {
      "id": "f2843f1a-b113-423d-af62-8b44da12ebce",
      "name": "Sticky Note6",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1488,
        -1568
      ],
      "parameters": {
        "color": 7,
        "height": 224,
        "content": "Runs this workflow automatically every week."
      },
      "typeVersion": 1
    },
    {
      "id": "73e7a455-0f2f-481e-81a8-e4b89ed1c6c1",
      "name": "Sticky Note7",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1120,
        -1776
      ],
      "parameters": {
        "color": 7,
        "width": 256,
        "height": 240,
        "content": "Fetches all active deals from Zoho that are still in early/mid pipeline stages."
      },
      "typeVersion": 1
    },
    {
      "id": "bca71ab2-ecb4-4791-b90d-761514d6bec7",
      "name": "Sticky Note8",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1120,
        -1392
      ],
      "parameters": {
        "color": 7,
        "width": 256,
        "height": 256,
        "content": "Fetches real-time market trend data (SPY index) from AlphaVantage."
      },
      "typeVersion": 1
    },
    {
      "id": "3cfe9af7-10b8-4507-91fe-51208b9610b8",
      "name": "Parse AI Output",
      "type": "n8n-nodes-base.code",
      "position": [
        32,
        -1504
      ],
      "parameters": {
        "jsCode": "const items = $input.all();\n\nconst results = items.map(item => {\n  const rawText =\n    item.json.output?.[0]?.content?.[0]?.text || \"\";\n\n  // Remove markdown wrappers\n  const cleaned = rawText\n    .replace(/```json/g, \"\")\n    .replace(/```/g, \"\")\n    .trim();\n\n  let parsed;\n  try {\n    parsed = JSON.parse(cleaned);\n  } catch (e) {\n    parsed = {\n      match_ratio: null,\n      confidence: \"UNKNOWN\",\n      reason: \"Failed to parse AI response\"\n    };\n  }\n\n  return {\n    json: {\n      match_ratio: parsed.match_ratio,\n      confidence: parsed.confidence,\n      reason: parsed.reason\n    }\n  };\n});\n\nreturn results;\n"
      },
      "typeVersion": 2
    },
    {
      "id": "a924816e-9de7-4b17-acc6-bbcc1ca186f1",
      "name": "Merge Forecast & AI data",
      "type": "n8n-nodes-base.set",
      "position": [
        224,
        -1504
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "940a5614-1915-411a-ab0b-d3b9edf1f0a7",
              "name": "id",
              "type": "string",
              "value": "={{ $('Generate Forecast Metrics1').item.json.id }}"
            },
            {
              "id": "1eb06bd8-b51e-4eaf-8aa2-6dd706d2df25",
              "name": "Deal_Name",
              "type": "string",
              "value": "={{ $('Generate Forecast Metrics1').item.json.Deal_Name }}"
            },
            {
              "id": "2b+1234567890e5-46ae-9bd7-c5d2564c4a01",
              "name": "Stage",
              "type": "string",
              "value": "={{ $('Generate Forecast Metrics1').item.json.Stage }}"
            },
            {
              "id": "37fb74b6-8d27-4b99-87d3-614c560bff9c",
              "name": "Amount",
              "type": "number",
              "value": "={{ $('Generate Forecast Metrics1').item.json.Amount }}"
            },
            {
              "id": "0e272341-46a1-4fbe-8258-02039fd1f942",
              "name": "Probability",
              "type": "number",
              "value": "={{ $('Generate Forecast Metrics1').item.json.Probability }}"
            },
            {
              "id": "e8c7058e-2840-4a5d-a613-8bba4806cec7",
              "name": "Expected_Revenue",
              "type": "number",
              "value": "={{ $('Generate Forecast Metrics1').item.json.Expected_Revenue }}"
            },
            {
              "id": "013b3035-d4a0-4624-8552-c9da65af4c8b",
              "name": "Market_Signal",
              "type": "number",
              "value": "={{ $('Generate Forecast Metrics1').item.json.Market_Signal }}"
            },
            {
              "id": "eb185701-9f77-4944-a253-5631358336a0",
              "name": "Seasonal_Factor",
              "type": "number",
              "value": "={{ $('Generate Forecast Metrics1').item.json.Seasonal_Factor }}"
            },
            {
              "id": "cd064e36-cbef-464d-a4c5-9a12f6b2a11e",
              "name": "Market_Seasonal_Adjusted_Forecast",
              "type": "number",
              "value": "={{ $('Generate Forecast Metrics1').item.json.Market_Seasonal_Adjusted_Forecast }}"
            },
            {
              "id": "5482bf0a-0f81-4073-aa73-461ff9638425",
              "name": "match_ratio",
              "type": "number",
              "value": "={{ $json.match_ratio }}"
            },
            {
              "id": "1c024593-bed9-4d68-96bd-0abe41a7f78c",
              "name": "confidence",
              "type": "string",
              "value": "={{ $json.confidence }}"
            },
            {
              "id": "d0473487-a38c-454f-a5dc-3e93d1bae583",
              "name": "reason",
              "type": "string",
              "value": "={{ $json.reason }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "6f197d7a-e472-4cf1-bfbe-7ee3c35b0b30",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -736,
        -1680
      ],
      "parameters": {
        "color": 7,
        "width": 1152,
        "height": 400,
        "content": "## Forecast Calculation Process\nThis part of the workflow collects all active deals from Zoho and real-time market signals. It calculates the expected revenue using deal amount, probability, seasonality, and market trends. An AI node then evaluates each deal\u2019s alignment with market conditions, giving a match ratio, confidence level, and reasoning. Finally, all metrics and AI insights are merged into one complete dataset ready for updating or storing."
      },
      "typeVersion": 1
    },
    {
      "id": "c92830dd-a1d2-4a63-beda-92519e5d6331",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        496,
        -1856
      ],
      "parameters": {
        "color": 7,
        "width": 304,
        "height": 256,
        "content": "This node saves all updated deal forecasts and AI insights into a database."
      },
      "typeVersion": 1
    },
    {
      "id": "0c07a869-4279-402a-8e73-7badd30732a6",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        480,
        -1456
      ],
      "parameters": {
        "color": 7,
        "width": 640,
        "height": 368,
        "content": "## Update & Share Forecast\nThis part of the workflow updates each deal in Zoho CRM with the new forecast values, including expected revenue, market signals, seasonal factors, and AI insights. After updating, it sends a summary message to Slack, so the team can quickly see the updated forecasts and deal performance."
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "3063b21a-5ccf-4c60-8b54-f670f8ead15f",
  "connections": {
    "Parse AI Output": {
      "main": [
        [
          {
            "node": "Merge Forecast & AI data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Weekly Trigger1": {
      "main": [
        [
          {
            "node": "Fetch open Deals1",
            "type": "main",
            "index": 0
          },
          {
            "node": "Get Market Signal1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch open Deals1": {
      "main": [
        [
          {
            "node": "Combine Deal & Market Info1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Market Signal1": {
      "main": [
        [
          {
            "node": "Combine Deal & Market Info1",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Deal Match Evaluator": {
      "main": [
        [
          {
            "node": "Parse AI Output",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Update Deal Forecast1": {
      "main": [
        [
          {
            "node": "Send Forecast Summary1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send Forecast Summary1": {
      "main": [
        []
      ]
    },
    "Merge Forecast & AI data": {
      "main": [
        [
          {
            "node": "Update Deal Forecast1",
            "type": "main",
            "index": 0
          },
          {
            "node": "Store Forecast",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generate Forecast Metrics1": {
      "main": [
        [
          {
            "node": "Deal Match Evaluator",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Combine Deal & Market Info1": {
      "main": [
        [
          {
            "node": "Generate Forecast Metrics1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}