feat: 完善差旅票据行程提取与费用明细回填逻辑

增强文档智能识别的票据场景关键词和字段提取能力,优化
会话关联草稿报销单的解析路径,修复费用明细合并和票据
去重边界问题,前端改进报销创建和审批详情交互,补充单
元测试覆盖。
This commit is contained in:
caoxiaozhu
2026-05-21 14:24:51 +08:00
parent b183b0bd5e
commit f28d7e6d16
24 changed files with 1565 additions and 433 deletions

View File

@@ -18,6 +18,47 @@ STATEFUL_CONTEXT_KEYS = (
"attachment_count",
"ocr_summary",
"ocr_documents",
"review_form_values",
"business_time_context",
)
REVIEW_FLOW_CONTEXT_KEYS = {
"request_context",
"attachment_names",
"attachment_count",
"ocr_summary",
"ocr_documents",
"review_form_values",
"business_time_context",
}
REVIEW_FLOW_CONTINUATION_KEYWORDS = (
"补充",
"继续",
"继续上传",
"当前",
"这张",
"这个",
"该单据",
"现有",
"已有",
"关联",
"合并",
"修改",
"更正",
"改成",
"调整",
"下一步",
"保存草稿",
)
NEW_EXPENSE_PROMPT_KEYWORDS = (
"申请报销",
"我要报销",
"我想报销",
"帮我报销",
"发起报销",
"提交报销",
"生成报销",
"创建报销",
"新建报销",
)
DEFAULT_CONVERSATION_RETENTION_DAYS = 3
@@ -182,10 +223,15 @@ class AgentConversationService:
*,
conversation: AgentConversation,
context_json: dict[str, Any],
message: str | None = None,
history_limit: int = 8,
) -> dict[str, Any]:
merged = dict(context_json or {})
state_json = dict(conversation.state_json or {})
should_hydrate_review_flow = self._should_hydrate_review_flow_context(
context_json=merged,
message=message,
)
merged["conversation_id"] = conversation.conversation_id
merged["conversation_history"] = self.list_message_history(
@@ -196,16 +242,53 @@ class AgentConversationService:
merged.setdefault("conversation_scenario", conversation.last_scenario)
if conversation.last_intent:
merged.setdefault("conversation_intent", conversation.last_intent)
if conversation.draft_claim_id and not str(merged.get("draft_claim_id") or "").strip():
if (
should_hydrate_review_flow
and conversation.draft_claim_id
and not str(merged.get("draft_claim_id") or "").strip()
):
merged["draft_claim_id"] = conversation.draft_claim_id
merged["conversation_state"] = state_json
for key in STATEFUL_CONTEXT_KEYS:
if key in REVIEW_FLOW_CONTEXT_KEYS and not should_hydrate_review_flow:
continue
if self._is_empty_value(merged.get(key)) and not self._is_empty_value(state_json.get(key)):
merged[key] = state_json.get(key)
return merged
@staticmethod
def _should_hydrate_review_flow_context(
*,
context_json: dict[str, Any],
message: str | None,
) -> bool:
if AgentConversationService._resolve_draft_claim_id(context_json):
return True
if str(context_json.get("review_action") or "").strip():
return True
if str(context_json.get("entry_source") or "").strip() == "detail":
return True
if not AgentConversationService._is_empty_value(context_json.get("attachment_names")):
return True
if not AgentConversationService._is_empty_value(context_json.get("ocr_documents")):
return True
if str(context_json.get("ocr_summary") or "").strip():
return True
try:
if int(context_json.get("attachment_count") or 0) > 0:
return True
except (TypeError, ValueError):
pass
compact_message = str(message or "").replace(" ", "")
if not compact_message:
return False
if any(keyword in compact_message for keyword in NEW_EXPENSE_PROMPT_KEYWORDS):
return False
return any(keyword in compact_message for keyword in REVIEW_FLOW_CONTINUATION_KEYWORDS)
def append_message(
self,
*,