| name | healthcare-cdss-patterns |
| description | 임상 의사결정지원시스템(CDSS) 개발 패턴입니다. 약물 상호작용 검사, 용량 검증, 임상 점수(NEWS2, qSOFA), 경고 심각도 분류, EMR 워크플로 통합을 다룹니다. |
| origin | Health1 Super Speciality Hospitals — contributed by Dr. Keyur Patel |
| version | 1.0.0 |
의료 CDSS 개발 패턴
EMR 워크플로에 통합되는 임상 의사결정지원시스템을 만들기 위한 패턴입니다. CDSS 모듈은 환자 안전 핵심 영역이므로 false negative에 대한 허용치는 0입니다.
사용 시점
- 약물 상호작용 검사를 구현할 때
- 용량 검증 엔진을 만들 때
- 임상 점수 시스템(NEWS2, qSOFA, APACHE, GCS)을 구현할 때
- 비정상 임상 수치 경고 시스템을 설계할 때
- 안전 점검이 들어간 약물 처방 입력 기능을 만들 때
- 검사 결과 해석을 임상 맥락과 통합할 때
동작 방식
CDSS 엔진은 부작용이 전혀 없는 순수 함수 라이브러리입니다. 임상 데이터를 입력하면 경고를 출력합니다. 이 구조 덕분에 완전한 테스트가 가능합니다.
주요 모듈 세 가지:
checkInteractions(newDrug, currentMeds, allergies) — 신규 약물을 현재 약물 및 알레르기와 비교합니다. 심각도 순으로 정렬된 InteractionAlert[]를 반환합니다. DrugInteractionPair 데이터 모델을 사용합니다.
validateDose(drug, dose, route, weight, age, renalFunction) — 처방 용량이 체중 기반, 연령 보정, 신기능 보정 규칙을 만족하는지 검증합니다. DoseValidationResult를 반환합니다.
calculateNEWS2(vitals) — NEWS2Input으로부터 National Early Warning Score 2를 계산합니다. 총점, 위험 수준, 에스컬레이션 지침이 담긴 NEWS2Result를 반환합니다.
EMR UI
↓ (user enters data)
CDSS Engine (pure functions, no side effects)
├── Drug Interaction Checker
├── Dose Validator
├── Clinical Scoring (NEWS2, qSOFA, etc.)
└── Alert Classifier
↓ (returns alerts)
EMR UI (displays alerts inline, blocks if critical)
약물 상호작용 검사
interface DrugInteractionPair {
drugA: string;
drugB: string;
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) {
((newDrug, allergy)) {
alerts.({ : , : [newDrug, allergy],
: ,
: });
}
}
alerts.( (a.) - (b.));
}
상호작용 쌍은 반드시 양방향이어야 합니다. Drug A가 Drug B와 상호작용한다면, Drug B도 Drug A와 상호작용해야 합니다.
용량 검증
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: 'No validation rules available', suggestedRange: null, factors: [] };
const factors: string[] = [];
if (rules.weightBased) {
(!patientWeight || patientWeight <= ) {
{ : , : ,
: , : [] };
}
factors.();
maxDose = rules. * patientWeight;
(dose > maxDose) {
{ : , : ,
: { : rules. * patientWeight, : maxDose, : rules. }, factors };
}
}
(rules. && patientAge !== ) {
factors.();
ageMax = rules.(patientAge);
(dose > ageMax) {
{ : , : ,
: { : rules., : ageMax, : rules. }, factors };
}
}
(rules. && renalFunction !== ) {
factors.();
renalMax = rules.(renalFunction);
(dose > renalMax) {
{ : , : ,
: { : rules., : renalMax, : rules. }, factors };
}
}
(dose > rules.) {
{ : , : ,
: { : rules., : rules., : rules. },
: [...factors, ] };
}
{ : , : ,
: { : rules., : rules., : rules. }, factors };
}
임상 점수: NEWS2
interface NEWS2Input {
respiratoryRate: number; oxygenSaturation: number; supplementalOxygen: boolean;
temperature: number; systolicBP: number; heartRate: number;
consciousness: 'alert' | 'voice' | 'pain' | 'unresponsive';
}
interface NEWS2Result {
total: number;
risk: 'low' | 'low-medium' | 'medium' | 'high';
components: Record<string, number>;
escalation: string;
}
점수표는 Royal College of Physicians 명세와 정확히 일치해야 합니다.
경고 심각도와 UI 동작
| Severity | UI 동작 | 임상의 조치 필요 여부 |
|---|
| Critical | 작업 차단. 닫을 수 없는 모달. 빨강. | 진행하려면 override 이유를 반드시 기록 |
| Major | 인라인 경고 배너. 주황. | 진행 전 acknowledge 필요 |
| Minor | 인라인 정보 노트. 노랑. | 참고용, 별도 조치 불필요 |
Critical 경고는 절대 자동 dismiss되면 안 되고, toast로 구현해서도 안 됩니다. Override 사유는 반드시 audit trail에 저장해야 합니다.
CDSS 테스트(false negative 허용치 0)
describe('CDSS — Patient Safety', () => {
INTERACTION_PAIRS.forEach(({ drugA, drugB, severity }) => {
it(`detects ${drugA} + ${drugB} (${severity})`, () => {
const alerts = checkInteractions(drugA, [drugB], []);
expect(alerts.length).toBeGreaterThan(0);
expect(alerts[0].severity).toBe(severity);
});
it(`detects ${drugB} + ${drugA} (reverse)`, () => {
const alerts = checkInteractions(drugB, [drugA], []);
expect(alerts.length).toBeGreaterThan(0);
});
});
it('blocks mg/kg drug when weight is missing', () => {
const result = validateDose('gentamicin', 300, 'iv');
expect(result.valid).toBe(false);
expect(result.factors).toContain();
});
(, {
( (, [], []))..();
});
});
통과 기준은 100%입니다. 상호작용 하나라도 놓치면 환자 안전 사고입니다.
안티패턴
- 문서화된 이유 없이 CDSS 점검을 옵션화하거나 건너뛰게 하는 것
- 상호작용 경고를 toast notification으로 구현하는 것
- 약물/임상 데이터에
any 타입을 사용하는 것
- 유지보수 가능한 데이터 구조 없이 상호작용 쌍을 하드코딩하는 것
- CDSS 엔진의 오류를 조용히 삼키는 것
- 체중 정보가 없는데 체중 기반 검증을 건너뛰고 통과시키는 것
예시
예시 1: 약물 상호작용 검사
const alerts = checkInteractions('warfarin', ['aspirin', 'metformin'], ['penicillin']);
예시 2: 용량 검증
const ok = validateDose('paracetamol', 1000, 'oral', 70, 45);
const bad = validateDose('paracetamol', 5000, 'oral', 70, 45);
const noWeight = validateDose('gentamicin', 300, 'iv');
예시 3: NEWS2 점수 계산
const result = calculateNEWS2({
respiratoryRate: 24, oxygenSaturation: 93, supplementalOxygen: true,
temperature: 38.5, systolicBP: 100, heartRate: 110, consciousness: 'voice'
});