Muster für die Entwicklung von CDSS im Gesundheitswesen
Muster für den Aufbau klinischer Entscheidungsunterstützungssysteme (CDSS), die in EMR-Workflows integriert werden können. CDSS-Module sind patientensicherheitskritisch – null Toleranz für falsch-negative Ergebnisse.
Wann verwenden
- Implementierung von Arzneimittelinteraktionsprüfungen
- Erstellung von Dosisvalidierungs-Engines
- Implementierung klinischer Bewertungssysteme (NEWS2, qSOFA, APACHE, GCS)
- Entwurf von Alarmsystemen für abnorme klinische Werte
- Aufbau von Medikationsbestellungen mit Sicherheitsprüfungen
- Integration von Laborwertinterpretation mit klinischem Kontext
Funktionsweise
Die CDSS-Engine ist eine reine Funktionsbibliothek ohne Seiteneffekte. Klinische Daten eingeben, Alarme ausgeben. Dies macht sie vollständig testbar.
Drei primäre Module:
checkInteractions(newDrug, currentMeds, allergies)– Prüft ein neues Medikament gegen aktuelle Medikationen und bekannte Allergien. Gibt nach Schweregrad sortierteInteractionAlert[]zurück. Verwendet das DatenmodellDrugInteractionPair.validateDose(drug, dose, route, weight, age, renalFunction)– Validiert eine verschriebene Dosis gegen gewichtsbasierte, alters- und nierenfunktionsangepasste Regeln. GibtDoseValidationResultzurück.calculateNEWS2(vitals)– National Early Warning Score 2 ausNEWS2Input. GibtNEWS2Resultmit Gesamtpunktzahl, Risikostufe und Eskalationsanleitung zurück.
EMR-UI
↓ (Benutzer gibt Daten ein)
CDSS-Engine (reine Funktionen, keine Seiteneffekte)
├── Arzneimittelinteraktionsprüfer
├── Dosisvalidator
├── Klinische Bewertung (NEWS2, qSOFA, etc.)
└── Alarmklassifizierer
↓ (gibt Alarme zurück)
EMR-UI (zeigt Alarme inline an, blockiert bei kritischen)
Arzneimittelinteraktionsprüfung
interface DrugInteractionPair {
drugA: string; // generischer Name
drugB: string; // generischer Name
severity: 'critical' | 'major' | 'minor';
mechanism: string;
clinicalEffect: string;
recommendation: string;
}
function checkInteractions(
newDrug: string,
currentMedications: string[],
allergyList: string[]
): InteractionAlert[] {
if (!newDrug) return [];
const alerts: InteractionAlert[] = [];
for (const current of currentMedications) {
const interaction = findInteraction(newDrug, current);
if (interaction) {
alerts.push({ severity: interaction.severity, pair: [newDrug, current],
message: interaction.clinicalEffect, recommendation: interaction.recommendation });
}
}
for (const allergy of allergyList) {
if (isCrossReactive(newDrug, allergy)) {
alerts.push({ severity: 'critical', pair: [newDrug, allergy],
message: `Kreuzreaktivität mit dokumentierter Allergie: ${allergy}`,
recommendation: 'Nicht ohne Allergiekonsultation verschreiben' });
}
}
return alerts.sort((a, b) => severityOrder(a.severity) - severityOrder(b.severity));
}
Interaktionspaare müssen bidirektional sein: Wenn Medikament A mit Medikament B interagiert, dann interagiert auch Medikament B mit Medikament A.
Dosisvalidierung
interface DoseValidationResult {
valid: boolean;
message: string;
suggestedRange: { min: number; max: number; unit: string } | null;
factors: string[];
}
function validateDose(
drug: string,
dose: number,
route: 'oral' | 'iv' | 'im' | 'sc' | 'topical',
patientWeight?: number,
patientAge?: number,
renalFunction?: number
): DoseValidationResult {
const rules = getDoseRules(drug, route);
if (!rules) return { valid: true, message: 'Keine Validierungsregeln verfügbar', suggestedRange: null, factors: [] };
const factors: string[] = [];
// SICHERHEIT: Wenn Regeln Gewicht erfordern, aber Gewicht fehlt, BLOCKIEREN (nicht durchlassen)
if (rules.weightBased) {
if (!patientWeight || patientWeight <= 0) {
return { valid: false, message: `Gewicht erforderlich für ${drug} (mg/kg Medikament)`,
suggestedRange: null, factors: ['weight_missing'] };
}
factors.push('weight');
const maxDose = rules.maxPerKg * patientWeight;
if (dose > maxDose) {
return { valid: false, message: `Dosis überschreitet Maximum für ${patientWeight}kg`,
suggestedRange: { min: rules.minPerKg * patientWeight, max: maxDose, unit: rules.unit }, factors };
}
}
// Altersbasierte Anpassung (wenn Regeln Altersgruppen definieren und Alter angegeben ist)
if (rules.ageAdjusted && patientAge !== undefined) {
factors.push('age');
const ageMax = rules.getAgeAdjustedMax(patientAge);
if (dose > ageMax) {
return { valid: false, message: `Überschreitet altersangepasstes Maximum für ${patientAge} Jahre`,
suggestedRange: { min: rules.typicalMin, max: ageMax, unit: rules.unit }, factors };
}
}
// Nierenfunktionsanpassung (wenn Regeln eGFR-Gruppen definieren und eGFR angegeben ist)
if (rules.renalAdjusted && renalFunction !== undefined) {
factors.push('renal');
const renalMax = rules.getRenalAdjustedMax(renalFunction);
if (dose > renalMax) {
return { valid: false, message: `Überschreitet nierenangepasstes Maximum für eGFR ${renalFunction}`,
suggestedRange: { min: rules.typicalMin, max: renalMax, unit: rules.unit }, factors };
}
}
// Absolutes Maximum
if (dose > rules.absoluteMax) {
return { valid: false, message: `Überschreitet absolutes Maximum ${rules.absoluteMax}${rules.unit}`,
suggestedRange: { min: rules.typicalMin, max: rules.absoluteMax, unit: rules.unit },
factors: [...factors, 'absolute_max'] };
}
return { valid: true, message: 'Innerhalb des Bereichs',
suggestedRange: { min: rules.typicalMin, max: rules.typicalMax, unit: rules.unit }, factors };
}
Klinische Bewertung: NEWS2
interface NEWS2Input {
respiratoryRate: number; oxygenSaturation: number; supplementalOxygen: boolean;
temperature: number; systolicBP: number; heartRate: number;
consciousness: 'alert' | 'voice' | 'pain' | 'unresponsive';
}
interface NEWS2Result {
total: number; // 0–20
risk: 'low' | 'low-medium' | 'medium' | 'high';
components: Record<string, number>;
escalation: string;
}
Bewertungstabellen müssen exakt der Spezifikation des Royal College of Physicians entsprechen.
Alarm-Schweregrad und UI-Verhalten
| Schweregrad | UI-Verhalten | Erforderliche Maßnahme des Klinikers |
|---|---|---|
| Kritisch | Aktion blockieren. Nicht schließbarer Modal. Rot. | Überschreibungsgrund muss dokumentiert werden, um fortzufahren |
| Schwerwiegend | Warnbanner inline. Orange. | Muss bestätigt werden, bevor fortgefahren wird |
| Geringfügig | Info-Hinweis inline. Gelb. | Nur zur Kenntnisnahme, keine Aktion erforderlich |
Kritische Alarme dürfen NIEMALS automatisch geschlossen oder als Toast-Benachrichtigungen implementiert werden. Überschreibungsgründe müssen im Prüfpfad gespeichert werden.
CDSS testen (Null Toleranz für falsch-negative Ergebnisse)
describe('CDSS — Patientensicherheit', () => {
INTERACTION_PAIRS.forEach(({ drugA, drugB, severity }) => {
it(`erkennt ${drugA} + ${drugB} (${severity})`, () => {
const alerts = checkInteractions(drugA, [drugB], []);
expect(alerts.length).toBeGreaterThan(0);
expect(alerts[0].severity).toBe(severity);
});
it(`erkennt ${drugB} + ${drugA} (umgekehrt)`, () => {
const alerts = checkInteractions(drugB, [drugA], []);
expect(alerts.length).toBeGreaterThan(0);
});
});
it('blockiert mg/kg Medikament bei fehlendem Gewicht', () => {
const result = validateDose('gentamicin', 300, 'iv');
expect(result.valid).toBe(false);
expect(result.factors).toContain('weight_missing');
});
it('behandelt fehlerhafte Arzneimitteldaten ordnungsgemäß', () => {
expect(() => checkInteractions('', [], [])).not.toThrow();
});
});
Bestehenskriterium: 100%. Eine einzige übersehene Interaktion ist ein Patienten-Sicherheitsvorfall.
Anti-Patterns
- CDSS-Prüfungen optional oder ohne dokumentierten Grund überspringbar machen
- Interaktionsprüfungen als Toast-Benachrichtigungen implementieren
any-Typen für Arzneimittel- oder klinische Daten verwenden- Interaktionspaare festcodieren anstatt eine wartbare Datenstruktur zu verwenden
- Fehler in der CDSS-Engine stillschweigend abfangen (muss Fehler lautstark anzeigen)
- Gewichtsbasierte Validierung überspringen, wenn kein Gewicht verfügbar ist (muss blockieren, nicht durchlassen)
Beispiele
Beispiel 1: Arzneimittelinteraktionsprüfung
const alerts = checkInteractions('warfarin', ['aspirin', 'metformin'], ['penicillin']);
// [{ severity: 'critical', pair: ['warfarin', 'aspirin'],
// message: 'Erhöhtes Blutungsrisiko', recommendation: 'Kombination vermeiden' }]
Beispiel 2: Dosisvalidierung
const ok = validateDose('paracetamol', 1000, 'oral', 70, 45);
// { valid: true, suggestedRange: { min: 500, max: 4000, unit: 'mg' } }
const bad = validateDose('paracetamol', 5000, 'oral', 70, 45);
// { valid: false, message: 'Überschreitet absolutes Maximum 4000mg' }
const noWeight = validateDose('gentamicin', 300, 'iv');
// { valid: false, factors: ['weight_missing'] }
Beispiel 3: NEWS2-Bewertung
const result = calculateNEWS2({
respiratoryRate: 24, oxygenSaturation: 93, supplementalOxygen: true,
temperature: 38.5, systolicBP: 100, heartRate: 110, consciousness: 'voice'
});
// { total: 13, risk: 'high', escalation: 'Dringende klinische Überprüfung. Erwägen Sie Intensivstation.' }