{
  "nodes": [
    {
      "id": "d6a74e08-2432-47ee-895b-76c58b4e81a1",
      "name": "Normalize + Build Options",
      "type": "n8n-nodes-base.code",
      "position": [
        -1232,
        -144
      ],
      "parameters": {
        "jsCode": "// Normalize + Build Options (must return 1 item)\n\nconst raw = $json?.body ?? $json ?? {};\n\n// Accept either direct VP payload OR simple webhook payload\nconst decision_id = raw.decision_id ?? raw.decisionId ?? raw.id ?? \"REQ-0000\";\nconst prompt = raw.prompt ?? raw.title ?? raw.message ?? \"No prompt provided\";\n\nlet options = raw.options;\n\n// If options came in as a string, try to parse (common beginner mistake)\nif (typeof options === \"string\") {\n  try {\n    options = JSON.parse(options);\n  } catch (e) {\n    options = null;\n  }\n}\n\n// Force options to be an array of {id,label}\nif (!Array.isArray(options)) {\n  return [\n    {\n      json: {\n        ok: false,\n        error: \"invalid_options\",\n        hint: \"options must be an array like: [{ id:'a', label:'Option A' }]\",\n        got_type: typeof raw.options,\n        got_value: raw.options,\n      },\n    },\n  ];\n}\n\nconst cleanOptions = options\n  .map((o, idx) => ({\n    id: String(o?.id ?? idx),\n    label: String(o?.label ?? o?.name ?? o?.text ?? `Option ${idx + 1}`),\n  }))\n  .filter((o) => o.label.trim().length > 0);\n\nif (cleanOptions.length < 2) {\n  return [\n    {\n      json: {\n        ok: false,\n        error: \"not_enough_options\",\n        hint: \"Provide at least 2 options.\",\n        got: cleanOptions,\n      },\n    },\n  ];\n}\n\n// Build the exact backend payload\nconst vp_payload = {\n  decision_id,\n  prompt,\n  options: cleanOptions,\n};\n\nreturn [\n  {\n    json: {\n      ok: true,\n      vp_payload,\n    },\n  },\n];"
      },
      "typeVersion": 2
    },
    {
      "id": "8d43eb7e-c6d7-4baf-bf48-b910dee72293",
      "name": "VectorPrime Rank (HTTP)",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -848,
        -144
      ],
      "parameters": {
        "url": "https://vectorprime-kernel-backend.onrender.com/v1/kernel/rank",
        "method": "POST",
        "options": {},
        "jsonBody": "={{ JSON.stringify($json.vp_payload) }}",
        "sendBody": true,
        "sendHeaders": true,
        "specifyBody": "json",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        }
      },
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.3,
      "continueOnFail": true
    },
    {
      "id": "a4689d97-8bca-49dd-bc9d-d018bf168c26",
      "name": "Parse VP Result",
      "type": "n8n-nodes-base.code",
      "position": [
        -656,
        -144
      ],
      "parameters": {
        "jsCode": "// Parse VP Result \u2014 robust for n8n HTTP Request node variations\n\nconst statusCode =\n  $json.statusCode ??\n  $json.status ??\n  null;\n\n// In many n8n configs, the HTTP node returns the JSON body directly as $json\n// In other configs, it returns { body: {...}, statusCode: 200 }\nconst body =\n  $json.body ??\n  $json;\n\n// Decide \u201cok\u201d based on the actual response shape coming back from VectorPrime\nconst ok =\n  (statusCode !== null && statusCode >= 200 && statusCode < 300) ||\n  (body && typeof body === \"object\" && Array.isArray(body.ranking));\n\nreturn [\n  {\n    json: {\n      ok,\n      statusCode,\n      vp: body,\n    },\n  },\n];"
      },
      "typeVersion": 2
    },
    {
      "id": "c068883f-9050-40e8-88d3-283d157e08cb",
      "name": "VP OK?",
      "type": "n8n-nodes-base.if",
      "position": [
        -480,
        -144
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "loose"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "802ee7cd-b166-4d44-8ad5-0ba93e31a032",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              },
              "leftValue": "={{$json.ok}}",
              "rightValue": "true"
            }
          ]
        },
        "looseTypeValidation": "={{ true }}"
      },
      "typeVersion": 2.2
    },
    {
      "id": "f50f17cf-8cd3-41cd-a245-84c972c929ab",
      "name": "Respond Error",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        880,
        48
      ],
      "parameters": {
        "options": {
          "responseCode": 400,
          "responseHeaders": {
            "entries": [
              {
                "name": "Content-Type",
                "value": "application/json"
              }
            ]
          }
        }
      },
      "typeVersion": 1.1
    },
    {
      "id": "3e8b5857-ea14-4e28-a851-c9ff351fe241",
      "name": "Create Audit Log",
      "type": "n8n-nodes-base.code",
      "position": [
        416,
        -160
      ],
      "parameters": {
        "jsCode": "// Create an audit log object (great for reviewers + real-world usage).\nconst now = new Date().toISOString();\n\nreturn [{\n  json: {\n    ok: true,\n    audited_at: now,\n    decision_id: $json.decision_id,\n    engine_used: $json.engine_used,\n    top_id: $json.top_id,\n    top_probability: $json.top_probability,\n    ranking: $json.ranking,\n    request: $json.request,\n    route_action: $json.route_action ?? $json.route,\n    // Keep a copy of the VectorPrime payload + raw response for debugging.\n    vp_payload: $json.vp_payload,\n    vp_raw: $json.vp_raw,\n    // Optional: demo payloads\n    slack_message: $json.slack_message,\n    backlog_record: $json.backlog_record,\n    email: ($json.email_to ? { to: $json.email_to, subject: $json.email_subject, body: $json.email_body } : undefined),\n  }\n}];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "3bccd42c-62d2-428e-af4a-65aa87dca227",
      "name": "Respond Success",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        880,
        -160
      ],
      "parameters": {
        "options": {
          "responseCode": 200,
          "responseHeaders": {
            "entries": [
              {
                "name": "Content-Type",
                "value": "application/json"
              }
            ]
          }
        }
      },
      "typeVersion": 1.1
    },
    {
      "id": "bb7022e2-ab9b-4e03-a832-3d2297256888",
      "name": "Webhook1",
      "type": "n8n-nodes-base.webhook",
      "position": [
        -1440,
        -144
      ],
      "parameters": {
        "path": "bf574f34-f8be-44dd-a154-206e4388d1d7",
        "options": {
          "responseHeaders": {
            "entries": [
              {
                "name": "Content-Type",
                "value": "application/json"
              }
            ]
          }
        },
        "httpMethod": "POST",
        "responseMode": "responseNode"
      },
      "typeVersion": 2.1
    },
    {
      "id": "026a302f-f0ee-48b6-a4c8-d2f81d8761d1",
      "name": "Slack Alert Payload",
      "type": "n8n-nodes-base.set",
      "position": [
        -208,
        -160
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 3.4
    },
    {
      "id": "21e3b2e7-c1e7-418b-afc0-2bf3ddc2989b",
      "name": "Backlog Record",
      "type": "n8n-nodes-base.set",
      "position": [
        32,
        -160
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 3.4
    },
    {
      "id": "5e230414-cca8-431f-90a5-f8239c086095",
      "name": "Email Draft",
      "type": "n8n-nodes-base.set",
      "position": [
        208,
        -160
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 3.4
    },
    {
      "id": "8e499f17-70e9-467c-8ae5-133e66ae5410",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1760,
        -1088
      ],
      "parameters": {
        "width": 1456,
        "height": 480,
        "content": "## What this template does\nThis workflow helps you triage incoming tickets/incidents by returning a prioritized list of \u201cnext actions\u201d. It receives a JSON payload via a webhook, validates it, calls an external ranking endpoint, then responds with a clean audit-friendly JSON result. You can keep the workflow as-is (webhook \u2192 ranked output), or connect the optional \u201cSlack / backlog / email draft\u201d steps to your own tools later.\n\n## How it works\n1) Webhook receives a ticket/incident JSON payload.\n2) Normalize step validates the payload and builds `vp_payload` (decision_id, prompt, options[]).\n3) HTTP Request sends `vp_payload` to the ranking endpoint.\n4) Parse step validates the response and sets `ok=true/false`.\n5) If ok=true \u2192 optional payload builders + audit log \u2192 respond success.\n6) If ok=false \u2192 respond with a safe error JSON.\n\n## Setup steps\n1) Create an API key at https://vectorprime.tech\n2) In n8n \u2192 Credentials, create \u201cHeader Auth\u201d:\n   - Header Name: Authorization\n   - Header Value: Bearer vp_YOUR_KEY_HERE\n3) Open \u201cVectorPrime Rank (HTTP)\u201d and select that credential.\n4) Test using the Production URL from Webhook1."
      },
      "typeVersion": 1
    },
    {
      "id": "eca935c6-c75f-4b02-9bee-fb560e32fcdf",
      "name": "Edit Fields",
      "type": "n8n-nodes-base.set",
      "position": [
        640,
        -160
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "55386b15-7ba5-48e1-8ceb-4e8b275fbba8",
              "name": "request",
              "type": "object",
              "value": "={{$node[\"Webhook1\"].json.body}}"
            },
            {
              "id": "7a6c880f-a5bd-4959-83fb-3c115a694c91",
              "name": "vp",
              "type": "object",
              "value": "={{$node[\"Parse VP Result\"].json}}"
            },
            {
              "id": "1d2e098e-361c-4315-8376-7975583d62d7",
              "name": "audited_at",
              "type": "string",
              "value": "={{$node[\"Create Audit Log\"].json.audited_at}}"
            },
            {
              "id": "5f0e7be6-e72f-4d36-bed3-eaf95c8f6466",
              "name": "ok",
              "type": "boolean",
              "value": true
            },
            {
              "id": "451c3c6e-9530-49e6-bc08-ebb3688e6bed",
              "name": "decision_id",
              "type": "string",
              "value": "={{$node[\"Webhook1\"].json.body.decision_id}}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "a74f9d02-bcb3-4604-851c-4062026be6c4",
      "name": "Normalize OK?",
      "type": "n8n-nodes-base.if",
      "position": [
        -1056,
        -144
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 3,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "83878efe-9a4b-4587-8f07-810e200ebfc7",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              },
              "leftValue": "={{$json.ok}}",
              "rightValue": ""
            }
          ]
        },
        "looseTypeValidation": "={{ false }}"
      },
      "typeVersion": 2.3
    },
    {
      "id": "e3462824-2f8f-4f21-b071-6228a55926a2",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1456,
        -432
      ],
      "parameters": {
        "color": 7,
        "width": 150,
        "height": 256,
        "content": "## Input (Webhook)\nReceives POST JSON with:\n- decision_id (string)\n- prompt (string)\n- options (array, 2+ items)"
      },
      "typeVersion": 1
    },
    {
      "id": "cb1b7d64-3bc4-49ad-a7ed-2e08154cda1d",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1232,
        -384
      ],
      "parameters": {
        "color": 7,
        "width": 278,
        "height": 208,
        "content": "## Normalize\nValidates input and builds `vp_payload`.\nReturns error JSON if options are missing/invalid."
      },
      "typeVersion": 1
    },
    {
      "id": "630ed7bf-51e2-43c8-bc7c-ceccfc4a4249",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -880,
        -384
      ],
      "parameters": {
        "color": 7,
        "width": 150,
        "height": 224,
        "content": "## Rank (HTTP)\nCalls the ranking endpoint using your Header Auth credential.\nBody must be JSON so options stay an array."
      },
      "typeVersion": 1
    },
    {
      "id": "4c4a3d9e-2de8-4440-ae61-b40b735d68a6",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -672,
        -400
      ],
      "parameters": {
        "color": 7,
        "width": 294,
        "height": 224,
        "content": "## Validate + Route\nParses response and routes:\n- ok=true \u2192 success path\n- ok=false \u2192 error response"
      },
      "typeVersion": 1
    },
    {
      "id": "a9877458-9efa-4551-ad85-16337dcc1fb6",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -224,
        -400
      ],
      "parameters": {
        "color": 7,
        "width": 1200,
        "height": 416,
        "content": "## Output + Audit\nBuilds optional payload drafts, writes audit log, and responds with JSON."
      },
      "typeVersion": 1
    },
    {
      "id": "cb23a11e-d142-4cd3-9170-24210664d0dc",
      "name": "Sticky Note6",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -880,
        -576
      ],
      "parameters": {
        "color": "#E41B1B",
        "width": 150,
        "height": 192,
        "content": "\u26a0\ufe0f Warning\nDo not send options via \u201cFields below\u201d.\nSend the body as JSON so options remain an array (prevents 422 errors)."
      },
      "typeVersion": 1
    }
  ],
  "connections": {
    "VP OK?": {
      "main": [
        [
          {
            "node": "Slack Alert Payload",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Respond Error",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Webhook1": {
      "main": [
        [
          {
            "node": "Normalize + Build Options",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Edit Fields": {
      "main": [
        [
          {
            "node": "Respond Success",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Email Draft": {
      "main": [
        [
          {
            "node": "Create Audit Log",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalize OK?": {
      "main": [
        [
          {
            "node": "VectorPrime Rank (HTTP)",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Respond Error",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Backlog Record": {
      "main": [
        [
          {
            "node": "Email Draft",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse VP Result": {
      "main": [
        [
          {
            "node": "VP OK?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create Audit Log": {
      "main": [
        [
          {
            "node": "Edit Fields",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Slack Alert Payload": {
      "main": [
        [
          {
            "node": "Backlog Record",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "VectorPrime Rank (HTTP)": {
      "main": [
        [
          {
            "node": "Parse VP Result",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalize + Build Options": {
      "main": [
        [
          {
            "node": "Normalize OK?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}