Install any skill in seconds. Free to start, no credit card required.
Get Started Free →臨床意思決定支援システム(CDSS)パターン、医学的推論、およびエビデンスベースの実装。
.claude/skills/affaan-m-healthcare-cdss-patterns/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-14 | ✗→✓ | ▲ Improved | 83% | 0% |
| case-02 | ✗→✓ | ▲ Improved | 36% | 0% |
| case-06 | ✗→✓ | ▲ Improved | 38% | 0% |
| case-07 | ✗→✓ | ▲ Improved | 101% | 0% |
| case-08 | ✗→✓ | ▲ Improved | 100% | 0% |
构建可集成至EMR工作流的临床决策支持系统的模式。CDSS模块关乎患者安全——对假阴性零容忍。
CDSS引擎是一个无副作用的纯函数库。输入临床数据,输出警报。这使得它完全可测试。
三个核心模块:
checkInteractions(newDrug, currentMeds, allergies) — 检查新药物与现有用药及已知过敏的冲突。返回按严重程度排序的InteractionAlert[]。使用DrugInteractionPair数据模型。validateDose(drug, dose, route, weight, age, renalFunction) — 根据体重、年龄和肾功能调整规则验证处方剂量。返回DoseValidationResult。calculateNEWS2(vitals) — 基于NEWS2Input计算国家早期预警评分2。返回包含总分、风险等级和升级指导的NEWS2Result。EMR UI
↓ (用户输入数据)
CDSS 引擎(纯函数,无副作用)
├── 药物相互作用检查器
├── 剂量验证器
├── 临床评分(NEWS2、qSOFA 等)
└── 警报分类器
↓ (返回警报)
EMR UI(内联显示警报,严重时阻止操作)typescriptinterface DrugInteractionPair { drugA: string; // generic name drugB: string; // generic 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: `Cross-reactivity with documented allergy: ${allergy}`, recommendation: 'Do not prescribe without allergy consultation' }); } } return alerts.sort((a, b) => severityOrder(a.severity) - severityOrder(b.severity)); }
相互作用对必须双向:若药物A与药物B相互作用,则药物B与药物A相互作用。
typescriptinterface 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[] = []; // SAFETY: if rules require weight but weight missing, BLOCK (not pass) if (rules.weightBased) { if (!patientWeight || patientWeight <= 0) { return { valid: false, message: `Weight required for ${drug} (mg/kg drug)`, suggestedRange: null, factors: ['weight_missing'] }; } factors.push('weight'); const maxDose = rules.maxPerKg * patientWeight; if (dose > maxDose) { return { valid: false, message: `Dose exceeds max for ${patientWeight}kg`, suggestedRange: { min: rules.minPerKg * patientWeight, max: maxDose, unit: rules.unit }, factors }; } } // Age-based adjustment (when rules define age brackets and age is provided) if (rules.ageAdjusted && patientAge !== undefined) { factors.push('age'); const ageMax = rules.getAgeAdjustedMax(patientAge); if (dose > ageMax) { return { valid: false, message: `Exceeds age-adjusted max for ${patientAge}yr`, suggestedRange: { min: rules.typicalMin, max: ageMax, unit: rules.unit }, factors }; } } // Renal adjustment (when rules define eGFR brackets and eGFR is provided) if (rules.renalAdjusted && renalFunction !== undefined) { factors.push('renal'); const renalMax = rules.getRenalAdjustedMax(renalFunction); if (dose > renalMax) { return { valid: false, message: `Exceeds renal-adjusted max for eGFR ${renalFunction}`, suggestedRange: { min: rules.typicalMin, max: renalMax, unit: rules.unit }, factors }; } } // Absolute max if (dose > rules.absoluteMax) { return { valid: false, message: `Exceeds absolute max ${rules.absoluteMax}${rules.unit}`, suggestedRange: { min: rules.typicalMin, max: rules.absoluteMax, unit: rules.unit }, factors: [...factors, 'absolute_max'] }; } return { valid: true, message: 'Within range', suggestedRange: { min: rules.typicalMin, max: rules.typicalMax, unit: rules.unit }, factors }; }
typescriptinterface 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; }
评分表必须严格符合皇家内科医师学会规范。
| 严重程度 | UI行为 | 临床医生操作要求 | |----------|--------|------------------| | 危急 | 阻止操作。不可关闭的模态框。红色。 | 必须记录覆盖原因才能继续 | | 主要 | 行内警告横幅。橙色。 | 必须确认后才能继续 | | 次要 | 行内信息提示。黄色。 | 仅需知晓,无需操作 |
危急警报绝不能自动关闭或实现为Toast通知。覆盖原因必须存储在审计追踪中。
typescriptdescribe('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('weight_missing'); }); it('handles malformed drug data gracefully', () => { expect(() => checkInteractions('', [], [])).not.toThrow(); }); });
通过标准:100%。一次遗漏的相互作用即构成患者安全事件。
any类型处理药物或临床数据typescriptconst alerts = checkInteractions('warfarin', ['aspirin', 'metformin'], ['penicillin']); // [{ severity: 'critical', pair: ['warfarin', 'aspirin'], // message: 'Increased bleeding risk', recommendation: 'Avoid combination' }]
typescriptconst 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: 'Exceeds absolute max 4000mg' } const noWeight = validateDose('gentamicin', 300, 'iv'); // { valid: false, factors: ['weight_missing'] }
typescriptconst result = calculateNEWS2({ respiratoryRate: 24, oxygenSaturation: 93, supplementalOxygen: true, temperature: 38.5, systolicBP: 100, heartRate: 110, consciousness: 'voice' }); // { total: 13, risk: 'high', escalation: 'Urgent clinical review. Consider ICU.' }
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-21 | pass→pass | 25,397 | 42,080 | +66% | 1 | 1 | 0% | 4,773 | 8,017 | +68% | 0 | 0 | — |
case-14 | fail→pass | 11,939 | 4,767 | -60% | 1 | 1 | 0% | 1,779 | 3,258 | +83% | 0 | 0 | — |
case-15 | pass→pass | 11,718 | 4,073 | -65% | 1 | 1 | 0% | 1,837 | 3,092 | +68% | 0 | 0 | — |
case-20 | pass→pass | 22,590 | 18,578 | -18% | 1 | 1 | 0% | 4,334 | 5,926 | +37% | 0 | 0 | — |
case-01 | fail→fail | 20,539 | 20,034 | -2% | 1 | 1 | 0% | 4,113 | 6,440 | +57% | 0 | 0 | — |
case-02 | fail→pass | 20,808 | 18,091 | -13% | 1 | 1 | 0% | 4,612 | 6,265 | +36% | 0 | 0 | — |
case-03 | fail→fail | 27,058 | 21,829 | -19% | 1 | 1 | 0% | 5,950 | 7,273 | +22% | 0 | 0 | — |
case-04 | pass→pass | 13,934 | 12,536 | -10% | 1 | 1 | 0% | 2,439 | 4,609 | +89% | 0 | 0 | — |
case-05 | pass→pass | 9,855 | 8,976 | -9% | 1 | 1 | 0% | 1,803 | 3,964 | +120% | 0 | 0 | — |
case-06 | fail→pass | 12,867 | 3,388 | -74% | 1 | 1 | 0% | 2,242 | 3,101 | +38% | 0 | 0 | — |
case-07 | fail→pass | 10,263 | 3,602 | -65% | 1 | 1 | 0% | 1,475 | 2,966 | +101% | 0 | 0 | — |
case-08 | fail→pass | 14,244 | 11,720 | -18% | 1 | 1 | 0% | 2,001 | 4,001 | +100% | 0 | 0 | — |
case-09 | fail→pass | 11,292 | 2,979 | -74% | 1 | 1 | 0% | 1,765 | 2,997 | +70% | 0 | 0 | — |
case-10 | fail→pass | 9,936 | 2,824 | -72% | 1 | 1 | 0% | 1,572 | 3,003 | +91% | 0 | 0 | — |
case-11 | pass→pass | 8,926 | 2,469 | -72% | 1 | 1 | 0% | 1,548 | 2,901 | +87% | 0 | 0 | — |
case-12 | pass→pass | 9,435 | 8,951 | -5% | 1 | 1 | 0% | 1,478 | 3,879 | +162% | 0 | 0 | — |
case-13 | pass→pass | 10,146 | 3,534 | -65% | 1 | 1 | 0% | 1,459 | 3,017 | +107% | 0 | 0 | — |
case-16 | fail→fail | 13,331 | 8,237 | -38% | 1 | 1 | 0% | 2,016 | 3,637 | +80% | 0 | 0 | — |
case-17 | pass→pass | 16,027 | 12,600 | -21% | 1 | 1 | 0% | 2,432 | 4,348 | +79% | 0 | 0 | — |
case-18 | pass→pass | 16,348 | 15,251 | -7% | 1 | 1 | 0% | 2,354 | 4,828 | +105% | 0 | 0 | — |
case-19 | pass→pass | 12,491 | 8,631 | -31% | 1 | 1 | 0% | 1,866 | 3,786 | +103% | 0 | 0 | — |
case-22 | pass→pass | 16,343 | 23,417 | +43% | 1 | 1 | 0% | 3,253 | 6,696 | +106% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +32 percentage points is the difference between those two pass rates over the 22 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.