package rule import ( "fmt" "strings" ) // ======================================================================== // 规则引擎(独立包,不依赖 agent 包) // ======================================================================== // 设计原则: // - 规则引擎是"硬约束",必须独立于 LLM 运行 // - 不依赖 agent 包,避免循环引用 // - 输入是纯文本 + 患者信息,输出是警告列表 + 拦截标志 // // 使用方式: // checker := rule.NewPrescriptionValidator() // warnings, blocked := checker.Validate(prescriptionText, &rule.PatientInfo{...}) // ======================================================================== // ========== 配伍禁忌规则 ========== // eighteenAnti 十八反:绝对禁止同用的药对 // // 中医经典配伍禁忌,源自《神农本草经》: // "甘草反甘遂、大戟、芫花、海藻" // "乌头反贝母、瓜蒌、半夏、白蔹、白及" // "藜芦反人参、沙参、丹参、玄参、苦参、细辛、芍药" var eighteenAnti = map[string][]string{ "甘草": {"甘遂", "大戟", "芫花", "海藻"}, "乌头": {"贝母", "瓜蒌", "半夏", "白蔹", "白及"}, "藜芦": {"人参", "沙参", "丹参", "玄参", "苦参", "细辛", "芍药"}, } // nineteenFear 十九畏:原则上不宜同用 // // 源自《药性论》等文献: // "硫黄畏朴硝,水银畏砒霜,狼毒畏密陀僧..." var nineteenFear = map[string]string{ "硫黄": "朴硝", "水银": "砒霜", "狼毒": "密陀僧", "巴豆": "牵牛", "丁香": "郁金", "牙硝": "三棱", "川乌": "犀角", "草乌": "犀角", "人参": "五灵脂", "官桂": "赤石脂", } // ========== 剂量上限规则(《中国药典》2020版) ========== // // 安全剂量上限(克/日),超过需要特殊标注或禁止。 // 数据来源:《中华人民共和国药典》2020年版 一部 var doseLimit = map[string]float64{ "麻黄": 9, // 常用3-9g,超量可致心悸 "附子": 15, // 常用3-15g,超量有毒 "细辛": 3, // 常用1-3g,超量伤肾 "朱砂": 0.5, // 有毒,极量0.5g "雄黄": 0.1, // 有毒 "大黄": 15, // 常用3-15g "桂枝": 10, // 常用3-10g "石膏": 60, // 常用15-60g "知母": 12, // 常用6-12g "甘草": 10, // 常用2-10g,长期大剂量致水肿 } // ========== 孕妇慎用/禁用药 ========== // // 分为"禁用"(毒性/破血)和"慎用"(需医师评估) // 参考:《中国药典》妊娠禁忌表 var pregnancyRisk = map[string]string{ // 禁用(毒性/破血) "附子": "禁用", "乌头": "禁用", "半夏": "禁用", "南星": "禁用", "桃仁": "禁用", "红花": "禁用", "三棱": "禁用", "莪术": "禁用", "水蛭": "禁用", "虻虫": "禁用", "麝香": "禁用", "巴豆": "禁用", // 慎用 "大黄": "慎用", "芒硝": "慎用", "枳实": "慎用", "肉桂": "慎用", "干姜": "慎用", "益母草": "慎用", } // ========== 患者信息(规则引擎的输入) ========== // // 独立于 agent 包的类型,避免循环依赖。 // agent.PrescriptionRequest 在调用规则引擎时转换为此类型。 type PatientInfo struct { IsPregnant bool // 是否孕妇 Allergies []string // 过敏史 Age int // 年龄(影响剂量) } // ======================================================================== // 病历质控 // ======================================================================== // EMRQualityChecker 病历质控检查器 // // 独立于 LLM,做硬规则校验。 // 检查项:必填字段、术语规范、格式合规。 type EMRQualityChecker struct{} // NewEMRQualityChecker 创建病历质控器 func NewEMRQualityChecker() *EMRQualityChecker { return &EMRQualityChecker{} } // Check 检查病历质量,返回问题列表 // // 检查项: // 1. 必须包含「主诉」 // 2. 必须包含「舌象」描述 // 3. 必须包含「脉象」描述 // 4. 必须包含「诊断」 // 5. 口语术语建议替换为标准术语 func (c *EMRQualityChecker) Check(emrText string) []string { issues := make([]string, 0) // 规则1:必须包含主诉 if !strings.Contains(emrText, "主诉") && !strings.Contains(emrText, "主 诉") { issues = append(issues, "【缺失】病历缺少「主诉」字段") } // 规则2:必须包含舌象 if !strings.Contains(emrText, "舌") { issues = append(issues, "【缺失】病历缺少「舌象」描述") } // 规则3:必须包含脉象 if !strings.Contains(emrText, "脉") { issues = append(issues, "【缺失】病历缺少「脉象」描述") } // 规则4:必须包含诊断 if !strings.Contains(emrText, "诊断") { issues = append(issues, "【缺失】病历缺少「诊断」字段") } // 规则5:术语规范化检查(口语→标准术语) slangMap := map[string]string{ "胃不舒服": "胃脘不适", "头晕": "眩晕", "心慌": "心悸", "睡不着": "失眠", "吃不下": "纳差", "拉肚子": "泄泻", } for slang, standard := range slangMap { if strings.Contains(emrText, slang) { issues = append(issues, fmt.Sprintf("【术语】建议将「%s」改为标准术语「%s」", slang, standard)) } } return issues } // ======================================================================== // 处方校验 // ======================================================================== // PrescriptionValidator 处方校验器 // // 核心安全组件,执行所有硬性规则检查。 // 任何处方在到达医生审核前,必须通过此校验。 type PrescriptionValidator struct{} // NewPrescriptionValidator 创建处方校验器 func NewPrescriptionValidator() *PrescriptionValidator { return &PrescriptionValidator{} } // Validate 校验处方,返回 (警告列表, 是否被硬规则拦截) // // 参数: // prescription - 处方文本(自然语言或结构化均可) // patient - 患者信息(孕妇/过敏/年龄) // // 返回: // warnings - 所有警告信息(含拦截原因) // blocked - true 表示被硬规则拦截,处方不可通过 // // 检查顺序: // ① 十八反 → ② 十九畏 → ③ 孕妇禁忌 → ④ 过敏冲突 → ⑤ 剂量提醒 func (v *PrescriptionValidator) Validate(prescription string, patient *PatientInfo) ([]string, bool) { warnings := make([]string, 0) blocked := false // ===== 规则1:十八反检查(硬拦截) ===== // 经典配伍禁忌,绝对禁止 for herb, conflicts := range eighteenAnti { if strings.Contains(prescription, herb) { for _, c := range conflicts { if strings.Contains(prescription, c) { warnings = append(warnings, fmt.Sprintf("【十八反-拦截】%s 反 %s,禁止同用!", herb, c)) blocked = true } } } } // ===== 规则2:十九畏检查(硬拦截) ===== // 原则上不宜同用 for a, b := range nineteenFear { if strings.Contains(prescription, a) && strings.Contains(prescription, b) { warnings = append(warnings, fmt.Sprintf("【十九畏-拦截】%s 畏 %s,原则不宜同用!", a, b)) blocked = true } } // ===== 规则3:孕妇禁忌检查(硬拦截/警告) ===== if patient != nil && patient.IsPregnant { for herb, risk := range pregnancyRisk { if strings.Contains(prescription, herb) { if risk == "禁用" { warnings = append(warnings, fmt.Sprintf("【孕妇安全-拦截】孕妇禁用 %s!", herb)) blocked = true } else { warnings = append(warnings, fmt.Sprintf("【孕妇安全-警告】孕妇慎用 %s", herb)) } } } } // ===== 规则4:过敏史检查(硬拦截) ===== if patient != nil { for _, allergen := range patient.Allergies { if allergen != "" && strings.Contains(prescription, allergen) { warnings = append(warnings, fmt.Sprintf("【过敏-拦截】处方含过敏原 %s!", allergen)) blocked = true } } } // ===== 规则5:剂量超限检查(仅警告,不拦截) ===== // 生产环境应解析处方中的精确剂量做对比 for herb, limit := range doseLimit { if strings.Contains(prescription, herb) { warnings = append(warnings, fmt.Sprintf("【剂量提醒】%s 用量请控制在 %.0fg 以内(药典上限)", herb, limit)) } } return warnings, blocked }