Files
xk-ai-agent/internal/rule/rule_engine.go
2026-08-14 21:50:48 +08:00

235 lines
8.1 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
}