{
  "name": "Call Center Improvement Engine",
  "nodes": [
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "improvement",
        "responseMode": "lastNode",
        "options": {}
      },
      "id": "a1b2c3d4-0001-0001-0001-a1b2c3d40001",
      "name": "Improvement Webhook",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 1,
      "position": [
        200,
        300
      ]
    },
    {
      "parameters": {
        "mode": "runOnceForAllItems",
        "language": "javaScript",
        "jsCode": "// \u2500\u2500 Step 1: Build the failure-analysis prompt \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// NOTE: n8n is the required improvement orchestration path for this repo.\n// This prompt must produce output that passes the same validation rules\n// as the local ImprovementEngine in agent/improvement_engine.py.\nconst body = $input.first().json.body;\nconst transcripts = body.transcripts.join('\\n\\n---\\n\\n');\nconst metrics = body.metrics;\nconst script = body.current_script;\n\n// Derive allowed keys from the actual script \u2014 never hard-code handler names.\n// This ensures the constraint stays correct when handlers are added or renamed.\nconst allowedKeys = Object.keys(script.objection_handlers || {}).join(' OR ');\n\nconst analysisPrompt = `You are a senior sales coach analyzing failed sales call transcripts.\\n\\nTRANSCRIPTS:\\n${transcripts}\\n\\nAGGREGATE METRICS:\\n- Success rate: ${(metrics.success_rate * 100).toFixed(0)}%\\n- Calls analyzed: ${metrics.calls_analyzed}\\n- Common failure: ${metrics.common_failure}\\n- Avg score: ${(metrics.avg_score * 100).toFixed(0)}%\\n\\nIdentify the single most impactful script weakness that caused these failures.\\n\\nCRITICAL: \"failed_section\" MUST be one of these exact keys from the current script:\\n${allowedKeys}\\nDo NOT invent new handler names. Do NOT use values like \"close\", \"pitch\", or any key not in the list above.\\nPick the closest existing key from the list.\\n\\nRespond ONLY with valid JSON (no markdown, no extra text):\\n{\\n  \"failure_moment\": \"brief description of exact turn where call was lost\",\\n  \"root_cause\": \"one-sentence root cause\",\\n  \"failed_section\": \"one of: ${allowedKeys}\",\\n  \"specific_weakness\": \"what the agent said that failed\",\\n  \"improvement_direction\": \"concrete direction for improvement (include: ROI example, risk reversal, urgency hook)\",\\n  \"confidence\": \"high OR medium OR low\"\\n}`;\n\nreturn [{\n  json: {\n    api_key: body.api_key || $env.ANTHROPIC_API_KEY || '',\n    model: 'claude-haiku-4-5-20251001',\n    current_script: script,\n    metrics: metrics,\n    analysis_request: {\n      model: 'claude-haiku-4-5-20251001',\n      max_tokens: 600,\n      system: 'You are a sales training expert. Return only valid JSON.',\n      messages: [{ role: 'user', content: analysisPrompt }]\n    }\n  }\n}];"
      },
      "id": "a1b2c3d4-0002-0002-0002-a1b2c3d40002",
      "name": "Build Analysis Prompt",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        440,
        300
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.anthropic.com/v1/messages",
        "authentication": "none",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "x-api-key",
              "value": "={{ $json.api_key }}"
            },
            {
              "name": "anthropic-version",
              "value": "2023-06-01"
            },
            {
              "name": "content-type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "contentType": "raw",
        "rawContentType": "application/json",
        "body": "={{ JSON.stringify($json.analysis_request) }}",
        "options": {}
      },
      "id": "a1b2c3d4-0003-0003-0003-a1b2c3d40003",
      "name": "LLM: Analyze Failures",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4,
      "position": [
        680,
        300
      ]
    },
    {
      "parameters": {
        "mode": "runOnceForAllItems",
        "language": "javaScript",
        "jsCode": "// \u2500\u2500 Step 2: Parse FailureAnalysis + build rewrite prompt \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// NOTE: n8n is the required improvement orchestration path for this repo.\n// The rewrite prompt uses the same structured handler format as ImprovementEngine.\nconst prev = $input.first().json;\n\n// Parse the analysis JSON from the LLM response\nlet analysis;\ntry {\n  let rawText = prev.content[0].text.trim();\n  rawText = rawText.replace(/^```(?:json)?\\s*/m, '').replace(/\\s*```$/m, '');\n  analysis = JSON.parse(rawText);\n} catch(e) {\n  // Fallback if parsing fails \u2014 Python client will validate failed_section\n  analysis = {\n    failure_moment: 'objection not handled',\n    root_cause: 'Weak objection handler \u2014 no value proof or risk reversal',\n    failed_section: 'price',\n    specific_weakness: 'Acknowledged concern but offered no counter-evidence',\n    improvement_direction: 'Add concrete ROI proof point, 30-day free trial offer, urgency hook',\n    confidence: 'medium'\n  };\n}\n\n// Retrieve current_script passed through the chain\nconst currentScript = $('Build Analysis Prompt').first().json.current_script;\nconst apiKey = $('Build Analysis Prompt').first().json.api_key;\nconst section = analysis.failed_section;\nconst nextVersion = (currentScript.version || 1) + 1;\nconst currentHandler = (currentScript.objection_handlers || {})[section] || {};\nconst currentHandlerJson = JSON.stringify(currentHandler, null, 2);\n\nconst rewritePrompt = `You are a sales script optimization expert.\\n\\nCURRENT SCRIPT (v${currentScript.version}):\\n${JSON.stringify(currentScript, null, 2)}\\n\\nFAILURE ANALYSIS:\\n- Weak section: objection_handlers[\"${section}\"]\\n- Current handler: ${currentHandlerJson}\\n- Root cause: ${analysis.root_cause}\\n- Improvement direction: ${analysis.improvement_direction}\\n\\nTASK: Rewrite ONLY the \"${section}\" objection handler. Keep ALL other fields identical \u2014 do not rewrite the script wholesale.\\n\\nThe handler MUST be a JSON object with exactly these three keys:\\n  \"proof_point\"   \u2014 a concrete, data-backed statement (specific ROI figure, savings amount,\\n                    adoption stat, or relevant proof). Be specific \u2014 no vague claims.\\n  \"risk_reversal\" \u2014 removes the perceived risk of saying yes (free trial, no contract,\\n                    cancel anytime, start free, etc.)\\n  \"urgency\"       \u2014 a soft time-based or opportunity-based hook ending with a question\\n\\nReturn the COMPLETE updated script as valid JSON only (no markdown, no extra text):\\n- \"version\": ${nextVersion}\\n- All original sections preserved exactly, only the \"${section}\" handler updated\\n- \"metadata.parent_version\": ${currentScript.version}\\n- \"metadata.improvement_rationale\": one sentence explaining what specific fact was added and why\\n\\nThis JSON will be validated by the Python client (agent/n8n_client.py) against the same\\nrules as the local ImprovementEngine. Return only valid JSON.`;\n\nreturn [{\n  json: {\n    api_key: apiKey,\n    analysis: analysis,\n    current_script: currentScript,\n    rewrite_request: {\n      model: 'claude-haiku-4-5-20251001',\n      max_tokens: 1500,\n      system: 'You are a sales script optimization expert. Return only valid JSON.',\n      messages: [{ role: 'user', content: rewritePrompt }]\n    }\n  }\n}];"
      },
      "id": "a1b2c3d4-0004-0004-0004-a1b2c3d40004",
      "name": "Parse Analysis + Build Rewrite",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        920,
        300
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.anthropic.com/v1/messages",
        "authentication": "none",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "x-api-key",
              "value": "={{ $json.api_key }}"
            },
            {
              "name": "anthropic-version",
              "value": "2023-06-01"
            },
            {
              "name": "content-type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "contentType": "raw",
        "rawContentType": "application/json",
        "body": "={{ JSON.stringify($json.rewrite_request) }}",
        "options": {}
      },
      "id": "a1b2c3d4-0005-0005-0005-a1b2c3d40005",
      "name": "LLM: Rewrite Script",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4,
      "position": [
        1160,
        300
      ]
    },
    {
      "parameters": {
        "mode": "runOnceForAllItems",
        "language": "javaScript",
        "jsCode": "// \u2500\u2500 Step 3: Parse new script + package response \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// NOTE: n8n is the required improvement orchestration path for this repo.\n// We apply the same version-pinning and key-merge rules as ImprovementEngine\n// here so the Python client receives a consistent, validated payload.\nconst llmResponse = $input.first().json;\nconst analysis = $('Parse Analysis + Build Rewrite').first().json.analysis;\nconst currentScript = $('Parse Analysis + Build Rewrite').first().json.current_script;\n\nlet newScript;\ntry {\n  let rawText = llmResponse.content[0].text.trim();\n  rawText = rawText.replace(/^```(?:json)?\\s*/m, '').replace(/\\s*```$/m, '');\n  newScript = JSON.parse(rawText);\n} catch(e) {\n  // Return structured error so Python client raises a clear RuntimeError\n  return [{ json: { error: 'Failed to parse new script: ' + e.message, raw: llmResponse.content[0].text } }];\n}\n\n// Enforce version \u2014 never trust the LLM to increment correctly\nconst nextVersion = (currentScript.version || 1) + 1;\nnewScript.version = nextVersion;\n\n// Ensure metadata exists with required fields\nif (!newScript.metadata) newScript.metadata = {};\nnewScript.metadata.created_at = new Date().toISOString();\nif (!newScript.metadata.parent_version) {\n  newScript.metadata.parent_version = currentScript.version;\n}\n\n// Preserve any top-level keys the LLM dropped (greeting, pitch, close, name, etc.)\nfor (const key of Object.keys(currentScript)) {\n  if (!(key in newScript)) {\n    newScript[key] = currentScript[key];\n  }\n}\n\nreturn [{\n  json: {\n    new_script: newScript,\n    analysis: analysis\n  }\n}];"
      },
      "id": "a1b2c3d4-0006-0006-0006-a1b2c3d40006",
      "name": "Parse New Script",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1400,
        300
      ]
    }
  ],
  "connections": {
    "Improvement Webhook": {
      "main": [
        [
          {
            "node": "Build Analysis Prompt",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Analysis Prompt": {
      "main": [
        [
          {
            "node": "LLM: Analyze Failures",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "LLM: Analyze Failures": {
      "main": [
        [
          {
            "node": "Parse Analysis + Build Rewrite",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Analysis + Build Rewrite": {
      "main": [
        [
          {
            "node": "LLM: Rewrite Script",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "LLM: Rewrite Script": {
      "main": [
        [
          {
            "node": "Parse New Script",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "meta": {
    "templateCredsSetupCompleted": true,
    "description": "Required improvement orchestration path. Receives failed call transcripts from Python, runs a 2-step LLM pipeline (analyze failures \u2192 rewrite weak script section), returns the improved script. The response is validated by agent/n8n_client.py against the same rules as the local ImprovementEngine. Import this workflow, toggle it ON, then run: python3 main.py --n8n"
  },
  "tags": []
}