feat: 重构报销单AI预审流程并添加平台风险规则引擎
- 将AI验审改为AI预审,高风险不再拦截而是随单流转给审批人复核 - 新增平台风险规则评估引擎,支持事由过短、票据异常、重复发票等多种评估器 - 用户上下文增加部门信息(department_name),认证流程同步关联组织架构 - 规则scenario_json改为中文标签(差旅/费用科目),统一场景分类 - 新增orchestrator审核流程测试用例 - 前端更新审计视图、差旅报销等相关页面
This commit is contained in:
@@ -255,7 +255,7 @@ class UserAgentService:
|
||||
query_payload = self._build_query_payload(payload)
|
||||
draft_payload = (
|
||||
self._build_draft_payload(payload)
|
||||
if payload.ontology.intent == "draft"
|
||||
if self._should_build_draft_payload(payload)
|
||||
else None
|
||||
)
|
||||
review_payload = self._build_review_payload(
|
||||
@@ -1683,7 +1683,10 @@ class UserAgentService:
|
||||
if not risk_flags and not platform_messages:
|
||||
return "当前未识别到明确风险标签,建议继续查看原始明细或补充更多上下文。"
|
||||
|
||||
reasons = [RISK_REASON_MAP.get(flag, f"{flag} 需要人工进一步确认。") for flag in risk_flags]
|
||||
reasons = [
|
||||
f"{flag}:{RISK_REASON_MAP.get(flag, f'{flag} 需要人工进一步确认。')}"
|
||||
for flag in risk_flags
|
||||
]
|
||||
if platform_messages:
|
||||
reasons.extend(platform_messages)
|
||||
citation_text = (
|
||||
@@ -1764,6 +1767,17 @@ class UserAgentService:
|
||||
approval_stage=approval_stage,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _should_build_draft_payload(payload: UserAgentRequest) -> bool:
|
||||
if payload.ontology.intent == "draft":
|
||||
return True
|
||||
if payload.ontology.scenario != "expense":
|
||||
return False
|
||||
return any(
|
||||
str(payload.tool_payload.get(key) or "").strip()
|
||||
for key in ("claim_id", "claim_no", "status")
|
||||
)
|
||||
|
||||
def _build_suggested_actions(
|
||||
self,
|
||||
payload: UserAgentRequest,
|
||||
@@ -1868,6 +1882,7 @@ class UserAgentService:
|
||||
payload,
|
||||
slot_cards=slot_cards,
|
||||
)
|
||||
submission_blocked = bool(payload.tool_payload.get("submission_blocked"))
|
||||
risk_briefs = self._build_review_risk_briefs(
|
||||
payload,
|
||||
citations=citations,
|
||||
@@ -1877,7 +1892,7 @@ class UserAgentService:
|
||||
association_choice_pending = self._is_review_association_choice_pending(payload)
|
||||
can_proceed = (
|
||||
False
|
||||
if association_choice_pending
|
||||
if association_choice_pending or submission_blocked
|
||||
else self._can_proceed_review(
|
||||
payload,
|
||||
missing_slot_keys=missing_slot_keys,
|
||||
@@ -2157,6 +2172,15 @@ class UserAgentService:
|
||||
claim_groups: list[UserAgentReviewClaimGroup],
|
||||
) -> list[UserAgentReviewRiskBrief]:
|
||||
briefs: list[UserAgentReviewRiskBrief] = []
|
||||
for reason in self._resolve_submission_blocked_reasons(payload):
|
||||
briefs.append(
|
||||
UserAgentReviewRiskBrief(
|
||||
title="AI预审未通过",
|
||||
level="high",
|
||||
content=reason,
|
||||
)
|
||||
)
|
||||
|
||||
employee_name = self._collect_entity_values(payload).get("employee_name") or str(
|
||||
payload.context_json.get("name") or ""
|
||||
).strip()
|
||||
@@ -2229,6 +2253,36 @@ class UserAgentService:
|
||||
|
||||
return briefs[:4]
|
||||
|
||||
@staticmethod
|
||||
def _resolve_submission_blocked_reasons(payload: UserAgentRequest) -> list[str]:
|
||||
raw_reasons = payload.tool_payload.get("submission_blocked_reasons")
|
||||
if raw_reasons is None:
|
||||
raw_reasons = payload.tool_payload.get("missing_fields")
|
||||
|
||||
reasons: list[str] = []
|
||||
if isinstance(raw_reasons, list):
|
||||
reasons.extend(str(item or "").strip() for item in raw_reasons)
|
||||
elif isinstance(raw_reasons, str):
|
||||
reasons.extend(
|
||||
item.strip()
|
||||
for item in re.split(r"[;;\n]+", raw_reasons)
|
||||
if item.strip()
|
||||
)
|
||||
|
||||
if not reasons:
|
||||
message = str(payload.tool_payload.get("message") or "").strip()
|
||||
prefix = "提交前请先补全信息:"
|
||||
if message.startswith(prefix):
|
||||
message = message[len(prefix):].strip()
|
||||
if message:
|
||||
reasons.extend(
|
||||
item.strip()
|
||||
for item in re.split(r"[;;\n]+", message)
|
||||
if item.strip() and not item.strip().startswith("AI预审暂未通过")
|
||||
)
|
||||
|
||||
return list(dict.fromkeys(reason for reason in reasons if reason))
|
||||
|
||||
def _build_review_confirmation_actions(
|
||||
self,
|
||||
payload: UserAgentRequest,
|
||||
@@ -2383,6 +2437,16 @@ class UserAgentService:
|
||||
stage_text = draft_payload.approval_stage or "审批中"
|
||||
return f"报销单 {draft_payload.claim_no or ''} 已提交,当前节点为 {stage_text}。".strip()
|
||||
if payload.tool_payload.get("submission_blocked"):
|
||||
reasons = self._resolve_submission_blocked_reasons(payload)
|
||||
if reasons:
|
||||
reason_lines = "\n".join(
|
||||
f"{index}. {reason}" for index, reason in enumerate(reasons, start=1)
|
||||
)
|
||||
return (
|
||||
"AI预审暂未通过,所以还没有提交到审批人。\n"
|
||||
f"{reason_lines}\n"
|
||||
"请先处理以上项目;处理完成后再点继续下一步。"
|
||||
)
|
||||
return str(payload.tool_payload.get("message") or "").strip() or "当前报销单暂时还不能提交审批。"
|
||||
return (
|
||||
f"{self._build_review_intent_summary(payload, slot_cards=review_payload.slot_cards, claim_groups=review_payload.claim_groups)} "
|
||||
|
||||
Reference in New Issue
Block a user