{
  "name": "SaaS Synthetic Product Testing Simulator",
  "nodes": [
    {
      "parameters": {},
      "id": "e4a86bc7-dcfc-447e-a421-8c348457fcb8",
      "name": "Manual Trigger",
      "type": "n8n-nodes-base.manualTrigger",
      "typeVersion": 1,
      "position": [
        -2512,
        16
      ]
    },
    {
      "parameters": {
        "keepOnlySet": true,
        "values": {
          "string": [
            {
              "name": "industry",
              "value": "SaaS"
            },
            {
              "name": "productType",
              "value": "AI CRM"
            },
            {
              "name": "targetMarket",
              "value": "SMEs"
            },
            {
              "name": "datasetSize",
              "value": "1000"
            },
            {
              "name": "outputDestination",
              "value": "postgres"
            }
          ]
        },
        "options": {}
      },
      "id": "f9e439fb-b75d-4bb7-bd91-f5d8a0477eef",
      "name": "Set Simulation Config",
      "type": "n8n-nodes-base.set",
      "typeVersion": 2,
      "position": [
        -2320,
        16
      ]
    },
    {
      "parameters": {
        "functionCode": "const count = parseInt($json.datasetSize || '1000', 10);\nconst companies = [];\nfor (let i = 1; i <= Math.max(1, Math.floor(count / 10)); i++) {\n  companies.push({\n    companyId: `COMP-${i}`,\n    industry: $json.industry,\n    productType: $json.productType,\n    targetMarket: $json.targetMarket,\n    employeeCount: Math.floor(Math.random() * 90) + 10,\n    monthlyRevenue: Math.floor(Math.random() * 50000) + 5000,\n    growthStage: ['Pre-seed', 'Seed', 'Series A'][Math.floor(Math.random() * 3)]\n  });\n}\nreturn companies.map(c => ({ json: c }));"
      },
      "id": "cafb494d-c887-4a23-8217-cc06449e5ab1",
      "name": "Generate Companies",
      "type": "n8n-nodes-base.function",
      "typeVersion": 1,
      "position": [
        -2064,
        16
      ]
    },
    {
      "parameters": {
        "options": {}
      },
      "id": "ec50aaa9-7f36-434b-8835-aa6870e7dbbc",
      "name": "Loop Companies",
      "type": "n8n-nodes-base.splitInBatches",
      "typeVersion": 1,
      "position": [
        -1808,
        16
      ]
    },
    {
      "parameters": {
        "functionCode": "const roles = ['Founder', 'Sales Manager', 'Marketing Manager', 'Customer Success', 'Operations'];\nconst users = [];\nfor (let i = 0; i < roles.length; i++) {\n  users.push({\n    companyId: $json.companyId,\n    userId: `${$json.companyId}-USER-${i + 1}`,\n    role: roles[i],\n    age: Math.floor(Math.random() * 18) + 24,\n    experienceYears: Math.floor(Math.random() * 12) + 1,\n    salaryBand: ['Low', 'Medium', 'High'][Math.floor(Math.random() * 3)]\n  });\n}\nreturn users.map(u => ({ json: u }));"
      },
      "id": "b8d05304-514e-4243-8aa6-fd1720202508",
      "name": "Generate User Personas",
      "type": "n8n-nodes-base.function",
      "typeVersion": 1,
      "position": [
        -1568,
        16
      ]
    },
    {
      "parameters": {
        "options": {}
      },
      "id": "6ed071b8-99ce-47c9-a01a-58cdcaf5e249",
      "name": "Loop Users",
      "type": "n8n-nodes-base.splitInBatches",
      "typeVersion": 1,
      "position": [
        -1312,
        16
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://generativelanguage.googleapis.com/v1beta/models/gemini-3.5-flash-lite:generateContent",
        "sendQuery": true,
        "queryParameters": {
          "parameters": [
            {
              "name": "key",
              "value": ""
            }
          ]
        },
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {}
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ { \"contents\": [ { \"parts\": [ { \"text\": \"You are a synthetic SaaS user simulator. Generate a strict JSON object for this user. User: \" + $json.userId + \", Role: \" + $json.role + \". Simulate a 30-day product journey with fields: behaviorArchetype, churnRisk, and events (array of objects with day and event). Return JSON only.\" } ] } ] } }}",
        "options": {
          "batching": {
            "batch": {
              "batchSize": 10,
              "batchInterval": 3000
            }
          }
        }
      },
      "id": "50dec674-efb1-4a21-8869-bf0f833ced84",
      "name": "Generate User Journey",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4,
      "position": [
        -1072,
        16
      ]
    },
    {
      "parameters": {
        "functionCode": "let raw = '';\ntry {\n  raw = $json.candidates[0].content.parts[0].text;\n} catch (e) {\n  raw = '{}';\n}\nlet parsed;\ntry {\n  parsed = JSON.parse(raw);\n} catch (e) {\n  parsed = { behaviorArchetype: 'unknown', churnRisk: 'unknown', events: [] };\n}\nreturn [{ json: { ...$json, simulation: parsed } }];"
      },
      "id": "89c5104d-7842-40da-99f9-2cf9c96b4bb6",
      "name": "Parse Journey JSON",
      "type": "n8n-nodes-base.function",
      "typeVersion": 1,
      "position": [
        -816,
        16
      ]
    },
    {
      "parameters": {
        "functionCode": "const items = $input.all();\nconst output = [];\n\nfor (const item of items) {\n  let rawData = item.json;\n\n  // 1. Parse JSON if stringified from Gemini\n  if (rawData.candidates && rawData.candidates[0]?.content?.parts[0]?.text) {\n    let text = rawData.candidates[0].content.parts[0].text;\n    text = text.replace(/```json/g, '').replace(/```/g, '').trim();\n    try { rawData = JSON.parse(text); } catch (e) {}\n  } else if (typeof rawData.body === 'string') {\n    try { rawData = JSON.parse(rawData.body.replace(/```json/g, '').replace(/```/g, '').trim()); } catch (e) {}\n  } else if (rawData.body && typeof rawData.body === 'object') {\n    rawData = rawData.body;\n  }\n\n  // 2. Dynamic metadata generation per execution batch\n  const randomSuffix = Math.floor(1000 + Math.random() * 9000);\n  const companyId = rawData.companyId || `COMP-${randomSuffix}`;\n  const userId = (rawData.userId && rawData.userId !== 'COMP-1-USER-1') \n    ? rawData.userId \n    : `USR-${randomSuffix}`;\n  \n  const role = rawData.role || 'Founder';\n  const behaviorArchetype = rawData.behaviorArchetype || 'Power User';\n  const churnRisk = rawData.churnRisk || 'Low';\n\n  const events = rawData.events || rawData.journey || (Array.isArray(rawData) ? rawData : []);\n  const baseDate = new Date();\n\n  // 3. Map events with incremental dates per simulated day\n  events.forEach((evt, idx) => {\n    const dayOffset = evt.day || idx + 1;\n    \n    // Increment date by the event day number\n    const eventTimestamp = new Date(baseDate.getTime() + (dayOffset * 24 * 60 * 60 * 1000));\n\n    output.push({\n      json: {\n        companyId: companyId,\n        userId: userId,\n        role: role,\n        behaviorArchetype: behaviorArchetype,\n        churnRisk: churnRisk,\n        day: dayOffset,\n        event: evt.event || evt.description || 'Action performed',\n        eventId: `${userId}-EV-${dayOffset}`,\n        simulatedAt: eventTimestamp.toISOString()\n      }\n    });\n  });\n}\n\nreturn output;"
      },
      "id": "e0a7e6fa-5e4a-447f-b391-9ddbf33cdc18",
      "name": "Create Event Records",
      "type": "n8n-nodes-base.function",
      "typeVersion": 1,
      "position": [
        -560,
        16
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 1
          },
          "conditions": [
            {
              "id": "92ba2a47-ce4b-4c7a-8ea4-2e02ab083003",
              "leftValue": "",
              "rightValue": "",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "1823b5e5-121b-46d4-8d03-ab7891628966",
      "name": "Validate Events",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        -320,
        16
      ]
    },
    {
      "parameters": {
        "schema": {
          "__rl": true,
          "mode": "list",
          "value": "public"
        },
        "table": {
          "__rl": true,
          "value": "synthetic_product_events",
          "mode": "list",
          "cachedResultName": "synthetic_product_events"
        },
        "options": {}
      },
      "id": "1902fe3e-bb87-47d5-b5e8-e0b6b68e9b64",
      "name": "Insert into PostgreSQL",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2,
      "position": [
        -64,
        0
      ],
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "options": {}
      },
      "id": "98e0d261-b89d-409b-a824-8468bd8e880a",
      "name": "Export CSV",
      "type": "n8n-nodes-base.spreadsheetFile",
      "typeVersion": 2,
      "position": [
        -64,
        224
      ]
    }
  ],
  "connections": {
    "Manual Trigger": {
      "main": [
        [
          {
            "node": "Set Simulation Config",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set Simulation Config": {
      "main": [
        [
          {
            "node": "Generate Companies",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generate Companies": {
      "main": [
        [
          {
            "node": "Loop Companies",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Loop Companies": {
      "main": [
        [
          {
            "node": "Generate User Personas",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generate User Personas": {
      "main": [
        [
          {
            "node": "Loop Users",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Loop Users": {
      "main": [
        [
          {
            "node": "Generate User Journey",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generate User Journey": {
      "main": [
        [
          {
            "node": "Parse Journey JSON",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Journey JSON": {
      "main": [
        [
          {
            "node": "Create Event Records",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create Event Records": {
      "main": [
        [
          {
            "node": "Validate Events",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Validate Events": {
      "main": [
        [
          {
            "node": "Insert into PostgreSQL",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Export CSV",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Insert into PostgreSQL": {
      "main": [
        [
          {
            "node": "Loop Users",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Export CSV": {
      "main": [
        [
          {
            "node": "Loop Users",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1",
    "binaryMode": "separate",
    "availableInMCP": false
  },
  "versionId": "957d17f2-1117-494a-a4c8-33ad96456f9c",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "nodeGroups": [],
  "id": "htDlDdWqeqcVGFM8",
  "tags": []
}