{
  "id": "ZglHLOi9fxEH6oKT",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "Real-Time Patient Monitoring with Philips IntelliVue Devices",
  "tags": [],
  "nodes": [
    {
      "id": "6b8ff0c7-6c35-4141-a0c8-030dcab3fe7b",
      "name": "Poll Device Data Every 30s",
      "type": "n8n-nodes-base.cron",
      "position": [
        -580,
        80
      ],
      "parameters": {
        "triggerTimes": {
          "item": [
            {}
          ]
        }
      },
      "typeVersion": 1
    },
    {
      "id": "97fbcb09-0510-404e-b934-598c783a15d9",
      "name": "Fetch from IntelliVue Gateway",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        -360,
        80
      ],
      "parameters": {
        "url": "http://{{ $env.INTELLIVUE_GATEWAY_IP }}:8080/api/patients/current",
        "options": {
          "timeout": 5000
        },
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "httpBasicAuth"
      },
      "credentials": {
        "httpBasicAuth": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "4d33d657-9b37-426d-a5e9-674bc91ce054",
      "name": "Process Device Data",
      "type": "n8n-nodes-base.code",
      "position": [
        -140,
        80
      ],
      "parameters": {
        "jsCode": "// Process different data sources from Philips IntelliVue\nconst inputData = $input.all();\nlet processedVitals = [];\n\n// Process each input source\nfor (const input of inputData) {\n  const data = input.json;\n  let vitals = null;\n\n  // Detect data source and process accordingly\n  if (data.MSH && data.MSH.includes('HL7')) {\n    // HL7 Message Processing\n    vitals = processHL7Message(data);\n  } else if (data.filename && data.filename.endsWith('.csv')) {\n    // CSV Export File Processing\n    vitals = processCSVExport(data);\n  } else if (data.patients || data.deviceId) {\n    // Gateway API Response Processing\n    vitals = processGatewayResponse(data);\n  }\n\n  if (vitals) {\n    processedVitals.push(vitals);\n  }\n}\n\n// HL7 Message Parser\nfunction processHL7Message(hl7Data) {\n  try {\n    const segments = hl7Data.message.split('\\r');\n    let patientInfo = {};\n    let vitalSigns = {};\n\n    segments.forEach(segment => {\n      const fields = segment.split('|');\n      \n      // Patient Identification (PID segment)\n      if (segment.startsWith('PID')) {\n        patientInfo = {\n          patient_id: fields[3] || 'UNKNOWN',\n          patient_name: fields[5] || 'Unknown Patient',\n          room_number: fields[11] || 'N/A'\n        };\n      }\n      \n      // Observation Results (OBX segment for vitals)\n      if (segment.startsWith('OBX')) {\n        const valueType = fields[2];\n        const observationId = fields[3];\n        const value = parseFloat(fields[5]) || 0;\n        const unit = fields[6] || '';\n        \n        // Map Philips observation codes to vital signs\n        switch(observationId) {\n          case 'MDC_ECG_HEART_RATE':\n            vitalSigns.heart_rate = value;\n            break;\n          case 'MDC_PULS_OXIM_SAT_O2':\n            vitalSigns.spo2 = value;\n            break;\n          case 'MDC_PRESS_BLD_NONINV_SYS':\n            vitalSigns.bp_systolic = value;\n            break;\n          case 'MDC_PRESS_BLD_NONINV_DIA':\n            vitalSigns.bp_diastolic = value;\n            break;\n          case 'MDC_TEMP_BODY':\n            vitalSigns.temperature = value;\n            break;\n          case 'MDC_RESP_RATE':\n            vitalSigns.respiration_rate = value;\n            break;\n          case 'MDC_CO2_AWAY':\n            vitalSigns.etco2 = value;\n            break;\n        }\n      }\n    });\n\n    return {\n      ...patientInfo,\n      ...vitalSigns,\n      data_source: 'HL7',\n      timestamp: new Date().toISOString(),\n      device_type: 'Philips_IntelliVue'\n    };\n  } catch (error) {\n    console.error('HL7 parsing error:', error);\n    return null;\n  }\n}\n\n// CSV Export File Parser\nfunction processCSVExport(csvData) {\n  try {\n    const lines = csvData.data.split('\\n');\n    const headers = lines[0].split(',');\n    const latestRecord = lines[lines.length - 2].split(','); // Skip empty last line\n\n    const vitals = {};\n    headers.forEach((header, index) => {\n      const cleanHeader = header.trim().replace(/\"/g, '');\n      const value = latestRecord[index] ? latestRecord[index].trim().replace(/\"/g, '') : '';\n      \n      // Map CSV columns to standard format\n      switch(cleanHeader.toLowerCase()) {\n        case 'patient_id':\n        case 'patientid':\n          vitals.patient_id = value;\n          break;\n        case 'heart_rate':\n        case 'hr':\n        case 'ecg':\n          vitals.heart_rate = parseFloat(value) || 0;\n          break;\n        case 'spo2':\n        case 'oxygen_saturation':\n          vitals.spo2 = parseFloat(value) || 0;\n          break;\n        case 'bp_sys':\n        case 'systolic':\n          vitals.bp_systolic = parseFloat(value) || 0;\n          break;\n        case 'bp_dia':\n        case 'diastolic':\n          vitals.bp_diastolic = parseFloat(value) || 0;\n          break;\n        case 'temp':\n        case 'temperature':\n          vitals.temperature = parseFloat(value) || 0;\n          break;\n        case 'resp_rate':\n        case 'rr':\n          vitals.respiration_rate = parseFloat(value) || 0;\n          break;\n        case 'etco2':\n        case 'co2':\n          vitals.etco2 = parseFloat(value) || 0;\n          break;\n        case 'room':\n        case 'location':\n          vitals.room_number = value;\n          break;\n      }\n    });\n\n    return {\n      ...vitals,\n      patient_name: vitals.patient_id || 'Unknown',\n      data_source: 'CSV_Export',\n      timestamp: new Date().toISOString(),\n      device_type: 'Philips_IntelliVue'\n    };\n  } catch (error) {\n    console.error('CSV parsing error:', error);\n    return null;\n  }\n}\n\n// Gateway API Response Parser\nfunction processGatewayResponse(apiData) {\n  try {\n    // Handle different API response formats\n    const patients = apiData.patients || [apiData];\n    const patient = patients[0] || apiData;\n\n    return {\n      patient_id: patient.patientId || patient.id || 'UNKNOWN',\n      patient_name: patient.name || patient.patientName || 'Unknown',\n      room_number: patient.room || patient.location || 'N/A',\n      heart_rate: patient.heartRate || patient.hr || 0,\n      spo2: patient.spo2 || patient.oxygenSaturation || 0,\n      bp_systolic: patient.systolicBP || patient.bpSys || 0,\n      bp_diastolic: patient.diastolicBP || patient.bpDia || 0,\n      temperature: patient.temperature || patient.temp || 0,\n      respiration_rate: patient.respirationRate || patient.rr || 0,\n      etco2: patient.etco2 || patient.co2 || 0,\n      data_source: 'Gateway_API',\n      timestamp: patient.timestamp || new Date().toISOString(),\n      device_type: 'Philips_IntelliVue',\n      device_id: patient.deviceId || apiData.deviceId || 'UNKNOWN'\n    };\n  } catch (error) {\n    console.error('Gateway API parsing error:', error);\n    return null;\n  }\n}\n\n// Return all processed vitals\nreturn processedVitals.filter(v => v !== null).map(vital => ({ json: vital }));"
      },
      "typeVersion": 2
    },
    {
      "id": "26fde5b7-0e12-426b-aa01-c267b332a79c",
      "name": "Validate & Enrich Data",
      "type": "n8n-nodes-base.code",
      "position": [
        80,
        80
      ],
      "parameters": {
        "jsCode": "// Validate and enrich vital signs data\nconst vitals = $json;\n\n// Data validation\nif (!vitals.patient_id || vitals.patient_id === 'UNKNOWN') {\n  console.warn('Missing or invalid patient ID');\n  return null;\n}\n\n// Calculate derived values\nif (vitals.bp_systolic && vitals.bp_diastolic) {\n  vitals.pulse_pressure = vitals.bp_systolic - vitals.bp_diastolic;\n  vitals.mean_arterial_pressure = Math.round(vitals.bp_diastolic + (vitals.pulse_pressure / 3));\n}\n\n// Convert temperature if needed (assume Celsius input)\nif (vitals.temperature) {\n  vitals.temperature_celsius = vitals.temperature;\n  vitals.temperature_fahrenheit = Math.round((vitals.temperature * 9/5 + 32) * 10) / 10;\n}\n\n// Determine clinical status\nvitals.clinical_status = determineClinicalStatus(vitals);\nvitals.alert_level = determineAlertLevel(vitals);\nvitals.clinical_alerts = generateClinicalAlerts(vitals);\n\n// Add metadata\nvitals.processed_at = new Date().toISOString();\nvitals.system_status = 'PROCESSED';\n\nfunction determineClinicalStatus(v) {\n  const abnormalConditions = [\n    v.heart_rate && (v.heart_rate < 60 || v.heart_rate > 100),\n    v.spo2 && v.spo2 < 95,\n    v.bp_systolic && (v.bp_systolic > 140 || v.bp_systolic < 90),\n    v.temperature && (v.temperature > 38.0 || v.temperature < 36.0),\n    v.respiration_rate && (v.respiration_rate < 12 || v.respiration_rate > 20)\n  ];\n  \n  return abnormalConditions.some(condition => condition) ? 'ABNORMAL' : 'NORMAL';\n}\n\nfunction determineAlertLevel(v) {\n  // Critical conditions\n  const critical = [\n    v.heart_rate && (v.heart_rate < 50 || v.heart_rate > 120),\n    v.spo2 && v.spo2 < 90,\n    v.bp_systolic && (v.bp_systolic > 180 || v.bp_systolic < 80),\n    v.temperature && (v.temperature > 39.0 || v.temperature < 35.0),\n    v.respiration_rate && (v.respiration_rate < 8 || v.respiration_rate > 30)\n  ];\n  \n  if (critical.some(c => c)) return 'CRITICAL';\n  if (v.clinical_status === 'ABNORMAL') return 'WARNING';\n  return 'NORMAL';\n}\n\nfunction generateClinicalAlerts(v) {\n  const alerts = [];\n  \n  if (v.heart_rate) {\n    if (v.heart_rate < 50) alerts.push('Severe Bradycardia');\n    else if (v.heart_rate > 120) alerts.push('Severe Tachycardia');\n    else if (v.heart_rate < 60) alerts.push('Bradycardia');\n    else if (v.heart_rate > 100) alerts.push('Tachycardia');\n  }\n  \n  if (v.spo2) {\n    if (v.spo2 < 90) alerts.push('Severe Hypoxemia');\n    else if (v.spo2 < 95) alerts.push('Hypoxemia');\n  }\n  \n  if (v.bp_systolic) {\n    if (v.bp_systolic > 180) alerts.push('Hypertensive Crisis');\n    else if (v.bp_systolic < 80) alerts.push('Severe Hypotension');\n    else if (v.bp_systolic > 140) alerts.push('Hypertension');\n    else if (v.bp_systolic < 90) alerts.push('Hypotension');\n  }\n  \n  if (v.temperature) {\n    if (v.temperature > 39.0) alerts.push('High Fever');\n    else if (v.temperature < 35.0) alerts.push('Hypothermia');\n    else if (v.temperature > 38.0) alerts.push('Fever');\n  }\n  \n  return alerts;\n}\n\nreturn { json: vitals };"
      },
      "typeVersion": 2
    },
    {
      "id": "4a6d57fc-ef8c-480c-946e-afdb6dd307ce",
      "name": "Save to Patient Database",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        300,
        80
      ],
      "parameters": {
        "range": "PatientVitals!A:Z",
        "options": {},
        "sheetId": "{{your_patient_vitals_sheet_id}}",
        "operation": "append",
        "authentication": "serviceAccount"
      },
      "credentials": {
        "googleApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2
    },
    {
      "id": "de7e2320-85ac-4754-949b-4660a1a1f990",
      "name": "Send Clinical Alert",
      "type": "n8n-nodes-base.emailSend",
      "position": [
        740,
        80
      ],
      "parameters": {
        "text": "=Patient: {{ $json.patient_name }} (ID: {{ $json.patient_id }}), Room: {{ $json.room_number }}\nHR: {{ $json.heart_rate || 'N/A' }} bpm | SpO\u2082: {{ $json.spo2 || 'N/A' }}% | BP: {{ $json.bp_systolic || 'N/A' }}/{{ $json.bp_diastolic || 'N/A' }} mmHg | Temp: {{ $json.temperature_celsius || 'N/A' }}\u00b0C | Resp: {{ $json.respiration_rate || 'N/A' }}/min | EtCO\u2082: {{ $json.etco2 || 'N/A' }} mmHg\nAlerts: {{ ($json.clinical_alerts || []).join(', ') || 'None' }}\nMAP: {{ $json.mean_arterial_pressure || 'N/A' }} mmHg | Status: {{ $json.clinical_status || 'Unknown' }}\nAction: Assess patient, confirm vitals, check devices, notify physician, document actions.\n\n",
        "options": {},
        "subject": "\ud83c\udfe5 PHILIPS INTELLIVUE ALERT \u2013 {{ $json.alert_level }}",
        "toEmail": "nursing-{{ $json.room_number }}@hospital.com, user@example.com",
        "fromEmail": "user@example.com",
        "emailFormat": "text"
      },
      "credentials": {
        "smtp": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.1
    },
    {
      "id": "1560e40e-b92a-413c-95ef-4a5100977104",
      "name": "Switch",
      "type": "n8n-nodes-base.switch",
      "position": [
        520,
        80
      ],
      "parameters": {
        "rules": {
          "values": [
            {
              "conditions": {
                "options": {
                  "version": 2,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "7c1c5be0-12e1-4730-99ac-802c20a00601",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.alert_level }}",
                    "rightValue": "CRITICAL"
                  }
                ]
              }
            },
            {
              "conditions": {
                "options": {
                  "version": 2,
                  "leftValue": "",
                  "caseSensitive": true,
                  "typeValidation": "strict"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "id": "52051cac-6a10-4537-ba91-93bafea7a847",
                    "operator": {
                      "name": "filter.operator.equals",
                      "type": "string",
                      "operation": "equals"
                    },
                    "leftValue": "={{ $json.alert_level }}",
                    "rightValue": "Normal"
                  }
                ]
              }
            }
          ]
        },
        "options": {}
      },
      "typeVersion": 3.2
    },
    {
      "id": "7b2c77b6-470c-42b9-8f28-67ca546aabb7",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -260,
        -220
      ],
      "parameters": {
        "width": 560,
        "height": 180,
        "content": "## \ud83d\udcca Google Sheet Structure:\n### Columns: patient_id, patient_name, room_number, timestamp, ecg_heart_rate, spo2_oxygen_saturation, nibp_systolic, nibp_diastolic, temperature_celsius, respiration_rate, etco2_end_tidal, cardiac_output, alert_level, alerts, device_status"
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "d381294b-d1ca-4f13-8581-c724961f23c5",
  "connections": {
    "Switch": {
      "main": [
        [
          {
            "node": "Send Clinical Alert",
            "type": "main",
            "index": 0
          }
        ],
        []
      ]
    },
    "Process Device Data": {
      "main": [
        [
          {
            "node": "Validate & Enrich Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Validate & Enrich Data": {
      "main": [
        [
          {
            "node": "Save to Patient Database",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Save to Patient Database": {
      "main": [
        [
          {
            "node": "Switch",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Poll Device Data Every 30s": {
      "main": [
        [
          {
            "node": "Fetch from IntelliVue Gateway",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch from IntelliVue Gateway": {
      "main": [
        [
          {
            "node": "Process Device Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}