Schémas de développement CDSS pour la santé
Schémas pour construire des systèmes d'aide à la décision clinique (CDSS) intégrés aux flux de travail DME. Les modules CDSS sont critiques pour la sécurité des patients — tolérance zéro pour les faux négatifs.
Quand l'utiliser
- Implémenter la vérification des interactions médicamenteuses
- Construire des moteurs de validation de doses
- Implémenter des systèmes de scores cliniques (NEWS2, qSOFA, APACHE, GCS)
- Concevoir des systèmes d'alerte pour les valeurs cliniques anormales
- Mettre en place la saisie d'ordonnances avec contrôles de sécurité
- Intégrer l'interprétation des résultats de laboratoire avec le contexte clinique
Comment ça fonctionne
Le moteur CDSS est une bibliothèque de fonctions pures sans effets de bord. Entrez des données cliniques, obtenez des alertes. Cela le rend entièrement testable.
Trois modules principaux :
checkInteractions(nouveauMedicament, medicamentsActuels, allergies)— Vérifie un nouveau médicament par rapport aux médicaments actuels et aux allergies connues. Retourne unInteractionAlert[]trié par sévérité. Utilise le modèle de donnéesDrugInteractionPair.validateDose(medicament, dose, voie, poids, age, fonctionRenale)— Valide une dose prescrite selon les règles pondérales, ajustées à l'âge et à la fonction rénale. Retourne unDoseValidationResult.calculateNEWS2(vitalSigns)— National Early Warning Score 2 à partir deNEWS2Input. Retourne unNEWS2Resultavec le score total, le niveau de risque et les recommandations d'escalade.
UI DME
↓ (l'utilisateur saisit les données)
Moteur CDSS (fonctions pures, sans effets de bord)
├── Vérificateur d'interactions médicamenteuses
├── Validateur de doses
├── Scores cliniques (NEWS2, qSOFA, etc.)
└── Classificateur d'alertes
↓ (retourne les alertes)
UI DME (affiche les alertes en ligne, bloque si critique)
Vérification des interactions médicamenteuses
interface DrugInteractionPair {
drugA: string; // nom générique
drugB: string; // nom générique
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: `Réactivité croisée avec une allergie documentée : ${allergy}`,
recommendation: 'Ne pas prescrire sans avis allergologique' });
}
}
return alerts.sort((a, b) => severityOrder(a.severity) - severityOrder(b.severity));
}
Les paires d'interactions doivent être bidirectionnelles : si le médicament A interagit avec le médicament B, alors le médicament B interagit avec le médicament A.
Validation des doses
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: 'Aucune règle de validation disponible', suggestedRange: null, factors: [] };
const factors: string[] = [];
// SÉCURITÉ : si les règles nécessitent le poids mais qu'il manque, BLOQUER (ne pas passer)
if (rules.weightBased) {
if (!patientWeight || patientWeight <= 0) {
return { valid: false, message: `Poids requis pour ${drug} (médicament en mg/kg)`,
suggestedRange: null, factors: ['weight_missing'] };
}
factors.push('weight');
const maxDose = rules.maxPerKg * patientWeight;
if (dose > maxDose) {
return { valid: false, message: `La dose dépasse le maximum pour ${patientWeight}kg`,
suggestedRange: { min: rules.minPerKg * patientWeight, max: maxDose, unit: rules.unit }, factors };
}
}
// Ajustement selon l'âge (lorsque les règles définissent des tranches d'âge et que l'âge est fourni)
if (rules.ageAdjusted && patientAge !== undefined) {
factors.push('age');
const ageMax = rules.getAgeAdjustedMax(patientAge);
if (dose > ageMax) {
return { valid: false, message: `Dépasse le maximum ajusté selon l'âge pour ${patientAge} ans`,
suggestedRange: { min: rules.typicalMin, max: ageMax, unit: rules.unit }, factors };
}
}
// Ajustement rénal (lorsque les règles définissent des tranches de DFGe et que le DFGe est fourni)
if (rules.renalAdjusted && renalFunction !== undefined) {
factors.push('renal');
const renalMax = rules.getRenalAdjustedMax(renalFunction);
if (dose > renalMax) {
return { valid: false, message: `Dépasse le maximum ajusté selon la fonction rénale pour DFGe ${renalFunction}`,
suggestedRange: { min: rules.typicalMin, max: renalMax, unit: rules.unit }, factors };
}
}
// Maximum absolu
if (dose > rules.absoluteMax) {
return { valid: false, message: `Dépasse le maximum absolu ${rules.absoluteMax}${rules.unit}`,
suggestedRange: { min: rules.typicalMin, max: rules.absoluteMax, unit: rules.unit },
factors: [...factors, 'absolute_max'] };
}
return { valid: true, message: 'Dans les limites',
suggestedRange: { min: rules.typicalMin, max: rules.typicalMax, unit: rules.unit }, factors };
}
Scores cliniques : 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;
}
Les tables de scores doivent correspondre exactement aux spécifications du Royal College of Physicians.
Sévérité des alertes et comportement UI
| Sévérité | Comportement UI | Action clinique requise |
|---|---|---|
| Critique | Bloquer l'action. Modale non désactivable. Rouge. | Doit documenter le motif de dérogation pour continuer |
| Majeure | Bannière d'avertissement en ligne. Orange. | Doit accuser réception avant de continuer |
| Mineure | Note d'information en ligne. Jaune. | Sensibilisation uniquement, aucune action requise |
Les alertes critiques ne doivent JAMAIS être automatiquement fermées ou implémentées sous forme de notifications toast. Les motifs de dérogation doivent être enregistrés dans la piste d'audit.
Tests CDSS (Tolérance zéro pour les faux négatifs)
describe('CDSS — Sécurité des patients', () => {
INTERACTION_PAIRS.forEach(({ drugA, drugB, severity }) => {
it(`détecte ${drugA} + ${drugB} (${severity})`, () => {
const alerts = checkInteractions(drugA, [drugB], []);
expect(alerts.length).toBeGreaterThan(0);
expect(alerts[0].severity).toBe(severity);
});
it(`détecte ${drugB} + ${drugA} (inverse)`, () => {
const alerts = checkInteractions(drugB, [drugA], []);
expect(alerts.length).toBeGreaterThan(0);
});
});
it('bloque un médicament en mg/kg lorsque le poids est manquant', () => {
const result = validateDose('gentamicin', 300, 'iv');
expect(result.valid).toBe(false);
expect(result.factors).toContain('weight_missing');
});
it('gère élégamment les données de médicament malformées', () => {
expect(() => checkInteractions('', [], [])).not.toThrow();
});
});
Critère de réussite : 100%. Une seule interaction manquée est un événement de sécurité patient.
Anti-patrons
- Rendre les vérifications CDSS optionnelles ou contournables sans motif documenté
- Implémenter les vérifications d'interactions sous forme de notifications toast
- Utiliser des types
anypour les données de médicaments ou cliniques - Coder en dur les paires d'interactions au lieu d'utiliser une structure de données maintenable
- Attraper silencieusement les erreurs dans le moteur CDSS (doit signaler les échecs bruyamment)
- Ignorer la validation pondérale lorsque le poids n'est pas disponible (doit bloquer, ne pas passer)
Exemples
Exemple 1 : Vérification d'interaction médicamenteuse
const alerts = checkInteractions('warfarine', ['aspirine', 'metformine'], ['pénicilline']);
// [{ severity: 'critical', pair: ['warfarine', 'aspirine'],
// message: 'Risque accru de saignement', recommendation: 'Éviter la combinaison' }]
Exemple 2 : Validation de dose
const ok = validateDose('paracétamol', 1000, 'oral', 70, 45);
// { valid: true, suggestedRange: { min: 500, max: 4000, unit: 'mg' } }
const bad = validateDose('paracétamol', 5000, 'oral', 70, 45);
// { valid: false, message: 'Dépasse le maximum absolu 4000mg' }
const noWeight = validateDose('gentamicine', 300, 'iv');
// { valid: false, factors: ['weight_missing'] }
Exemple 3 : Score NEWS2
const result = calculateNEWS2({
respiratoryRate: 24, oxygenSaturation: 93, supplementalOxygen: true,
temperature: 38.5, systolicBP: 100, heartRate: 110, consciousness: 'voice'
});
// { total: 13, risk: 'high', escalation: 'Évaluation clinique urgente. Envisager soins intensifs.' }