{
  "name": "medback",
  "nodes": [
    {
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "// 1. Try to find the text in several common n8n AI output paths\nconst rawText = $json.text || \n                $json.response?.generations?.[0]?.[0]?.text || \n                $json.output;\n\nif (!rawText) {\n  throw new Error(\"Could not find medical text in the input. Check the previous node's output.\");\n}\n\n// 2. Clean out Markdown JSON wrappers (```json ... ```)\nconst cleanJson = rawText.replace(/```json/g, \"\").replace(/```/g, \"\").trim();\n\n// 3. Return as a clean JSON object\ntry {\n  return JSON.parse(cleanJson);\n} catch (e) {\n  return { error: \"Failed to parse JSON\", originalText: rawText };\n}"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        400,
        -144
      ],
      "id": "e40cdee6-4d67-4bea-ad1c-aa8a81a9c2bc",
      "name": "Code in JavaScript"
    },
    {
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "// Format raw extraction for Draft (patient-friendly, document-specific only)\nconst raw = $json;\nif (raw.error) return { json: { text: JSON.stringify(raw) } };\nconst prompt = `You are a medical assistant. Convert this raw medical extraction into a patient-friendly checklist. Rules:\n- Use ONLY information from the extraction below. Do NOT add generic advice (e.g. no \"watch for new symptoms\" unless the note says so).\n- Use 6th-grade reading level. Translate symbols like '<' and '>' into plain English (e.g. \"worse when...\", \"better when...\").\n- If you see ICD-10-GM or OPS codes, give a brief plain-language explanation with the code in parentheses.\n- Checklist: one item per diagnosis, one per medication (with dosage/frequency if present), one per warning or aggravating/relieving factor from the note. Be specific, not vague.\n- audio_script: 2-3 short, warm sentences summarizing only what is in the extraction, for text-to-speech.\n\nRaw extraction:\n${JSON.stringify(raw, null, 2)}\n\nReturn ONLY valid JSON (no markdown, no backticks):\n{\"checklist\":[{\"text\":\"string\",\"checked\":true|false}],\"audio_script\":\"string\"}`;\nreturn { json: { text: prompt } };"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        520,
        -144
      ],
      "id": "format-draft-prompt",
      "name": "Format Draft Prompt"
    },
    {
      "parameters": {
        "promptType": "define",
        "text": "={{ $json.text }}",
        "messages": {
          "messageValues": [
            {
              "type": "HumanMessagePromptTemplate",
              "messageType": "text",
              "message": "={{ $json.text }}"
            }
          ]
        },
        "batching": {}
      },
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "typeVersion": 1.9,
      "position": [
        640,
        -144
      ],
      "id": "draft-chain-id",
      "name": "Draft Chain"
    },
    {
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "// Parse Draft output to checklist + audio_script\nconst rawText = $json.text || $json.response?.generations?.[0]?.[0]?.text || $json.output || '';\nconst clean = rawText.replace(/```json/g, '').replace(/```/g, '').trim();\nlet draft = { checklist: [], audio_script: '' };\ntry {\n  draft = JSON.parse(clean);\n} catch (e) {\n  draft = { checklist: [{ text: 'Could not parse draft', checked: false }], audio_script: 'Please see the checklist for details.' };\n}\nif (!Array.isArray(draft.checklist)) draft.checklist = [];\nif (!draft.audio_script) draft.audio_script = draft.checklist.map(c => c.text).join('. ') || 'See checklist.';\nreturn { json: draft };"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        760,
        -144
      ],
      "id": "parse-draft-id",
      "name": "Parse Draft"
    },
    {
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "// Format prompt for Guardian (hallucination check)\nconst raw = $('Code in JavaScript').first().json;\nconst draft = $json;\nif (raw.error || !draft.checklist) return { json: { text: 'Skip' } };\nconst prompt = `You are a medical safety auditor. Compare the ORIGINAL extraction with the DRAFT summary. Did the draft hallucinate (add meds, change dosages, add/remove diagnoses)? Return ONLY valid JSON: {\"pass\": true|false, \"reason\": \"brief explanation\"}\n\nORIGINAL:\n${JSON.stringify(raw, null, 2)}\n\nDRAFT:\n${JSON.stringify(draft, null, 2)}`;\nreturn { json: { text: prompt } };"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        760,
        80
      ],
      "id": "format-guardian-prompt",
      "name": "Format Guardian Prompt"
    },
    {
      "parameters": {
        "promptType": "define",
        "text": "={{ $json.text }}",
        "messages": {
          "messageValues": [
            {
              "type": "HumanMessagePromptTemplate",
              "messageType": "text",
              "message": "={{ $json.text }}"
            }
          ]
        },
        "batching": {}
      },
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "typeVersion": 1.9,
      "position": [
        880,
        80
      ],
      "id": "guardian-chain-id",
      "name": "Guardian Chain"
    },
    {
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "// Parse Guardian output for pass/fail\nconst rawText = $json.text || $json.response?.generations?.[0]?.[0]?.text || $json.output || '';\nconst clean = rawText.replace(/```json/g, '').replace(/```/g, '').trim();\nlet result = { pass: true, reason: '' };\ntry {\n  const parsed = JSON.parse(clean);\n  result = { pass: parsed.pass === true, reason: parsed.reason || '' };\n} catch (e) {}\nreturn { json: result };"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1000,
        80
      ],
      "id": "parse-guardian-id",
      "name": "Parse Guardian"
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "guardian-fail",
              "leftValue": "={{ $json.pass }}",
              "rightValue": false,
              "operator": {
                "type": "boolean",
                "operation": "equals"
              }
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        1120,
        80
      ],
      "id": "if-guardian-fail",
      "name": "IF Guardian Fail"
    },
    {
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "// Anonymize: strip PII before sending to Discord\nconst draft = $('Parse Draft').first().json;\nconst guardian = $('Parse Guardian').first().json;\nconst raw = $('Code in JavaScript').first().json;\nconst anonymized = {\n  diagnosis: raw.Diagnosis || '(redacted)',\n  medCount: Array.isArray(raw.Medications) ? raw.Medications.length : 0,\n  warningCount: (raw.Warning_Signs || raw.WarningSigns || raw['Warning Signs'] || []).length,\n  guardianFail: guardian.pass === false,\n  reason: guardian.reason || ''\n};\nreturn { json: anonymized };"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1240,
        40
      ],
      "id": "anonymize-id",
      "name": "Anonymize"
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://discord.com/api/webhooks/YOUR_WEBHOOK_ID/YOUR_WEBHOOK_TOKEN",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"content\": \"ClearMED: Hallucination detected. Review needed.\",\n  \"embeds\": [{\n    \"title\": \"Guardian Check Failed\",\n    \"description\": \"Reason: {{ $json.reason }}\",\n    \"fields\": [\n      {\"name\": \"Diagnosis\", \"value\": \"{{ $json.diagnosis }}\"},\n      {\"name\": \"Medications count\", \"value\": \"{{ $json.medCount }}\"},\n      {\"name\": \"Warnings count\", \"value\": \"{{ $json.warningCount }}\"}\n    ]\n  }]\n}"
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.3,
      "position": [
        1360,
        40
      ],
      "id": "discord-webhook-id",
      "name": "Discord Webhook"
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.elevenlabs.io/v1/text-to-speech/Qy4b2JlSGxY7I9M9Bqxb",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"text\": \"{{ $('Parse Draft').first().json.audio_script || 'Please see the checklist for details.' }}\",\n  \"model_id\": \"eleven_monolingual_v1\",\n  \"voice_settings\": {\n    \"stability\": 0.5,\n    \"similarity_boost\": 0.75\n  }\n}",
        "options": {
          "response": {
            "response": {
              "responseFormat": "file",
              "outputPropertyName": "audio"
            }
          }
        }
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.3,
      "position": [
        624,
        -144
      ],
      "id": "d84368ad-2681-4b7d-890e-2e98f943539e",
      "name": "HTTP Request",
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "const fallback = { summary: 'See checklist.', checklist: [{ text: 'No items', checked: false }], audio_base64: null, verifiedSafe: false };\ntry {\n  let draft = { checklist: [], audio_script: '' };\n  let guardian = { pass: true, reason: '' };\n  try { draft = $('Parse Draft').first().json || draft; } catch (_) {}\n  try { guardian = $('Parse Guardian').first().json || guardian; } catch (_) {}\n  const checklist = Array.isArray(draft.checklist) ? draft.checklist : [{ text: 'No items', checked: false }];\n  const summary = (draft.checklist && draft.checklist[0] && draft.checklist[0].text) ? draft.checklist[0].text : 'See checklist.';\n  let audio_base64 = null;\n  try {\n    const buffer = await this.helpers.getBinaryDataBuffer(0, 'audio');\n    if (buffer && buffer.length) audio_base64 = buffer.toString('base64');\n  } catch (_) {}\n  const verifiedSafe = guardian.pass === true;\n  return { json: { summary, checklist, audio_base64, verifiedSafe } };\n} catch (e) {\n  return { json: { ...fallback, summary: 'Processing error' } };\n}"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        736,
        -144
      ],
      "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
      "name": "Prepare Response"
    },
    {
      "parameters": {
        "respondWith": "firstIncomingItem",
        "options": {}
      },
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.5,
      "position": [
        848,
        -144
      ],
      "id": "8f8fdf42-eca5-4613-83ee-38be2ec5a043",
      "name": "Respond to Webhook"
    },
    {
      "parameters": {
        "promptType": "define",
        "text": "Look at the attached medical document (German doctor's note). OCR the text with 100% accuracy. Extract the following into a JSON format:\n\nDiagnosis (include ICD-10-GM codes if present, e.g. G43.0; preserve codes exactly)\n\nMedications (Name, Dosage, Frequency)\n\nWarning Signs (include OPS or other procedure codes if present)\n\nReturn ONLY raw JSON. No conversational filler. Do not include patient name, address, or other identifiers.",
        "messages": {
          "messageValues": [
            {
              "type": "HumanMessagePromptTemplate",
              "messageType": "imageBinary",
              "binaryPropertyName": "data"
            }
          ]
        },
        "batching": {}
      },
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "typeVersion": 1.9,
      "position": [
        48,
        -144
      ],
      "id": "c8ee5c51-5131-4f54-911a-57ed327dbe90",
      "name": "Basic LLM Chain"
    },
    {
      "parameters": {
        "options": {}
      },
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "typeVersion": 1,
      "position": [
        48,
        48
      ],
      "id": "c3c60620-4e90-475e-92ac-e3269904075f",
      "name": "OpenAI Chat Model",
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "scan",
        "responseMode": "responseNode",
        "options": {}
      },
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2.1,
      "position": [
        -352,
        -144
      ],
      "id": "0d96ce48-121b-4a7b-9196-19be837e347e",
      "name": "Webhook1"
    },
    {
      "parameters": {
        "jsCode": "// Get the image from webhook form data\nconst imageFile = $input.item.binary.image || $input.item.binary.data;\n\nif (!imageFile) {\n  throw new Error('No image found in webhook data');\n}\n\n// Pass through the binary data\nreturn {\n  json: {},\n  binary: {\n    data: imageFile\n  }\n};"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -144,
        -144
      ],
      "id": "5cc0118d-e569-497f-b890-1ff03c9fdf2e",
      "name": "Code in JavaScript1"
    }
  ],
  "connections": {
    "Code in JavaScript": {
      "main": [
        [
          {
            "node": "Format Draft Prompt",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format Draft Prompt": {
      "main": [
        [
          {
            "node": "Draft Chain",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Draft Chain": {
      "main": [
        [
          {
            "node": "Parse Draft",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Draft": {
      "main": [
        [
          {
            "node": "Format Guardian Prompt",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format Guardian Prompt": {
      "main": [
        [
          {
            "node": "Guardian Chain",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Guardian Chain": {
      "main": [
        [
          {
            "node": "Parse Guardian",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "IF Guardian Fail": {
      "main": [
        [
          {
            "node": "Anonymize",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Anonymize": {
      "main": [
        [
          {
            "node": "Discord Webhook",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "HTTP Request": {
      "main": [
        [
          {
            "node": "Prepare Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Guardian": {
      "main": [
        [
          {
            "node": "IF Guardian Fail",
            "type": "main",
            "index": 0
          },
          {
            "node": "HTTP Request",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare Response": {
      "main": [
        [
          {
            "node": "Respond to Webhook",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Basic LLM Chain": {
      "main": [
        [
          {
            "node": "Code in JavaScript",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpenAI Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "Basic LLM Chain",
            "type": "ai_languageModel",
            "index": 0
          }
        ],
        [
          {
            "node": "Draft Chain",
            "type": "ai_languageModel",
            "index": 0
          }
        ],
        [
          {
            "node": "Guardian Chain",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Webhook1": {
      "main": [
        [
          {
            "node": "Code in JavaScript1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code in JavaScript1": {
      "main": [
        [
          {
            "node": "Basic LLM Chain",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1",
    "availableInMCP": false
  },
  "versionId": "44fa9b8a-065b-4067-827c-40ddee0b8acf",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "id": "meB2jd3Qo_DmgNrTa-tDP",
  "tags": []
}