Wire intent→coordinator DocResolver confirm cards→action_executor kinds for comment, reopen, pending-supplements query, and real submit-review execution.
564 lines
23 KiB
Go
564 lines
23 KiB
Go
package intent
|
||
|
||
import (
|
||
"fmt"
|
||
"regexp"
|
||
"strings"
|
||
)
|
||
|
||
// matchDocGovAction 匹配法定受控文档治理流转动作与槽位提取
|
||
func matchDocGovAction(input string, userCtx *ContextSnapshot) *CandidateIntent {
|
||
// 0. 询问驳回原因:归属 DECISION_QUERY,不得路由为 DOCGOV_*_REJECT
|
||
if (strings.Contains(input, "驳回") || strings.Contains(input, "退回")) && isRejectReasonQuery(input) {
|
||
slots := make(map[string]string)
|
||
fillTargetDocSlot(slots, input, userCtx)
|
||
if q := scopeMismatchClarify(slots, activeWorkflowKeyOf(userCtx)); q != "" {
|
||
return &CandidateIntent{
|
||
Intent: IntentQueryRejectReason,
|
||
Confidence: 0.96,
|
||
Branch: BranchNewIntentPreempt,
|
||
FilledSlots: slots,
|
||
Reasoning: "用户询问的受控文档属于非激活工作流,前置剪枝为只读询问并给出切换引导",
|
||
ClarificationQuestion: q,
|
||
}
|
||
}
|
||
return &CandidateIntent{
|
||
Intent: IntentQueryRejectReason,
|
||
Confidence: 0.96,
|
||
FilledSlots: slots,
|
||
Reasoning: "用户询问受控文档驳回/退回原因,路由为 QUERY_REJECT_REASON",
|
||
}
|
||
}
|
||
|
||
// 跨工作流作用域前置剪枝:治理写动作显式点名他流文档时,拦截并引导切换,不落入任何 DOCGOV_* 写意图。
|
||
if cand := docGovScopePreempt(input, userCtx); cand != nil {
|
||
return cand
|
||
}
|
||
|
||
// 1. 驳回 (Review Reject or Final Reject)
|
||
if strings.Contains(input, "驳回") {
|
||
isFinal := false
|
||
if strings.Contains(input, "终审") || strings.Contains(input, "批准") {
|
||
isFinal = true
|
||
} else if userCtx != nil && (userCtx.DocLifecycleState == "approving" || userCtx.UserDocRole == "approver") {
|
||
isFinal = true
|
||
}
|
||
|
||
targetIntent := IntentDocGovReviewReject
|
||
if isFinal {
|
||
targetIntent = IntentDocGovFinalReject
|
||
}
|
||
|
||
slots := make(map[string]string)
|
||
|
||
// target_doc
|
||
targetDoc := resolveTargetDoc(input, userCtx)
|
||
if targetDoc != "" {
|
||
slots[SlotTargetDoc] = targetDoc
|
||
}
|
||
|
||
// target_section
|
||
section := extractSection(input)
|
||
if section != "" {
|
||
slots[SlotTargetSection] = section
|
||
}
|
||
|
||
// reject_reason
|
||
reason := extractRejectReason(input)
|
||
if reason != "" {
|
||
slots[SlotRejectReason] = reason
|
||
}
|
||
|
||
confidence := 0.95
|
||
reasoning := fmt.Sprintf("用户下达法定治理驳回指令,目标意图为 %s", targetIntent)
|
||
clarifyQ := ""
|
||
|
||
// Stage 0 权限与状态前置剪枝校验
|
||
if userCtx != nil {
|
||
if !isFinal && userCtx.UserDocRole != "" && userCtx.UserDocRole != "reviewer" && userCtx.UserDocRole != "system" {
|
||
confidence = 0.90
|
||
reasoning = fmt.Sprintf("前置剪枝:当前用户角色不是审核人(UserDocRole: %s),权限不符,无法执行审核驳回", userCtx.UserDocRole)
|
||
clarifyQ = fmt.Sprintf("您当前在受控文档中的角色为【%s】,不具备审核驳回权限(需审核人角色)。", userCtx.UserDocRole)
|
||
} else if isFinal && userCtx.UserDocRole != "" && userCtx.UserDocRole != "approver" && userCtx.UserDocRole != "system" {
|
||
confidence = 0.90
|
||
reasoning = fmt.Sprintf("前置剪枝:当前用户角色不是批准人(UserDocRole: %s),权限不符,无法执行终审驳回", userCtx.UserDocRole)
|
||
clarifyQ = fmt.Sprintf("您当前在受控文档中的角色为【%s】,不具备终审批准驳回权限(需批准人角色)。", userCtx.UserDocRole)
|
||
}
|
||
|
||
if userCtx.DocLifecycleState == "drafting" {
|
||
confidence = 0.90
|
||
reasoning = "前置剪枝:当前文档处于编制草稿阶段(drafting),尚未提交审核,无法执行驳回操作"
|
||
clarifyQ = "当前文档处于编制中(drafting),尚未送审,无需驳回。"
|
||
}
|
||
}
|
||
|
||
return &CandidateIntent{
|
||
Intent: targetIntent,
|
||
Confidence: confidence,
|
||
Branch: BranchNewIntentPreempt,
|
||
FilledSlots: slots,
|
||
Reasoning: reasoning,
|
||
ClarificationQuestion: clarifyQ,
|
||
}
|
||
}
|
||
|
||
// 2. 终审批准 (Final Approve) — 排除重开语义,避免「批准重开」误入终审
|
||
if strings.Contains(input, "终审批准") || strings.Contains(input, "批准发布") || strings.Contains(input, "批准生效") ||
|
||
strings.Contains(input, "同意发布") || (strings.Contains(input, "批准") && !strings.Contains(input, "不") &&
|
||
!strings.Contains(input, "重开") && !strings.Contains(input, "再打开") && !strings.Contains(input, "再修订")) {
|
||
targetIntent := IntentDocGovFinalApprove
|
||
slots := make(map[string]string)
|
||
targetDoc := resolveTargetDoc(input, userCtx)
|
||
if targetDoc != "" {
|
||
slots[SlotTargetDoc] = targetDoc
|
||
}
|
||
|
||
confidence := 0.95
|
||
reasoning := "用户下达受控文档终审批准生效指令"
|
||
clarifyQ := ""
|
||
|
||
if userCtx != nil {
|
||
if userCtx.UserDocRole != "" && userCtx.UserDocRole != "approver" && userCtx.UserDocRole != "system" {
|
||
confidence = 0.90
|
||
reasoning = fmt.Sprintf("前置剪枝:当前用户角色不是批准人(UserDocRole: %s),权限不符,无法执行终审批准", userCtx.UserDocRole)
|
||
clarifyQ = fmt.Sprintf("您当前在受控文档中的角色为【%s】,不具备终审批准权限(需批准人角色)。", userCtx.UserDocRole)
|
||
} else if userCtx.DocLifecycleState != "" && userCtx.DocLifecycleState != "approving" {
|
||
confidence = 0.90
|
||
reasoning = fmt.Sprintf("前置剪枝:当前文档处于【%s】阶段,尚未进入终审批准阶段(approving),无法执行终审批准", userCtx.DocLifecycleState)
|
||
clarifyQ = fmt.Sprintf("当前文档处于【%s】阶段,未进入终审阶段,无法执行终审批准。", userCtx.DocLifecycleState)
|
||
}
|
||
}
|
||
|
||
return &CandidateIntent{
|
||
Intent: targetIntent,
|
||
Confidence: confidence,
|
||
Branch: BranchNewIntentPreempt,
|
||
FilledSlots: slots,
|
||
Reasoning: reasoning,
|
||
ClarificationQuestion: clarifyQ,
|
||
}
|
||
}
|
||
|
||
// 3. 审核通过 (Review Approve)
|
||
if strings.Contains(input, "审核通过") || strings.Contains(input, "审阅通过") || strings.Contains(input, "评审通过") ||
|
||
(strings.Contains(input, "通过") && (strings.Contains(input, "审核") || strings.Contains(input, "审阅") || strings.Contains(input, "报告") || strings.Contains(input, "准予") || strings.Contains(input, "环节"))) {
|
||
if !strings.Contains(input, "下一阶段") && !strings.Contains(input, "推进") {
|
||
targetIntent := IntentDocGovReviewApprove
|
||
slots := make(map[string]string)
|
||
targetDoc := resolveTargetDoc(input, userCtx)
|
||
if targetDoc != "" {
|
||
slots[SlotTargetDoc] = targetDoc
|
||
}
|
||
|
||
confidence := 0.95
|
||
reasoning := "用户下达受控文档审核通过指令"
|
||
clarifyQ := ""
|
||
|
||
if userCtx != nil {
|
||
if userCtx.UserDocRole != "" && userCtx.UserDocRole != "reviewer" && userCtx.UserDocRole != "system" {
|
||
confidence = 0.90
|
||
reasoning = fmt.Sprintf("前置剪枝:当前用户角色不是审核人(UserDocRole: %s),权限不符,无法执行审核通过", userCtx.UserDocRole)
|
||
clarifyQ = fmt.Sprintf("您当前在受控文档中的角色为【%s】,不具备审核通过权限(需审核人角色)。", userCtx.UserDocRole)
|
||
} else if userCtx.DocLifecycleState != "" && userCtx.DocLifecycleState != "reviewing" {
|
||
confidence = 0.90
|
||
reasoning = fmt.Sprintf("前置剪枝:当前文档处于【%s】阶段,尚未进入审核阶段(reviewing),无法执行审核通过", userCtx.DocLifecycleState)
|
||
clarifyQ = fmt.Sprintf("当前文档处于【%s】阶段,未进入审核阶段,无法执行审核通过。", userCtx.DocLifecycleState)
|
||
}
|
||
}
|
||
|
||
return &CandidateIntent{
|
||
Intent: targetIntent,
|
||
Confidence: confidence,
|
||
Branch: BranchNewIntentPreempt,
|
||
FilledSlots: slots,
|
||
Reasoning: reasoning,
|
||
ClarificationQuestion: clarifyQ,
|
||
}
|
||
}
|
||
}
|
||
|
||
// 4. 认领文档 (Claim) — 查询语气不得路由为写操作
|
||
if strings.Contains(input, "认领") {
|
||
if detectDialogueAct(input) == DialogueActAsk || isDocGovStatusQuery(input, "claim") {
|
||
slots := make(map[string]string)
|
||
if doc := resolveTargetDoc(input, userCtx); doc != "" {
|
||
slots[SlotTargetDoc] = doc
|
||
}
|
||
return &CandidateIntent{
|
||
Intent: IntentQueryUserRights,
|
||
Confidence: 0.91,
|
||
Branch: BranchNewIntentPreempt,
|
||
FilledSlots: slots,
|
||
Reasoning: "查询语气下的文档认领状态询问,不得路由为写操作 DOCGOV_CLAIM",
|
||
ClarificationQuestion: "您是想查询该文档当前由谁认领负责吗?我将为您检索认领状态,不会执行认领操作。",
|
||
}
|
||
}
|
||
slots := make(map[string]string)
|
||
targetDoc := resolveTargetDoc(input, userCtx)
|
||
if targetDoc != "" {
|
||
slots[SlotTargetDoc] = targetDoc
|
||
}
|
||
|
||
return &CandidateIntent{
|
||
Intent: IntentDocGovClaim,
|
||
Confidence: 0.94,
|
||
Branch: BranchNewIntentPreempt,
|
||
FilledSlots: slots,
|
||
Reasoning: "用户下达认领文档编制责任人指令",
|
||
}
|
||
}
|
||
|
||
// 5. 提交送审 (Submit Review) — 查询语气不得路由为写操作
|
||
if strings.Contains(input, "送审") || strings.Contains(input, "提交审核") || strings.Contains(input, "发起审核") || strings.Contains(input, "提审") {
|
||
if detectDialogueAct(input) == DialogueActAsk || isDocGovStatusQuery(input, "submit") {
|
||
slots := make(map[string]string)
|
||
if doc := resolveTargetDoc(input, userCtx); doc != "" {
|
||
slots[SlotTargetDoc] = doc
|
||
}
|
||
return &CandidateIntent{
|
||
Intent: IntentQueryRejectReason,
|
||
Confidence: 0.91,
|
||
Branch: BranchNewIntentPreempt,
|
||
FilledSlots: slots,
|
||
Reasoning: "查询语气下的送审/审核状态询问,不得路由为写操作 DOCGOV_SUBMIT_REVIEW",
|
||
ClarificationQuestion: "您是想查询该文档的送审或审核状态吗?我将为您检索当前流转状态,不会发起新的送审。",
|
||
}
|
||
}
|
||
slots := make(map[string]string)
|
||
targetDoc := resolveTargetDoc(input, userCtx)
|
||
if targetDoc != "" {
|
||
slots[SlotTargetDoc] = targetDoc
|
||
}
|
||
|
||
return &CandidateIntent{
|
||
Intent: IntentDocGovSubmitReview,
|
||
Confidence: 0.94,
|
||
Branch: BranchNewIntentPreempt,
|
||
FilledSlots: slots,
|
||
Reasoning: "用户下达编制完成送交审核指令",
|
||
}
|
||
}
|
||
|
||
// 6. 提案裁决 (Proposal Decide)
|
||
if strings.Contains(input, "提案") || strings.Contains(input, "修改建议") || strings.Contains(input, "变更建议") {
|
||
if strings.Contains(input, "同意") || strings.Contains(input, "采纳") || strings.Contains(input, "拒绝") || strings.Contains(input, "拍板") {
|
||
slots := make(map[string]string)
|
||
if strings.Contains(input, "拒绝") {
|
||
slots[SlotProposalAction] = "reject"
|
||
} else {
|
||
slots[SlotProposalAction] = "accept"
|
||
}
|
||
return &CandidateIntent{
|
||
Intent: IntentDocGovProposalDecide,
|
||
Confidence: 0.93,
|
||
Branch: BranchNewIntentPreempt,
|
||
FilledSlots: slots,
|
||
Reasoning: "用户对受控文档的修改提案或批注做出决策裁决",
|
||
}
|
||
}
|
||
}
|
||
|
||
// 7. 积压补充查询(只读)— 置于写动作前,避免「积压是什么」被误触
|
||
if isPendingSupplementsQuery(input) {
|
||
slots := make(map[string]string)
|
||
fillTargetDocSlot(slots, input, userCtx)
|
||
return &CandidateIntent{
|
||
Intent: IntentQueryPendingSupplements,
|
||
Confidence: 0.95,
|
||
Branch: BranchNewIntentPreempt,
|
||
FilledSlots: slots,
|
||
Reasoning: "用户查询锁定期间积压/待消化补充,路由为只读 QUERY_PENDING_SUPPLEMENTS",
|
||
}
|
||
}
|
||
|
||
// 8. 重开批准(须显式「批准/同意」+「重开/再打开/再修订」;裸词「重开」不触发)
|
||
if isReopenApprovePhrase(input) {
|
||
slots := make(map[string]string)
|
||
fillTargetDocSlot(slots, input, userCtx)
|
||
confidence := 0.95
|
||
reasoning := "用户下达批准重开/再修订指令"
|
||
clarifyQ := ""
|
||
if userCtx != nil {
|
||
if userCtx.UserDocRole != "" && userCtx.UserDocRole != "approver" && userCtx.UserDocRole != "system" {
|
||
confidence = 0.90
|
||
reasoning = fmt.Sprintf("前置剪枝:当前用户角色不是批准人(UserDocRole: %s),无法批准重开", userCtx.UserDocRole)
|
||
clarifyQ = fmt.Sprintf("您当前在受控文档中的角色为【%s】,批准重开需批准人(或创建人兜底,由服务端二次校验)。", userCtx.UserDocRole)
|
||
}
|
||
if userCtx.DocLifecycleState != "" && userCtx.DocLifecycleState != "approved" {
|
||
confidence = 0.90
|
||
reasoning = fmt.Sprintf("前置剪枝:当前文档处于【%s】阶段,重开仅适用于已批准(approved)文档", userCtx.DocLifecycleState)
|
||
clarifyQ = fmt.Sprintf("当前文档处于【%s】阶段,仅已批准文档可申请/批准重开。", userCtx.DocLifecycleState)
|
||
}
|
||
}
|
||
return &CandidateIntent{
|
||
Intent: IntentDocGovReopenApprove,
|
||
Confidence: confidence,
|
||
Branch: BranchNewIntentPreempt,
|
||
FilledSlots: slots,
|
||
Reasoning: reasoning,
|
||
ClarificationQuestion: clarifyQ,
|
||
}
|
||
}
|
||
|
||
// 9. 重开申请(须「申请」+「重开/再修订/再打开」,或显式文档引用 + 申请语义;裸词「重开」不触发)
|
||
if isReopenRequestPhrase(input) {
|
||
if detectDialogueAct(input) == DialogueActAsk {
|
||
slots := make(map[string]string)
|
||
fillTargetDocSlot(slots, input, userCtx)
|
||
return &CandidateIntent{
|
||
Intent: IntentQueryDocStatus,
|
||
Confidence: 0.91,
|
||
Branch: BranchNewIntentPreempt,
|
||
FilledSlots: slots,
|
||
Reasoning: "查询语气下的重开状态询问,不得路由为写操作 DOCGOV_REOPEN_REQUEST",
|
||
ClarificationQuestion: "您是想查询该文档是否已批准、可否申请重开吗?我将检索状态,不会发起重开申请。",
|
||
}
|
||
}
|
||
slots := make(map[string]string)
|
||
fillTargetDocSlot(slots, input, userCtx)
|
||
if reason := extractReopenReason(input); reason != "" {
|
||
slots[SlotReopenReason] = reason
|
||
}
|
||
confidence := 0.94
|
||
reasoning := "用户下达申请再修订/重开指令"
|
||
clarifyQ := ""
|
||
if slots[SlotReopenReason] == "" {
|
||
confidence = 0.90
|
||
clarifyQ = "请补充重开原因(例如:申请重开《需求表》,原因:需补充风险分析章节)。"
|
||
reasoning = "重开申请缺少原因槽位,需澄清"
|
||
}
|
||
if userCtx != nil {
|
||
role := userCtx.UserDocRole
|
||
if role != "" && role != "author" && role != "creator" && role != "system" {
|
||
confidence = 0.90
|
||
reasoning = fmt.Sprintf("前置剪枝:当前用户角色为【%s】,申请重开通常需作者/职责人(服务端将二次校验)", role)
|
||
clarifyQ = fmt.Sprintf("您当前角色为【%s】。申请重开需为文档职责人或项目创建人;确认后由服务端校验。", role)
|
||
}
|
||
if userCtx.DocLifecycleState != "" && userCtx.DocLifecycleState != "approved" {
|
||
confidence = 0.90
|
||
reasoning = fmt.Sprintf("前置剪枝:当前文档处于【%s】阶段,仅已批准(approved)可申请重开", userCtx.DocLifecycleState)
|
||
clarifyQ = fmt.Sprintf("当前文档处于【%s】阶段,仅已批准文档可申请重开。", userCtx.DocLifecycleState)
|
||
}
|
||
}
|
||
return &CandidateIntent{
|
||
Intent: IntentDocGovReopenRequest,
|
||
Confidence: confidence,
|
||
Branch: BranchNewIntentPreempt,
|
||
FilledSlots: slots,
|
||
Reasoning: reasoning,
|
||
ClarificationQuestion: clarifyQ,
|
||
}
|
||
}
|
||
|
||
// 10. 批注解决/关闭
|
||
if isCommentResolvePhrase(input) {
|
||
slots := make(map[string]string)
|
||
fillTargetDocSlot(slots, input, userCtx)
|
||
if sec := extractSection(input); sec != "" {
|
||
slots[SlotTargetSection] = sec
|
||
}
|
||
return &CandidateIntent{
|
||
Intent: IntentDocGovCommentResolve,
|
||
Confidence: 0.93,
|
||
Branch: BranchNewIntentPreempt,
|
||
FilledSlots: slots,
|
||
Reasoning: "用户下达解决/关闭批注意图",
|
||
}
|
||
}
|
||
|
||
// 11. 添加批注(须批注词 + 治理动词共现;查询语气走只读;缺内容澄清)
|
||
if isCommentAddPhrase(input) {
|
||
slots := make(map[string]string)
|
||
fillTargetDocSlot(slots, input, userCtx)
|
||
if sec := extractSection(input); sec != "" {
|
||
slots[SlotTargetSection] = sec
|
||
}
|
||
if ct := extractCommentType(input); ct != "" {
|
||
slots[SlotCommentType] = ct
|
||
} else {
|
||
slots[SlotCommentType] = "suggestion"
|
||
}
|
||
if content := extractCommentContent(input); content != "" {
|
||
slots[SlotCommentContent] = content
|
||
}
|
||
|
||
if detectDialogueAct(input) == DialogueActAsk || isCommentStatusQuery(input) {
|
||
return &CandidateIntent{
|
||
Intent: IntentQueryDocStatus,
|
||
Confidence: 0.91,
|
||
Branch: BranchNewIntentPreempt,
|
||
FilledSlots: slots,
|
||
Reasoning: "查询语气下的批注询问,不得路由为写操作 DOCGOV_COMMENT_ADD",
|
||
ClarificationQuestion: "您是想查询该文档已有批注吗?我将检索批注列表,不会新增批注。",
|
||
}
|
||
}
|
||
|
||
confidence := 0.94
|
||
reasoning := "用户下达添加批注/意见指令"
|
||
clarifyQ := ""
|
||
if slots[SlotCommentContent] == "" {
|
||
confidence = 0.90
|
||
clarifyQ = "请说明要添加的批注内容(例如:给第3节加条意见:风险分析缺少残留风险评价)。"
|
||
reasoning = "批注意图缺少内容槽位,需澄清"
|
||
}
|
||
if userCtx != nil {
|
||
if !commentRoleStateAllowed(userCtx.UserDocRole, userCtx.DocLifecycleState) {
|
||
confidence = 0.90
|
||
reasoning = fmt.Sprintf("前置剪枝:角色【%s】在阶段【%s】不允许批注(按批注矩阵)", userCtx.UserDocRole, userCtx.DocLifecycleState)
|
||
clarifyQ = fmt.Sprintf("按批注矩阵,角色【%s】在【%s】阶段不可添加批注。", userCtx.UserDocRole, userCtx.DocLifecycleState)
|
||
}
|
||
}
|
||
return &CandidateIntent{
|
||
Intent: IntentDocGovCommentAdd,
|
||
Confidence: confidence,
|
||
Branch: BranchNewIntentPreempt,
|
||
FilledSlots: slots,
|
||
Reasoning: reasoning,
|
||
ClarificationQuestion: clarifyQ,
|
||
}
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
func isPendingSupplementsQuery(input string) bool {
|
||
markers := []string{
|
||
"待消化的补充", "待消化补充", "积压补充", "锁定期间补充", "锁定期补充",
|
||
"积压的补充", "未消化补充", "pending supplement",
|
||
}
|
||
for _, m := range markers {
|
||
if strings.Contains(input, m) {
|
||
return true
|
||
}
|
||
}
|
||
if strings.Contains(input, "积压") && (strings.Contains(input, "补充") || strings.Contains(input, "是什么") || strings.Contains(input, "有哪些") || strings.Contains(input, "查询")) {
|
||
return true
|
||
}
|
||
return false
|
||
}
|
||
|
||
func isReopenApprovePhrase(input string) bool {
|
||
hasApprove := strings.Contains(input, "批准重开") || strings.Contains(input, "同意重开") ||
|
||
strings.Contains(input, "批准再打开") || strings.Contains(input, "同意再打开") ||
|
||
strings.Contains(input, "批准再修订") || strings.Contains(input, "同意再修订")
|
||
if hasApprove {
|
||
return true
|
||
}
|
||
hasReopen := strings.Contains(input, "重开") || strings.Contains(input, "再打开") || strings.Contains(input, "再修订")
|
||
hasApproveVerb := strings.Contains(input, "批准") || strings.Contains(input, "同意")
|
||
return hasReopen && hasApproveVerb && !strings.Contains(input, "申请")
|
||
}
|
||
|
||
func isReopenRequestPhrase(input string) bool {
|
||
// 裸词「重开」不触发
|
||
if strings.TrimSpace(input) == "重开" {
|
||
return false
|
||
}
|
||
explicit := []string{"申请重开", "申请再修订", "申请再打开", "申请重新打开", "请求重开", "请求再修订"}
|
||
for _, p := range explicit {
|
||
if strings.Contains(input, p) {
|
||
return true
|
||
}
|
||
}
|
||
hasReopen := strings.Contains(input, "重开") || strings.Contains(input, "再打开") || strings.Contains(input, "再修订")
|
||
hasApply := strings.Contains(input, "申请") || strings.Contains(input, "请求")
|
||
if hasReopen && hasApply {
|
||
return true
|
||
}
|
||
// 显式文档引用 + 再打开/再修订(仍须避免裸词)
|
||
if hasReopen && extractDocName(input) != "" && (strings.Contains(input, "再") || strings.Contains(input, "申请")) {
|
||
return true
|
||
}
|
||
return false
|
||
}
|
||
|
||
func isCommentResolvePhrase(input string) bool {
|
||
markers := []string{"解决批注", "关闭批注", "批注已解决", "批注已处理", "标记批注已解决", "resolve批注"}
|
||
for _, m := range markers {
|
||
if strings.Contains(input, m) {
|
||
return true
|
||
}
|
||
}
|
||
return (strings.Contains(input, "批注") || strings.Contains(input, "意见")) &&
|
||
(strings.Contains(input, "解决") || strings.Contains(input, "关闭")) &&
|
||
!strings.Contains(input, "添加") && !strings.Contains(input, "加条")
|
||
}
|
||
|
||
func isCommentAddPhrase(input string) bool {
|
||
// 批注词必须与治理动词共现,避免「批注」裸词误触
|
||
govVerbs := []string{"添加批注", "加条意见", "加条批注", "加上批注", "写条批注", "写个批注", "新增批注", "添加意见", "给.*加.*意见", "批注:", "批注:"}
|
||
for _, v := range govVerbs {
|
||
if strings.Contains(v, ".*") {
|
||
continue
|
||
}
|
||
if strings.Contains(input, v) {
|
||
return true
|
||
}
|
||
}
|
||
hasCommentWord := strings.Contains(input, "批注") || strings.Contains(input, "加条意见") || strings.Contains(input, "添加意见")
|
||
hasAction := strings.Contains(input, "添加") || strings.Contains(input, "加条") || strings.Contains(input, "加上") ||
|
||
strings.Contains(input, "写") || strings.Contains(input, "新增") || strings.Contains(input, "给")
|
||
return hasCommentWord && hasAction
|
||
}
|
||
|
||
func isCommentStatusQuery(input string) bool {
|
||
markers := []string{"有哪些批注", "有什么批注", "批注列表", "查看批注", "查一下批注", "我的批注"}
|
||
for _, m := range markers {
|
||
if strings.Contains(input, m) {
|
||
return true
|
||
}
|
||
}
|
||
return false
|
||
}
|
||
|
||
func extractCommentContent(input string) string {
|
||
if r := extractRejectReason(input); r != "" {
|
||
return r
|
||
}
|
||
re := regexp.MustCompile(`(?:添加批注|加条意见|加条批注|加上批注|写条批注|写个批注|新增批注|添加意见)[::\s]*(.+)`)
|
||
if match := re.FindStringSubmatch(input); len(match) > 1 {
|
||
return strings.Trim(strings.TrimSpace(match[1]), ",,。;; ")
|
||
}
|
||
return ""
|
||
}
|
||
|
||
func extractCommentType(input string) string {
|
||
switch {
|
||
case strings.Contains(input, "问题") || strings.Contains(input, "issue"):
|
||
return "issue"
|
||
case strings.Contains(input, "同意") || strings.Contains(input, "approval") || strings.Contains(input, "认可"):
|
||
return "approval"
|
||
case strings.Contains(input, "建议") || strings.Contains(input, "suggestion"):
|
||
return "suggestion"
|
||
default:
|
||
return ""
|
||
}
|
||
}
|
||
|
||
func extractReopenReason(input string) string {
|
||
re := regexp.MustCompile(`(?:原因|理由|因为|由于)[::\s]*(.+)`)
|
||
if match := re.FindStringSubmatch(input); len(match) > 1 {
|
||
return strings.Trim(strings.TrimSpace(match[1]), ",,。;; ")
|
||
}
|
||
return ""
|
||
}
|
||
|
||
// commentRoleStateAllowed mirrors docgov.roleAllowedToComment for Stage-0 pruning.
|
||
func commentRoleStateAllowed(role, state string) bool {
|
||
if role == "" || state == "" {
|
||
return true // 快照缺失时不剪枝,交给服务端二次校验
|
||
}
|
||
switch state {
|
||
case "drafting":
|
||
return role == "author" || role == "reviewer" || role == "system"
|
||
case "reviewing":
|
||
return role == "reviewer" || role == "author" || role == "system"
|
||
case "approving":
|
||
return role == "approver" || role == "reviewer" || role == "system"
|
||
case "approved":
|
||
return false
|
||
default:
|
||
return false
|
||
}
|
||
}
|