feat: 增强差旅报销审核流程与票据智能推理
优化本体解析和编排器的差旅场景处理能力,完善报销单草稿 保存和费用明细同步逻辑,前端报销创建页面增加行程推理和 票据审核交互,新增助手会话快照工具函数,补充单元测试。
This commit is contained in:
@@ -97,6 +97,15 @@ GROUP_SCENE_LABELS = {
|
||||
"other": "其他费用",
|
||||
}
|
||||
|
||||
EXPENSE_SCENE_SELECTION_OPTIONS = (
|
||||
("travel", "差旅费", "出差、长途交通、住宿、差旅补贴等场景。"),
|
||||
("transport", "交通费", "市内打车、停车、过路费等日常交通场景。"),
|
||||
("hotel", "住宿费", "单独住宿、酒店发票等场景。"),
|
||||
("entertainment", "业务招待费", "客户接待、宴请、招待等场景。"),
|
||||
("office", "办公费", "办公用品、耗材、办公设备等采购场景。"),
|
||||
("other", "其他费用", "暂不属于以上分类的报销场景。"),
|
||||
)
|
||||
|
||||
KNOWLEDGE_MODEL_MAIN_TIMEOUT_SECONDS = 3
|
||||
KNOWLEDGE_MODEL_BACKUP_TIMEOUT_SECONDS = 5
|
||||
KNOWLEDGE_MODEL_TIMEOUT_SECONDS = KNOWLEDGE_MODEL_BACKUP_TIMEOUT_SECONDS
|
||||
@@ -275,6 +284,17 @@ class UserAgentService:
|
||||
AgentFoundationService(self.db).ensure_foundation_ready()
|
||||
citations = self._build_citations(payload)
|
||||
suggested_actions = self._build_suggested_actions(payload)
|
||||
if self._should_prompt_expense_scene_selection(payload):
|
||||
return UserAgentResponse(
|
||||
answer=self._build_expense_scene_selection_answer(payload),
|
||||
citations=citations,
|
||||
suggested_actions=suggested_actions,
|
||||
query_payload=None,
|
||||
draft_payload=None,
|
||||
review_payload=None,
|
||||
risk_flags=[],
|
||||
requires_confirmation=False,
|
||||
)
|
||||
risk_flags = self._resolve_risk_flags(payload)
|
||||
query_payload = self._build_query_payload(payload)
|
||||
draft_payload = (
|
||||
@@ -1801,6 +1821,11 @@ class UserAgentService:
|
||||
|
||||
@staticmethod
|
||||
def _should_build_draft_payload(payload: UserAgentRequest) -> bool:
|
||||
if payload.ontology.scenario == "expense" and payload.tool_payload.get("preview_only"):
|
||||
return any(
|
||||
str(payload.tool_payload.get(key) or "").strip()
|
||||
for key in ("claim_id", "claim_no")
|
||||
)
|
||||
if payload.ontology.intent == "draft":
|
||||
return True
|
||||
if payload.ontology.scenario != "expense":
|
||||
@@ -1817,6 +1842,21 @@ class UserAgentService:
|
||||
if payload.ontology.scenario == "knowledge":
|
||||
return []
|
||||
|
||||
if self._should_prompt_expense_scene_selection(payload):
|
||||
return [
|
||||
UserAgentSuggestedAction(
|
||||
label=label,
|
||||
action_type="select_expense_type",
|
||||
description=description,
|
||||
payload={
|
||||
"expense_type": code,
|
||||
"expense_type_label": label,
|
||||
"original_message": payload.message,
|
||||
},
|
||||
)
|
||||
for code, label, description in EXPENSE_SCENE_SELECTION_OPTIONS
|
||||
]
|
||||
|
||||
if self._is_generic_expense_prompt(payload):
|
||||
return [
|
||||
UserAgentSuggestedAction(
|
||||
@@ -1886,6 +1926,35 @@ class UserAgentService:
|
||||
),
|
||||
]
|
||||
|
||||
def _should_prompt_expense_scene_selection(self, payload: UserAgentRequest) -> bool:
|
||||
if payload.ontology.scenario != "expense":
|
||||
return False
|
||||
if payload.ontology.intent not in {"draft", "operate"}:
|
||||
return False
|
||||
if str(payload.context_json.get("review_action") or "").strip():
|
||||
return False
|
||||
review_form_values = self._resolve_review_form_values(payload)
|
||||
if str(review_form_values.get("expense_type") or review_form_values.get("reimbursement_type") or "").strip():
|
||||
return False
|
||||
if self._resolve_attachment_count(payload) > 0 or self._resolve_ocr_documents(payload):
|
||||
return False
|
||||
return not any(
|
||||
item.type == "expense_type" and str(item.normalized_value or item.value or "").strip()
|
||||
for item in payload.ontology.entities
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _build_expense_scene_selection_answer(payload: UserAgentRequest) -> str:
|
||||
has_time = bool(payload.ontology.time_range.start_date or payload.ontology.time_range.raw)
|
||||
context_hint = "我先识别到这是一次报销申请"
|
||||
if has_time:
|
||||
context_hint += ",并看到了业务发生时间"
|
||||
return (
|
||||
f"{context_hint}。但你还没有明确这笔单据属于哪类报销。"
|
||||
"请先在下面选择报销场景,我会按你选择的场景再继续识别时间、地点、事由、金额和所需票据,"
|
||||
"避免系统先入为主把项目支持、部署等描述误判成差旅。"
|
||||
)
|
||||
|
||||
def _build_review_payload(
|
||||
self,
|
||||
payload: UserAgentRequest,
|
||||
@@ -3363,6 +3432,17 @@ class UserAgentService:
|
||||
)
|
||||
|
||||
review_action = str(payload.context_json.get("review_action") or "").strip()
|
||||
if payload.tool_payload.get("preview_only") and not review_action:
|
||||
base_message = review_payload.body_message or self._build_review_intent_summary(
|
||||
payload,
|
||||
slot_cards=review_payload.slot_cards,
|
||||
claim_groups=review_payload.claim_groups,
|
||||
)
|
||||
return (
|
||||
f"{base_message} "
|
||||
"本次只是核对预览,尚未保存为草稿;需要暂存时请点击“保存为草稿”,"
|
||||
"需要正式提交时再点击“继续下一步”。"
|
||||
)
|
||||
if review_action == "save_draft":
|
||||
if draft_payload is not None and draft_payload.claim_no:
|
||||
return (
|
||||
|
||||
Reference in New Issue
Block a user