{
  "id": "iZz4JefE5d45kAuw",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "AI-Powered Multilingual Lab Report Translator for Patients",
  "tags": [],
  "nodes": [
    {
      "id": "0aff9bf6-7566-40e7-b3ab-f46b7e384776",
      "name": "Overview",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -2512,
        -720
      ],
      "parameters": {
        "width": 688,
        "height": 540,
        "content": "## \ud83c\udfe5 AI Lab Report Summariser & Translator\n\n### How it works\nWhen a new PDF is uploaded to a watched Google Drive folder, this workflow downloads it, extracts the text, and uses GPT-4o-mini to parse structured data \u2014 patient name, report type, findings, and detected language. It then produces a plain-English explanation of every test result (no jargon), translates that into the patient's preferred language, and emails the final summary as a formatted Markdown attachment via Gmail.\n\nError alerts at three key AI steps fire to Slack so issues never go unnoticed.\n\n### Setup steps\n1. **Google Drive** \u2014 Connect OAuth2 and update the `folderToWatch` ID to your own lab reports folder.\n2. **OpenAI** \u2014 Add your API key. All three AI nodes use `gpt-4o-mini` by default.\n3. **Gmail** \u2014 Connect OAuth2 and update the `sendTo` field to use a dynamic patient email expression instead of the hardcoded placeholder.\n4. **Slack** \u2014 Connect OAuth2 and set the correct channel ID in all three error alert nodes.\n5. **Test** \u2014 Upload a sample PDF to your Drive folder and run the workflow. Check the Gmail output and Slack for any errors.\n\n> \u26a0\ufe0f This workflow processes medical documents. Ensure your Drive folder, Gmail account, and any storage comply with your organisation's data privacy policy."
      },
      "typeVersion": 1
    },
    {
      "id": "ede5b689-08fa-469c-a911-e90899d06808",
      "name": "Section: File Ingestion",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1616,
        -272
      ],
      "parameters": {
        "color": 7,
        "width": 620,
        "height": 760,
        "content": "## \ud83d\udcc2 File Ingestion\n\nPolls a Google Drive folder every minute for new PDF uploads. When one appears, it downloads the file and extracts all raw text \u2014 ready to pass into the AI pipeline."
      },
      "typeVersion": 1
    },
    {
      "id": "48de0ca9-f14d-4f0c-b606-7abc01923b6a",
      "name": "Section: AI Extraction & Parsing",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -976,
        -240
      ],
      "parameters": {
        "color": 7,
        "width": 592,
        "height": 732,
        "content": "## \ud83e\udde0 AI Extraction & Parsing\n\nGPT-4o-mini reads the raw PDF text and returns structured JSON \u2014 patient details, report type, lab name, test findings, and the detected language. A robust code node handles malformed responses and strips markdown fences before parsing."
      },
      "typeVersion": 1
    },
    {
      "id": "18da7b21-5f96-4833-8993-559cf4b99789",
      "name": "Section: Plain-Language Summary",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -352,
        -320
      ],
      "parameters": {
        "color": 7,
        "width": 592,
        "height": 930,
        "content": "## \ud83d\udcac Plain-Language Summary\n\nConverts clinical findings into a warm, jargon-free explanation any patient can understand. Each result gets a status (Normal / Borderline / Abnormal) and an everyday analogy. Output is clean Markdown with a 'What to do next' section."
      },
      "typeVersion": 1
    },
    {
      "id": "07444db6-96c7-4f6c-9f78-300568826f41",
      "name": "Section: Translation & Report Assembly",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        272,
        -368
      ],
      "parameters": {
        "color": 7,
        "width": 528,
        "height": 1042,
        "content": "## \ud83c\udf0d Translation & Report Assembly\n\nIf the patient's preferred language differs from English, GPT-4o-mini translates the full summary while preserving Markdown formatting. A code node then assembles the final report with header metadata and converts it to a downloadable `.md` file."
      },
      "typeVersion": 1
    },
    {
      "id": "c92661b4-9de2-4876-b3a8-fb05f52749b4",
      "name": "Section: Patient Email Delivery",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        848,
        -304
      ],
      "parameters": {
        "color": 7,
        "width": 440,
        "height": 824,
        "content": "## \ud83d\udce7 Patient Email Delivery\n\nSends the formatted HTML email with the Markdown summary attached. The email references the patient's name, report type, and date. Update the `sendTo` field to use `{{ $json.patient_email }}` once your data reliably contains patient emails."
      },
      "typeVersion": 1
    },
    {
      "id": "a184350b-8b04-4924-9d14-7af8138807c2",
      "name": "Credentials & Security",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1408,
        384
      ],
      "parameters": {
        "color": 3,
        "width": 320,
        "height": 256,
        "content": "## \ud83d\udd10 Credentials & Security\n\nUse OAuth2 for Google Drive, Gmail, and Slack. Use API key auth for OpenAI. Never hardcode personal emails or folder IDs \u2014 replace with environment variables or n8n credential manager. Restrict Drive folder access to authorised staff only."
      },
      "typeVersion": 1
    },
    {
      "id": "c4a6a1f6-e4b2-42dc-969a-ed7e85c9233b",
      "name": "Google Drive: Watch for New Lab Report",
      "type": "n8n-nodes-base.googleDriveTrigger",
      "position": [
        -1600,
        64
      ],
      "parameters": {
        "event": "fileCreated",
        "options": {},
        "pollTimes": {
          "item": [
            {
              "mode": "everyMinute"
            }
          ]
        },
        "triggerOn": "specificFolder",
        "folderToWatch": {
          "__rl": true,
          "mode": "list",
          "value": "YOUR_GOOGLE_DRIVE_FOLDER_ID",
          "cachedResultUrl": "https://drive.google.com/drive/folders/YOUR_GOOGLE_DRIVE_FOLDER_ID",
          "cachedResultName": "Lab Reports Folder"
        }
      },
      "credentials": {
        "googleDriveOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "25581f38-aa1a-4b70-bc2c-60619ed4ed9a",
      "name": "Google Drive: Download PDF",
      "type": "n8n-nodes-base.googleDrive",
      "position": [
        -1344,
        64
      ],
      "parameters": {
        "fileId": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $json.id }}"
        },
        "options": {},
        "operation": "download"
      },
      "credentials": {
        "googleDriveOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 3
    },
    {
      "id": "37483d18-481b-45fa-85ad-ba5de0941c80",
      "name": "Extract Text: Parse PDF to Raw Text",
      "type": "n8n-nodes-base.extractFromFile",
      "position": [
        -1120,
        64
      ],
      "parameters": {
        "options": {},
        "operation": "pdf"
      },
      "typeVersion": 1
    },
    {
      "id": "dfc5d212-d6f7-46d0-b8bd-17a3ef3500e3",
      "name": "AI: Extract Structure + Detect Language",
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "onError": "continueErrorOutput",
      "position": [
        -912,
        64
      ],
      "parameters": {
        "modelId": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4o-mini",
          "cachedResultName": "GPT-4O-MINI"
        },
        "options": {
          "temperature": 0.1
        },
        "messages": {
          "values": [
            {
              "role": "system",
              "content": "You are a medical document analyst. Your job is to extract structured data from raw lab report text and detect the patient's language and preferred language if stated.\n\nFrom the raw text, extract and return ONLY a JSON object (no markdown, no explanation):\n{\n  \"patient_name\": \"full name or Unknown\",\n  \"patient_email\": \"email or Unknown\",\n  \"report_date\": \"date or Unknown\",\n  \"report_type\": \"e.g. Blood Test, MRI, Urine Analysis\",\n  \"lab_name\": \"lab or hospital name or Unknown\",\n  \"detected_language\": \"language of the report e.g. English, Hindi, French, German\",\n  \"preferred_language\": \"patient preferred language if mentioned, else same as detected_language\",\n  \"raw_findings\": \"all test results, values, reference ranges as-is from the report\"\n}"
            },
            {
              "content": "=Extract structured data from this lab report:\n\n{{ $json.text }}"
            }
          ]
        }
      },
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.4
    },
    {
      "id": "4be0b1f5-b916-4931-abfd-65b8bfebf09b",
      "name": "Code: Parse & Sanitise Extraction JSON",
      "type": "n8n-nodes-base.code",
      "position": [
        -512,
        48
      ],
      "parameters": {
        "jsCode": "// Get raw response from GPT\nlet raw = $('AI: Extract Structure + Detect Language').item.json.message.content;\n\n// Step 1: Strip markdown code fences if GPT wrapped in ```json ... ```\nraw = raw.replace(/^```json\\s*/i, '').replace(/^```\\s*/i, '').replace(/```\\s*$/i, '').trim();\n\n// Step 2: Sanitize bad control characters inside JSON string values\nraw = raw\n  .replace(/\\t/g, ' ')\n  .replace(/\\r\\n/g, ' ')\n  .replace(/\\r/g, ' ')\n  .replace(/\\n/g, ' ')\n  .replace(/[\\x00-\\x1F\\x7F]/g, ' ');\n\n// Step 3: Try to parse cleanly\nlet parsed;\ntry {\n  parsed = JSON.parse(raw);\n} catch(e) {\n  const match = raw.match(/\\{[\\s\\S]*\\}/);\n  if (match) {\n    try {\n      parsed = JSON.parse(match[0]);\n    } catch(e2) {\n      const extractField = (field) => {\n        const re = new RegExp('\"' + field + '\"\\\\s*:\\\\s*\"([^\"]*?\")');\n        const m = raw.match(re);\n        return m ? m[1] : 'Unknown';\n      };\n      parsed = {\n        patient_name:       extractField('patient_name'),\n        patient_email:      extractField('patient_email'),\n        report_date:        extractField('report_date'),\n        report_type:        extractField('report_type'),\n        lab_name:           extractField('lab_name'),\n        detected_language:  extractField('detected_language'),\n        preferred_language: extractField('preferred_language'),\n        raw_findings:       extractField('raw_findings')\n      };\n    }\n  } else {\n    throw new Error('Could not extract JSON from GPT response: ' + raw.substring(0, 200));\n  }\n}\n\nconst pdfText  = $('Extract Text: Parse PDF to Raw Text').item.json.text || '';\nconst fileName = $('Google Drive: Watch for New Lab Report').item.json.name || 'lab_report.pdf';\nconst fileId   = $('Google Drive: Watch for New Lab Report').item.json.id;\n\nreturn [{ json: {\n  ...parsed,\n  pdf_raw_text: pdfText,\n  file_name:    fileName,\n  file_id:      fileId\n}}];"
      },
      "typeVersion": 2
    },
    {
      "id": "d32a4fda-95b9-4433-9a37-4840b3a0eeda",
      "name": "AI: Simplify Findings to Plain English",
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "onError": "continueErrorOutput",
      "position": [
        -272,
        48
      ],
      "parameters": {
        "modelId": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4o-mini",
          "cachedResultName": "GPT-4O-MINI"
        },
        "options": {
          "temperature": 0.4
        },
        "messages": {
          "values": [
            {
              "role": "system",
              "content": "You are a compassionate medical communication expert. Your job is to convert raw lab report findings into a clear, patient-friendly plain-language summary in ENGLISH only \u2014 even if the original report is in another language.\n\nRules:\n- Never use medical jargon without explaining it in simple terms\n- Use analogies where helpful (e.g. 'Your haemoglobin is like the oxygen-carrying capacity of your blood')\n- For each finding, clearly state: what was tested, the result, whether it is Normal / Borderline / Abnormal, and what it means in everyday life\n- End with a friendly 'What to do next' section\n- Format output as clean Markdown\n- Be warm and reassuring in tone"
            },
            {
              "content": "=Patient: {{ $json.patient_name }}\nReport Type: {{ $json.report_type }}\nReport Date: {{ $json.report_date }}\nLab: {{ $json.lab_name }}\n\nRaw Findings:\n{{ $json.raw_findings }}\n\nPlease write a plain-language summary in English."
            }
          ]
        }
      },
      "typeVersion": 1.4
    },
    {
      "id": "e2f08c89-2c38-4b4f-8291-31c09640d944",
      "name": "Code: Attach Plain English Summary",
      "type": "n8n-nodes-base.code",
      "position": [
        112,
        48
      ],
      "parameters": {
        "jsCode": "// Carry forward all fields + plain English summary\nconst structuredData = $('Code: Parse & Sanitise Extraction JSON').item.json;\nconst plainEnglish = $('AI: Simplify Findings to Plain English').item.json.message.content;\n\nreturn [{ json: {\n  ...structuredData,\n  plain_english_summary: plainEnglish\n}}];"
      },
      "typeVersion": 2
    },
    {
      "id": "41b31c01-eeb3-4f27-992c-aae4ec981bca",
      "name": "AI: Translate Summary to Patient Language",
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "onError": "continueErrorOutput",
      "position": [
        352,
        48
      ],
      "parameters": {
        "modelId": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4o-mini",
          "cachedResultName": "GPT-4O-MINI"
        },
        "options": {
          "temperature": 0.2
        },
        "messages": {
          "values": [
            {
              "role": "system",
              "content": "=You are a professional medical translator. Translate the following plain-language medical summary accurately into {{ $json.preferred_language }}.\n\nRules:\n- Preserve all Markdown formatting (headings, bold, bullet points)\n- Keep medical terms in the target language (use local equivalents where they exist)\n- Keep the warm, reassuring tone\n- If the preferred language is English, return the text unchanged\n- Output ONLY the translated Markdown \u2014 no explanation, no preamble"
            },
            {
              "content": "=Translate this medical summary to {{ $json.preferred_language }}:\n\n{{ $json.plain_english_summary }}"
            }
          ]
        }
      },
      "typeVersion": 1.4
    },
    {
      "id": "27cb534c-f958-4871-9bf9-aeaa67da2ee7",
      "name": "Code: Build Final Markdown Report",
      "type": "n8n-nodes-base.code",
      "position": [
        688,
        48
      ],
      "parameters": {
        "jsCode": "const structuredData = $('Code: Attach Plain English Summary').item.json;\nconst translatedSummary = $('AI: Translate Summary to Patient Language').item.json.message.content;\nconst now = new Date().toISOString();\n\nconst finalMarkdown = `# \ud83c\udfe5 Lab Report Summary\\n\\n**Patient:** ${structuredData.patient_name}\\n**Report Type:** ${structuredData.report_type}\\n**Report Date:** ${structuredData.report_date}\\n**Lab:** ${structuredData.lab_name}\\n**Language:** ${structuredData.preferred_language}\\n**Processed:** ${now}\\n\\n---\\n\\n${translatedSummary}`;\n\nreturn [{ json: {\n  ...structuredData,\n  translated_summary: translatedSummary,\n  final_markdown: finalMarkdown,\n  processed_at: now\n}}];"
      },
      "typeVersion": 2
    },
    {
      "id": "f08e1a18-40c8-47d8-8496-0f9e156973a6",
      "name": "Code: Convert Report to Markdown File",
      "type": "n8n-nodes-base.code",
      "position": [
        896,
        48
      ],
      "parameters": {
        "jsCode": "const markdownText = $('Code: Build Final Markdown Report').item.json.final_markdown;\n\nconst binaryData = await this.helpers.prepareBinaryData(\n  Buffer.from(markdownText, 'utf8'),\n  'lab_report_summary.md',\n  'text/markdown'\n);\n\nreturn [{\n  json: $('Code: Build Final Markdown Report').item.json,\n  binary: {\n    data: binaryData\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "3cefb973-3f24-4511-a524-069593a959ef",
      "name": "Gmail: Email Summary to Patient",
      "type": "n8n-nodes-base.gmail",
      "position": [
        1136,
        48
      ],
      "parameters": {
        "sendTo": "={{ $json.patient_email }}",
        "message": "=<div style=\"font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto; padding: 20px;\">\n  <div style=\"background: #1B2A4A; padding: 20px; border-radius: 8px 8px 0 0;\">\n    <h2 style=\"color: white; margin: 0;\">\ud83c\udfe5 Your Lab Report Summary</h2>\n  </div>\n  <div style=\"background: #f9f9f9; padding: 20px; border: 1px solid #e0e0e0;\">\n    <p>Dear <strong>{{ $('Code: Build Final Markdown Report').item.json.patient_name }}</strong>,</p>\n    <p>Your lab report has been processed and translated into <strong>{{ $('Code: Build Final Markdown Report').item.json.preferred_language }}</strong>. Please find your easy-to-understand summary attached to this email.</p>\n    <div style=\"background: #D6E8F7; border-left: 4px solid #2D5F8A; padding: 12px; margin: 16px 0; border-radius: 4px;\">\n      <strong>Report Details:</strong><br/>\n      \ud83d\udccb Type: {{ $('Code: Build Final Markdown Report').item.json.report_type }}<br/>\n      \ud83d\udcc5 Date: {{ $('Code: Build Final Markdown Report').item.json.report_date }}<br/>\n      \ud83c\udfe5 Lab: {{ $('Code: Build Final Markdown Report').item.json.lab_name }}\n    </div>\n    <p>The summary explains your results in plain language. If you have any concerns about your results, please contact your doctor.</p>\n    <p style=\"color: #888; font-size: 12px;\">This summary was generated by AI and is for informational purposes only. Always consult your healthcare provider for medical advice.</p>\n  </div>\n</div>",
        "options": {
          "attachmentsUi": {
            "attachmentsBinary": [
              {}
            ]
          }
        },
        "subject": "=Your Lab Report Summary \u2013 {{ $('Code: Build Final Markdown Report').item.json.report_type }} ({{ $('Code: Build Final Markdown Report').item.json.report_date }})"
      },
      "credentials": {
        "gmailOAuth2": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.1
    },
    {
      "id": "48ad1e70-ab5f-48ab-a9e8-89825beb7d55",
      "name": "Slack: Alert on Extraction Error",
      "type": "n8n-nodes-base.slack",
      "position": [
        -576,
        288
      ],
      "parameters": {
        "text": "=\u26a0\ufe0f Lab Report workflow error at AI Extraction step.\nFile: {{ $('Google Drive: Watch for New Lab Report').item.json.name }}\nError: {{ $json.error.message }}",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "list",
          "value": "YOUR_SLACK_CHANNEL_ID",
          "cachedResultName": "workflow-errors"
        },
        "otherOptions": {},
        "authentication": "oAuth2"
      },
      "typeVersion": 2.3
    },
    {
      "id": "62aaa5ab-1aeb-4beb-aa99-50dc7a227ff4",
      "name": "Slack: Alert on Simplification Error",
      "type": "n8n-nodes-base.slack",
      "position": [
        -32,
        368
      ],
      "parameters": {
        "text": "=\u26a0\ufe0f Lab Report workflow error at Plain English step.\nFile: {{ $('Google Drive: Watch for New Lab Report').item.json.name }}\nError: {{ $json.error.message }}",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "list",
          "value": "YOUR_SLACK_CHANNEL_ID",
          "cachedResultName": "workflow-errors"
        },
        "otherOptions": {},
        "authentication": "oAuth2"
      },
      "typeVersion": 2.3
    },
    {
      "id": "3abe305b-a778-4af1-8426-4591469eaf01",
      "name": "Slack: Alert on Translation Error",
      "type": "n8n-nodes-base.slack",
      "position": [
        656,
        432
      ],
      "parameters": {
        "text": "=\u26a0\ufe0f Lab Report workflow error at Translation step.\nFile: {{ $('Google Drive: Watch for New Lab Report').item.json.name }}\nError: {{ $json.error.message }}",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "list",
          "value": "YOUR_SLACK_CHANNEL_ID",
          "cachedResultName": "workflow-errors"
        },
        "otherOptions": {},
        "authentication": "oAuth2"
      },
      "typeVersion": 2.3
    }
  ],
  "active": false,
  "settings": {
    "binaryMode": "separate",
    "availableInMCP": false,
    "executionOrder": "v1"
  },
  "versionId": "13b83b68-07c5-45d2-9813-55414dafc953",
  "connections": {
    "Google Drive: Download PDF": {
      "main": [
        [
          {
            "node": "Extract Text: Parse PDF to Raw Text",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Slack: Alert on Extraction Error": {
      "main": [
        [
          {
            "node": "Google Drive: Download PDF",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code: Build Final Markdown Report": {
      "main": [
        [
          {
            "node": "Code: Convert Report to Markdown File",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Slack: Alert on Translation Error": {
      "main": [
        [
          {
            "node": "Google Drive: Download PDF",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code: Attach Plain English Summary": {
      "main": [
        [
          {
            "node": "AI: Translate Summary to Patient Language",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Text: Parse PDF to Raw Text": {
      "main": [
        [
          {
            "node": "AI: Extract Structure + Detect Language",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Slack: Alert on Simplification Error": {
      "main": [
        [
          {
            "node": "Google Drive: Download PDF",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code: Convert Report to Markdown File": {
      "main": [
        [
          {
            "node": "Gmail: Email Summary to Patient",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AI: Simplify Findings to Plain English": {
      "main": [
        [
          {
            "node": "Code: Attach Plain English Summary",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Slack: Alert on Simplification Error",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code: Parse & Sanitise Extraction JSON": {
      "main": [
        [
          {
            "node": "AI: Simplify Findings to Plain English",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Google Drive: Watch for New Lab Report": {
      "main": [
        [
          {
            "node": "Google Drive: Download PDF",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AI: Extract Structure + Detect Language": {
      "main": [
        [
          {
            "node": "Code: Parse & Sanitise Extraction JSON",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Slack: Alert on Extraction Error",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AI: Translate Summary to Patient Language": {
      "main": [
        [
          {
            "node": "Code: Build Final Markdown Report",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Slack: Alert on Translation Error",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}