{
  "name": "Phase 6 - Lead Scoring",
  "nodes": [
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "768a0599-beed-4bfc-b940-b267bb1bba8f",
              "name": "supabaseUrl",
              "value": "https://YOUR_PROJECT_REF.supabase.co",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "7e204228-95a4-444d-b85f-1d9e6e1736ba",
      "name": "Config",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        816,
        2592
      ]
    },
    {
      "parameters": {
        "method": "PATCH",
        "url": "={{ $('Config').item.json.supabaseUrl + '/rest/v1/contacts?id=eq.' + $json.contactId }}",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "supabaseApi",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Prefer",
              "value": "return=minimal"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ { \"lead_band\": $json.lead_band, \"lead_score\": $json.lead_score, \"lead_reasoning\": $json.lead_reasoning } }}",
        "options": {}
      },
      "id": "b49f393e-dea9-4405-a448-51de44030bca",
      "name": "Write Score to Contact",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        2208,
        2592
      ],
      "retryOnFail": true,
      "maxTries": 3,
      "waitBetweenTries": 2000,
      "credentials": {
        "supabaseApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "url": "={{ $('Config').item.json.supabaseUrl + '/rest/v1/meetings?meeting_notes=not.is.null&select=id,meeting_notes,contact_id,contacts(name,role,school_id,schools(name,tier,board))' }}",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "supabaseApi",
        "options": {}
      },
      "id": "1eb3c973-9b4d-40b7-b256-35c2e73999ae",
      "name": "Get Meetings w/ Notes",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1040,
        2592
      ],
      "retryOnFail": true,
      "maxTries": 3,
      "waitBetweenTries": 2000,
      "credentials": {
        "supabaseApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "modelId": {
          "__rl": true,
          "value": "gpt-4o-mini",
          "mode": "list",
          "cachedResultName": "GPT-4O-MINI"
        },
        "responses": {
          "values": [
            {
              "role": "system",
              "content": "You are a lead-scoring assistant for Learn with Leaders. Read the meeting notes and score the lead's readiness to partner.\n\nSignals \u2014 Positive: explicit interest or a concrete next step named; a decision-maker is involved; timeline urgency; strong program fit or large student numbers. Negative: unresolved objections; \"just exploring\"; went quiet or non-committal; poor fit.\n\nBands: hot = ready to move + decision-maker + clear next step; warm = genuinely interested but has blockers (budget, sign-off, timing); cold = low interest or poor fit. Also give a 0-100 score (hot ~70-100, warm ~40-69, cold ~0-39).\n\nReturn ONLY strict JSON \u2014 no markdown, no code fences, no extra text:\n{\"band\":\"hot|warm|cold\",\"score\":<integer>,\"reasoning\":\"<1-2 sentences citing the notes>\"}"
            },
            {
              "content": "={{ $json.userPrompt }}"
            }
          ]
        },
        "builtInTools": {},
        "options": {}
      },
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "typeVersion": 2.3,
      "position": [
        1520,
        2592
      ],
      "id": "a2423c56-9139-4057-98a9-ee88fdd54d72",
      "name": "Message a model",
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {},
      "type": "n8n-nodes-base.manualTrigger",
      "typeVersion": 1,
      "position": [
        576,
        2592
      ],
      "id": "347f5584-a152-4d7b-b6af-67cc2ce56f07",
      "name": "When clicking \u2018Execute workflow\u2019"
    },
    {
      "parameters": {
        "jsCode": "const src = $('Build Scoring').all();\nfunction extractText(r) {\n  if (r == null) return '';\n  if (typeof r === 'string') return r;\n  if (Array.isArray(r.output)) {\n    let acc = '';\n    for (const msg of r.output) {\n      if (msg && Array.isArray(msg.content)) {\n        for (const c of msg.content) { if (c && typeof c.text === 'string') acc += c.text; }\n      } else if (typeof msg === 'string') { acc += msg; }\n      else if (msg && typeof msg.text === 'string') { acc += msg.text; }\n    }\n    if (acc) return acc;\n  }\n  if (typeof r.output_text === 'string') return r.output_text;\n  if (typeof r.output === 'string') return r.output;\n  if (typeof r.content === 'string') return r.content;\n  if (typeof r.text === 'string') return r.text;\n  if (r.message && typeof r.message.content === 'string') return r.message.content;\n  if (r.choices && r.choices[0] && r.choices[0].message && typeof r.choices[0].message.content === 'string') return r.choices[0].message.content;\n  if (Array.isArray(r.content)) return r.content.map(b => (b && b.text) ? b.text : '').join('');\n  return '';\n}\nconst out = [];\nfor (let i = 0; i < items.length; i++) {\n  let t = extractText(items[i].json).trim();\n  t = t.replace(/```json/gi, '').replace(/```/g, '').trim();\n  const m = t.match(/\\{[\\s\\S]*\\}/);\n  let obj = {};\n  try { obj = JSON.parse(m ? m[0] : t); } catch (e) { obj = {}; }\n  const band = (obj.band || '').toString().toLowerCase();\n  const validBand = ['cold','warm','hot'].includes(band) ? band : null;\n  const c = (src[i] && src[i].json) ? src[i].json : {};\n  if (!validBand || !c.contactId) continue;\n  const score = Number(obj.score);\n  out.push({ json: {\n    contactId: c.contactId,\n    lead_band: validBand,\n    lead_score: Number.isFinite(score) ? Math.round(score) : null,\n    lead_reasoning: (obj.reasoning || '').toString()\n  }});\n}\nreturn out;\n"
      },
      "id": "81ab8722-6775-425b-afa2-2a8391f716f8",
      "name": "Extract Score",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1968,
        2592
      ]
    },
    {
      "parameters": {
        "jsCode": "const out = [];\nfor (const item of items) {\n  const m = item.json;\n  const c = Array.isArray(m.contacts) ? (m.contacts[0]||{}) : (m.contacts||{});\n  const s = c && (Array.isArray(c.schools) ? (c.schools[0]||{}) : (c.schools||{})) || {};\n  const userPrompt =\n    \"Score this lead from the meeting notes.\\n\" +\n    \"Contact: \" + (c.name || 'Unknown') + \" (\" + (c.role || 'school contact') + \") at \" + (s.name || 'their school') +\n    (s.tier != null ? \" [Tier \" + s.tier + \"]\" : \"\") + \".\\n\\n\" +\n    \"MEETING NOTES:\\n\" + (m.meeting_notes || '(no notes)') + \"\\n\";\n  out.push({ json: { contactId: m.contact_id, userPrompt: userPrompt } });\n}\nreturn out;\n"
      },
      "id": "66441dd2-e59a-4765-ab22-03266f46639c",
      "name": "Build Scoring",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1264,
        2592
      ]
    }
  ],
  "connections": {
    "When clicking \u2018Execute workflow\u2019": {
      "main": [
        [
          {
            "node": "Config",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Meetings w/ Notes": {
      "main": [
        [
          {
            "node": "Build Scoring",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Scoring": {
      "main": [
        [
          {
            "node": "Message a model",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Score": {
      "main": [
        [
          {
            "node": "Write Score to Contact",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Config": {
      "main": [
        [
          {
            "node": "Get Meetings w/ Notes",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Message a model": {
      "main": [
        [
          {
            "node": "Extract Score",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1"
  }
}