Skip to content

About

n8n workflows for AI-powered Clinical SOAP note generation (TTT, STT, TTS)

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

πŸ₯ Clinical SOAP Note Generator

AI-powered clinical documentation workflows built on n8n

Generate structured SOAP notes from text or voice β€” automatically.

n8n Ollama ElevenLabs PostgreSQL


πŸ“‹ Overview

This project provides three production-ready n8n workflows for automated clinical note generation. A clinician or intake system sends raw clinical input β€” as typed text or a voice recording β€” and receives a fully structured SOAP (Subjective, Objective, Assessment, Plan) note in return, conforming to the master clinical progress note template.

Workflow File Input Output
TTT clinicalNote(TTT).json Raw clinical text SOAP note JSON
STT clinicalNote(STT).json Audio recording SOAP note JSON
TTS clinicalNote(TTS).json Raw clinical text SOAP note + spoken audio

πŸ—‚οΈ Master SOAP Template

πŸ“„ Download the full master template PDF

All workflows produce output conforming to the following clinical progress note structure:

Clinical Progress Note
══════════════════════════════════════════════════

Patient Information
  β”œβ”€β”€ Patient Name      β†’ {{patient_name}}
  β”œβ”€β”€ Date of Birth     β†’ {{patient_dob}}
  β”œβ”€β”€ Patient ID        β†’ {{patient_id}}
  β”œβ”€β”€ Date of Visit     β†’ {{visit_date}}
  β”œβ”€β”€ Practitioner      β†’ {{practitioner}}, {{practitioner_credentials}}
  └── Chief Complaint   β†’ {{chief_complaint}}

S β€” Subjective  (Patient's own words & reported history)
  β”œβ”€β”€ HPI Narrative           β†’ {{hpi_narrative}}
  β”œβ”€β”€ Reported Symptoms       β†’ {{symptoms_reported}}
  β”œβ”€β”€ Progress Since Last     β†’ {{progress_since_last}}
  └── Compliance with Plan    β†’ {{compliance_notes}}

O β€” Objective  (Measurable, observable clinical data)
  β”œβ”€β”€ Vital Signs
  β”‚     β”œβ”€β”€ BP              β†’ {{bp}}
  β”‚     β”œβ”€β”€ Heart Rate      β†’ {{heart_rate}}
  β”‚     β”œβ”€β”€ Temperature     β†’ {{temperature}}
  β”‚     └── O2 Saturation   β†’ {{o2_saturation}}
  β”œβ”€β”€ Physical Exam         β†’ {{physical_exam_findings}}
  β”œβ”€β”€ Labs Reviewed         β†’ {{lab_results}}
  └── Imaging               β†’ {{imaging_results}}

A β€” Assessment  (Clinician's diagnosis & interpretation)
  β”œβ”€β”€ Primary Diagnosis (ICD-10) β†’ {{primary_diagnosis}} ({{icd_10_code}})
  β”œβ”€β”€ Problem List               β†’ {{problem_list}}
  └── Clinical Rationale         β†’ {{assessment_summary}}

P β€” Plan  (Course of action)
  β”œβ”€β”€ Medication Changes     β†’ {{medication_plan}}
  β”œβ”€β”€ Interventions          β†’ {{intervention_details}}
  β”œβ”€β”€ Follow-up Instructions β†’ {{follow_up_plan}}
  └── Referrals              β†’ {{referrals}}

βš™οΈ Architecture

HTTP Request (Text or Audio)
           β”‚
           β–Ό
    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    β”‚   Webhook   β”‚  ← n8n entry point
    β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜
           β”‚
     β”Œβ”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”
     β–Ό            β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     [STT only]
β”‚ Verify  β”‚  β”‚ ElevenLabsβ”‚ ←── Audio transcription
β”‚Practit. β”‚  β”‚  Scribe   β”‚
β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜
     β–Ό             β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”        β”‚
β”‚ Verify  β”‚        β”‚
β”‚ Patient β”‚        β”‚
β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”˜        β”‚
     β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜
            β–Ό
     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
     β”‚  codeParse  β”‚  ← Normalize patient + practitioner + clinical input
     β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜
            β–Ό
     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
     β”‚  Gemma 3 4B β”‚  ← Local LLM via Ollama
     β”‚ (LLM Chain) β”‚
     β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜
            β–Ό
     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
     β”‚ Clean JSON  β”‚  ← Strip markdown, parse output
     β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜
            β–Ό
     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
     β”‚  Response                β”‚
     β”‚  JSON (TTT / STT)        β”‚
     β”‚  audio/mpeg (TTS)        β”‚
     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

πŸ”Œ Workflow Triggers & API Reference

1️⃣ TTT β€” Text to Text

Accepts raw clinical text β†’ returns SOAP note as JSON

Endpoint

POST /webhook/clinical-note/start
Content-Type: application/json

Request Body

{
  "provider_email": "provider@clinic.com",
  "patient_email": "patient@example.com",
  "appointment_id": "APPT-XXXX",
  "mode": "text",
  "text_input": "<raw clinical notes from the encounter>",
  "metadata": {
    "source": "patient",
    "language": "en-US"
  }
}

Example Clinical Input

34-year-old male with 4 days of fever, cough, sore throat, congestion,
headache, chills, and fatigue. Cough initially dry, now mildly productive.
Denies chest pain, shortness of breath, nausea, vomiting, or diarrhea.
Partial relief with paracetamol. Vitals: Temp 37.8 C, BP 118/74, HR 88,
RR 16, SpO2 98% on room air. Mild pharyngeal erythema. Lungs clear.
COVID and rapid strep negative.

Response

{
  "status": "success",
  "code": 200,
  "message": "Clinical SOAP note generated successfully.",
  "data": {
    "soap_note": {
      "patient_name": "{{patient_name}}",
      "patient_dob": "{{patient_dob}}",
      "patient_id": "{{patient_id}}",
      "visit_date": "{{visit_date}}",
      "practitioner": "{{practitioner_name}}",
      "practitioner_credentials": "MD",
      "chief_compliant": "Patient presents with cough, congestion, and subjective fever.",
      "subjective": {
        "hpi_narrative": "Patient reports 4 days of fever, cough, sore throat, congestion, headache, chills, and fatigue...",
        "symptoms_reported": "Fever, cough, sore throat, congestion, headache, chills, fatigue. Denies chest pain, shortness of breath.",
        "progress_since_last": "NA",
        "compliance_notes": "NA"
      },
      "objective": {
        "bp": "118/74",
        "heart_rate": "88",
        "temperature": "37.8 C",
        "o2_saturation": "98 percent on room air",
        "physical_exam_findings": "Mild pharyngeal erythema. Lungs clear bilaterally. Heart rhythm normal. Abdomen soft, non-tender.",
        "lab_results": "COVID negative. Rapid strep negative.",
        "imaging_results": "None"
      },
      "assessment": {
        "primary_diagnosis": "Upper Respiratory Infection",
        "icd_10_code": "J06.9",
        "problem_list": "Upper Respiratory Infection",
        "assessment_summary": "Presentation consistent with viral upper respiratory infection based on symptom duration, negative rapid tests, and clinical findings."
      },
      "plan": {
        "medication_plan": "Acetaminophen 325mg every 6 hours as needed. Saline nasal spray for congestion.",
        "intervention_details": "Patient educated on hydration and rest.",
        "follow_up_plan": "Return if symptoms worsen or do not improve within 72 hours.",
        "referrals": "None"
      }
    }
  },
  "timestamp": "2025-12-16T00:00:00.000Z"
}

2️⃣ STT β€” Speech to Text

Accepts a clinical audio recording β†’ transcribes it β†’ returns SOAP note as JSON

Endpoint

POST /webhook/clinical-note-audio
Content-Type: multipart/form-data

Request Fields

Field Type Description
data File Audio recording of the clinical encounter (.wav, .mp3, etc.)
practitioner_email String Practitioner's registered email
patient_email String Patient's registered email

cURL Example

curl -X POST http://localhost:5678/webhook/clinical-note-audio \
  -F "data=@recording.wav" \
  -F "practitioner_email=provider@clinic.com" \
  -F "patient_email=patient@example.com"

Response β€” Same SOAP note JSON structure as TTT.

Audio is transcribed using ElevenLabs Scribe v1 before being processed by the LLM.


3️⃣ TTS β€” Text to Speech

Accepts raw clinical text β†’ generates SOAP note β†’ returns spoken summary as audio

Endpoint

POST /webhook/clinical-note-tts
Content-Type: application/json

Request Body β€” Same structure as TTT.

Response

HTTP 200
Content-Type: audio/mpeg
Content-Disposition: inline; filename="clinical_note.mp3"

[binary audio stream]

The spoken summary includes:

  • Chief Complaint
  • History of Present Illness
  • Assessment Summary
  • Follow-up Plan

🧩 Node Reference

Shared Nodes (all 3 workflows)

Node Type Purpose
clinicalNote Webhook Entry point β€” receives the HTTP request
verifyPractitioner PostgreSQL Looks up practitioner FHIR resource by email
verifyPatient PostgreSQL Looks up patient FHIR resource by email
codeParse Code Normalizes patient, practitioner & clinical text into one payload
promptChain LLM Chain Sends structured prompt to Gemma 3 via Ollama
cleanOutputJSON Code Strips LLM markdown, returns parsed JSON
respondSuccess Respond to Webhook Returns final JSON response

STT-only Nodes

Node Type Purpose
speech2Text HTTP Request Sends audio to ElevenLabs Scribe v1 for transcription
extractTranscription Code Extracts text field from transcription response
merge Merge Joins verified patient/practitioner data with transcription

TTS-only Nodes

Node Type Purpose
cleanOutputJSON Code Extracts HPI, assessment & plan for the spoken script
text2Speech HTTP Request Sends script to ElevenLabs TTS API, receives audio
respondSucessAudio Respond to Webhook Returns binary audio response

πŸ—„οΈ Database Schema (FHIR)

Patient and practitioner data is stored as FHIR R4 JSON resources in PostgreSQL:

CREATE TABLE practitioner (
  resource JSONB
);

CREATE TABLE patient (
  resource JSONB
);

Lookup queries used internally:

-- Find practitioner by email
SELECT resource FROM practitioner
WHERE resource @> jsonb_build_object(
  'telecom', jsonb_build_array(
    jsonb_build_object('system', 'email', 'value', $1)
  )
) LIMIT 1;

-- Find patient by email
SELECT resource FROM patient
WHERE resource->>'email' = $1 LIMIT 1;

πŸš€ Setup Guide

Prerequisites

  • n8n (self-hosted)
  • Ollama with gemma3:4b pulled
  • PostgreSQL with FHIR-structured patient/practitioner data
  • ElevenLabs account (for STT and TTS workflows)

Steps

1. Import workflows into n8n

Go to Settings β†’ Import Workflow and import each .json file.

2. Configure credentials in n8n

Credential Where used Notes
PostgreSQL verifyPractitioner, verifyPatient Your Cloud SQL or local Postgres
Ollama API gemma3 node Default: http://localhost:11434
ElevenLabs API Key speech2Text, text2Speech Set as xi-api-key header

3. (Optional) Google Cloud SQL β€” use the Auth Proxy

If your PostgreSQL is hosted on Google Cloud SQL, run the proxy locally before starting n8n:

.\cloud-sql-proxy.exe YOUR_PROJECT:YOUR_REGION:YOUR_INSTANCE --port=5432

Then set Postgres host to 127.0.0.1 in your n8n credential.

4. Activate workflows and test

Use the example requests above or import into Postman/Insomnia.


πŸ” Credential Placeholders

The workflow JSON files contain the following placeholders. Replace them with your actual values inside n8n:

Placeholder Replace with
YOUR_ELEVENLABS_API_KEY Your ElevenLabs API key
YOUR_POSTGRES_CREDENTIAL_ID Your n8n Postgres credential ID
YOUR_OLLAMA_CREDENTIAL_ID Your n8n Ollama credential ID
YOUR_N8N_INSTANCE_ID Your n8n instance ID

⚠️ Never commit real API keys or credentials to version control.


πŸ“„ License

This project is for educational and portfolio purposes.

About

n8n workflows for AI-powered Clinical SOAP note generation (TTT, STT, TTS)

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors