235 lines
8.1 KiB
Go
235 lines
8.1 KiB
Go
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
|
||
}
|