183 lines
6.2 KiB
Go
183 lines
6.2 KiB
Go
package intent
|
||
|
||
import (
|
||
"regexp"
|
||
"strings"
|
||
)
|
||
|
||
// matchKnowledgeQuery 匹配法规标准、标杆案例与权限查询
|
||
func matchKnowledgeQuery(input string) *CandidateIntent {
|
||
// 排除事实参数补充的输入场景
|
||
excludeKeywords := []string{"客户说", "客户反馈", "客户补充", "补充说", "补充", "修改为", "改为", "增加"}
|
||
for _, ek := range excludeKeywords {
|
||
if strings.Contains(input, ek) {
|
||
return nil
|
||
}
|
||
}
|
||
|
||
// 1. 用户权限查询 (IntentQueryUserRights)
|
||
userRightsPatterns := []string{
|
||
"我的权限", "我有什么权限", "我负责什么", "我的角色", "查看我的职责",
|
||
"查询权限", "当前权限", "我在这个项目的角色", "法定角色", "法定职责", "我的职责",
|
||
"文档权限", "文档职责", "权限和职责", "职责和权限", "负责哪些文档", "权限",
|
||
}
|
||
for _, p := range userRightsPatterns {
|
||
if strings.Contains(input, p) {
|
||
return &CandidateIntent{
|
||
Intent: IntentQueryUserRights,
|
||
Confidence: 0.95,
|
||
FilledSlots: map[string]string{
|
||
SlotQueryTopic: "user_rights",
|
||
},
|
||
Reasoning: "用户查询自身在项目及受控文档中的法定职责与操作权限",
|
||
}
|
||
}
|
||
}
|
||
|
||
// 2. 标杆/相似案例查询 (IntentQuerySimilarCases)
|
||
similarCasesPatterns := []string{
|
||
"参考案例", "往期经验", "类似项目", "相似案例", "同类产品",
|
||
"同类项目", "以往案例", "标杆案例", "同类获批", "竞品获批案例",
|
||
"注册案例", "申报案例",
|
||
}
|
||
for _, p := range similarCasesPatterns {
|
||
if strings.Contains(input, p) {
|
||
return &CandidateIntent{
|
||
Intent: IntentQuerySimilarCases,
|
||
Confidence: 0.92,
|
||
FilledSlots: map[string]string{
|
||
SlotQueryTopic: "similar_cases",
|
||
},
|
||
Reasoning: "用户查询以往类似产品的注册申报、上市审评或标杆案例与经验",
|
||
}
|
||
}
|
||
}
|
||
|
||
// 3. 法规强标查询 (IntentQueryRegulations)
|
||
regKeywords := []string{
|
||
"gb 9706", "gb9706", "yy 0505", "yy0505", "强制性标准", "强标",
|
||
"技术审评指导原则", "指导原则", "合规要求", "注册标准",
|
||
}
|
||
lower := strings.ToLower(input)
|
||
for _, rk := range regKeywords {
|
||
if strings.Contains(lower, rk) {
|
||
return &CandidateIntent{
|
||
Intent: IntentQueryRegulations,
|
||
Confidence: 0.92,
|
||
FilledSlots: map[string]string{
|
||
SlotQueryTopic: "regulations",
|
||
},
|
||
Reasoning: "用户咨询医疗器械适用的国家标准、行业强标或审评指导原则法规",
|
||
}
|
||
}
|
||
}
|
||
|
||
if (strings.Contains(lower, "法规") || strings.Contains(lower, "标准") || strings.Contains(lower, "yy") || strings.Contains(lower, "fda") || strings.Contains(lower, "ce")) &&
|
||
(strings.Contains(lower, "查") || strings.Contains(lower, "哪些") || strings.Contains(lower, "满足") || strings.Contains(lower, "要求") || strings.Contains(lower, "规范")) {
|
||
return &CandidateIntent{
|
||
Intent: IntentQueryRegulations,
|
||
Confidence: 0.90,
|
||
FilledSlots: map[string]string{
|
||
SlotQueryTopic: "regulations",
|
||
},
|
||
Reasoning: "用户咨询医疗器械适用的国家标准、行业强标或审评指导原则法规",
|
||
}
|
||
}
|
||
|
||
if cand := matchMaterialRequest(input); cand != nil {
|
||
return cand
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// matchMaterialRequest 识别说明书、操作视频、竞品资料等获取请求。
|
||
// 写动词(生成/编写)留给任务触发,避免把「编写说明书」误判成检索。
|
||
func matchMaterialRequest(input string) *CandidateIntent {
|
||
if hasTaskWriteVerb(input) || hasWeakTaskWriteVerb(input) {
|
||
return nil
|
||
}
|
||
for _, v := range []string{"撰写", "写一", "输出"} {
|
||
if strings.Contains(input, v) {
|
||
return nil
|
||
}
|
||
}
|
||
patterns := []string{
|
||
"说明书", "操作视频", "产品视频", "彩页", "规格书",
|
||
"使用手册", "操作手册", "产品手册", "手册",
|
||
"上一代", "前代", "上代产品",
|
||
"竞品", "同类产品资料",
|
||
}
|
||
for _, p := range patterns {
|
||
if strings.Contains(input, p) {
|
||
return &CandidateIntent{
|
||
Intent: IntentQuerySimilarCases,
|
||
Confidence: 0.82,
|
||
FilledSlots: map[string]string{
|
||
SlotQueryTopic: "materials",
|
||
},
|
||
Reasoning: "用户在索取说明书、操作视频、竞品或上一代产品资料,走知识检索",
|
||
}
|
||
}
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// extractDocName 从文本或预设映射中提取目标受控文档名称/标识
|
||
func extractDocName(input string) string {
|
||
reDocID := regexp.MustCompile(`(doc-[a-zA-Z0-9_-]+)`)
|
||
if match := reDocID.FindString(input); match != "" {
|
||
return match
|
||
}
|
||
|
||
docNames := []string{
|
||
"用户初始需求收集表", "用户初始需求表", "用户需求收集表", "需求收集表", "需求表",
|
||
"技术与市场调研报告", "调研报告",
|
||
"法规与注册论证报告", "法规评估", "注册评估",
|
||
"初步设计与架构方案", "初步设计", "设计方案",
|
||
"CDO报价单", "初步报价单", "报价单",
|
||
}
|
||
for _, name := range docNames {
|
||
if strings.Contains(input, name) {
|
||
return name
|
||
}
|
||
}
|
||
|
||
reBa := regexp.MustCompile(`把\s*(.+?)\s*(?:审核驳回|终审驳回|驳回|审核通过|批准|认领|送审|提交审核)`)
|
||
if match := reBa.FindStringSubmatch(input); len(match) > 1 {
|
||
return strings.TrimSpace(match[1])
|
||
}
|
||
|
||
return ""
|
||
}
|
||
|
||
// extractRejectReason 从用户输入中提取驳回意见或批注内容
|
||
func extractRejectReason(input string) string {
|
||
reReason := regexp.MustCompile(`(?:意见为|意见是|意见|理由为|理由是|理由|原因|批注为|批注|修改意见)[::\s]\s*(.+)`)
|
||
if match := reReason.FindStringSubmatch(input); len(match) > 1 {
|
||
res := strings.TrimSpace(match[1])
|
||
return strings.Trim(res, ",,。;; ")
|
||
}
|
||
|
||
reAfterReject := regexp.MustCompile(`驳回[,,::\s]+(.+)`)
|
||
if match := reAfterReject.FindStringSubmatch(input); len(match) > 1 {
|
||
res := strings.TrimSpace(match[1])
|
||
res = regexp.MustCompile(`^(?:意见为|意见是|意见|理由为|理由是|理由|原因|批注为|批注|修改意见)[::\s]\s*`).ReplaceAllString(res, "")
|
||
res = strings.Trim(res, ",,。;; ")
|
||
if res != "" {
|
||
return res
|
||
}
|
||
}
|
||
|
||
return ""
|
||
}
|
||
|
||
// extractSection 从文本中抽取目标章节线索 (如 "第3节", "3.1节")
|
||
func extractSection(input string) string {
|
||
reSection := regexp.MustCompile(`(第[0-9一二三四五六七八九十]+[章节条部分]+|[0-9]+\.[0-9]+[节章]*)`)
|
||
if match := reSection.FindString(input); match != "" {
|
||
return match
|
||
}
|
||
return ""
|
||
}
|