Healthcare CDSS Development Patterns (deutsche Übersetzung)
Muster zum Aufbau von Clinical Decision Support Systems, die in EMR-Workflows integriert werden. CDSS-Module sind patientensicherheitskritisch – Nulltoleranz für falsch negative Ergebnisse.
Wann verwenden
- Implementierung von Arzneimittelwechselwirkungsprüfungen
- Erstellung von Dosisvalidierungs-Engines
- Implementierung klinischer Bewertungssysteme (NEWS2, qSOFA, APACHE, GCS)
- Gestaltung von Warnsystemen für abnormale klinische Werte
- Aufbau von Medikamentenbestellung mit Sicherheitsprüfungen
- Integration von Laborergebnisinterpretation mit klinischem Kontext
Wie es funktioniert
Die CDSS-Engine ist eine reine Funktionsbibliothek ohne Seiteneffekte. Klinische Daten eingeben, Warnungen ausgeben. Dadurch ist sie vollständig testbar.
Drei Hauptmodule:
checkInteractions(newDrug, currentMeds, allergies)– Prüft ein neues Medikament gegen aktuelle Medikationen und bekannte Allergien. GibtInteractionAlert[]sortiert nach Schweregrad zurück. Verwendet dasDrugInteractionPair-Datenmodell.validateDose(drug, dose, route, weight, age, renalFunction)– Validiert eine verschriebene Dosis gegen gewichtsbasierte, altersangepasste und nierenangepasste Regeln. GibtDoseValidationResultzurück.calculateNEWS2(vitals)– National Early Warning Score 2 ausNEWS2Input. GibtNEWS2Resultmit Gesamtpunktzahl, Risikostufe und Eskalationsanleitung zurück.
EMR-Benutzeroberfläche
↓ (Benutzer gibt Daten ein)
CDSS-Engine (reine Funktionen, keine Seiteneffekte)
├── Arzneimittelwechselwirkungsprüfer
├── Dosisvalidator
├── Klinische Bewertung (NEWS2, qSOFA, etc.)
└── Warnklassifizierer
↓ (gibt Warnungen zurück)
EMR-Benutzeroberfläche (zeigt Warnungen inline an, blockiert bei kritisch)
Arzneimittelwechselwirkungsprü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 Allergie-Konsultation verschreiben' });
}
}
return alerts.sort((a, b) => severityOrder(a.severity) - severityOrder(b.severity));
}
Wechselwirkungspaare müssen bidirektional sein: Wenn Arzneimittel A mit Arzneimittel B interagiert, dann interagiert Arzneimittel B mit Arzneimittel 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 Arzneistoff)`,
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 Altersklassen 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}J.`,
suggestedRange: { min: rules.typicalMin, max: ageMax, unit: rules.unit }, factors };
}
}
// Nierenanpassung (wenn Regeln eGFR-Klassen 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: 'Im Bereich',
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.
Warnschwere und UI-Verhalten
| Schweregrad | UI-Verhalten | Erforderliche Maßnahme des Klinikers |
|---|---|---|
| Kritisch | Aktion blockieren. Nicht schließbarer Modal. Rot. | Übergehungsgrund muss dokumentiert werden, um fortzufahren |
| Schwerwiegend | Warnbanner inline. Orange. | Vor Fortfahren bestätigen |
| Gering | Info-Hinweis inline. Gelb. | Nur zur Kenntnisnahme, keine Aktion erforderlich |
Kritische Warnungen dürfen NIEMALS automatisch geschlossen oder als Toast-Benachrichtigungen implementiert werden. Übergehungsgründe müssen im Prüfpfad gespeichert werden.
Testen von CDSS (Nulltoleranz 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 elegant', () => {
expect(() => checkInteractions('', [], [])).not.toThrow();
});
});
Bestandenskriterium: 100 %. Eine einzige verpasste Wechselwirkung ist ein Patientensicherheitsvorfall.
Anti-Patterns
- CDSS-Prüfungen ohne dokumentierten Grund optional oder überspringbar machen
- Wechselwirkungsprüfungen als Toast-Benachrichtigungen implementieren
any-Typen für Arzneimittel- oder klinische Daten verwenden- Wechselwirkungspaare fest codieren anstatt eine wartbare Datenstruktur zu verwenden
- Fehler in der CDSS-Engine stillschweigend abfangen (muss Fehler lautstark melden)
- Gewichtsbasierte Validierung überspringen, wenn Gewicht nicht verfügbar ist (muss blockieren, nicht durchlassen)
Beispiele
Beispiel 1: Arzneimittelwechselwirkungsprü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. Intensivstation in Betracht ziehen.' }