feat: 增强差旅报销审核流程与票据智能推理

优化本体解析和编排器的差旅场景处理能力,完善报销单草稿
保存和费用明细同步逻辑,前端报销创建页面增加行程推理和
票据审核交互,新增助手会话快照工具函数,补充单元测试。
This commit is contained in:
caoxiaozhu
2026-05-21 16:09:47 +08:00
parent f28d7e6d16
commit e701fa01da
33 changed files with 3033 additions and 337 deletions

View File

@@ -496,25 +496,18 @@ def test_user_agent_guides_generic_expense_request() -> None:
)
)
assert response.review_payload is not None
assert response.answer == response.review_payload.body_message
assert response.review_payload.can_proceed is False
assert response.review_payload.missing_slots == [
"报销类型",
"发生时间",
"金额",
"事由说明",
"票据附件",
]
assert [item.action_type for item in response.review_payload.confirmation_actions] == [
"cancel_review",
"edit_review",
assert response.review_payload is None
assert response.draft_payload is None
assert "请先在下面选择报销场景" in response.answer
assert [item.action_type for item in response.suggested_actions] == [
"select_expense_type",
"select_expense_type",
"select_expense_type",
"select_expense_type",
"select_expense_type",
"select_expense_type",
]
edit_action = next(
item for item in response.review_payload.confirmation_actions if item.action_type == "edit_review"
)
assert edit_action.label == "选择报销类型"
assert edit_action.emphasis == "primary"
assert [item.label for item in response.suggested_actions[:3]] == ["差旅费", "交通费", "住宿费"]
def test_user_agent_asks_for_type_when_trip_context_is_ambiguous() -> None:
@@ -537,25 +530,69 @@ def test_user_agent_asks_for_type_when_trip_context_is_ambiguous() -> None:
)
)
assert response.review_payload is None
assert response.draft_payload is None
assert "请先在下面选择报销场景" in response.answer
assert "避免系统先入为主" in response.answer
assert [item.label for item in response.suggested_actions] == [
"差旅费",
"交通费",
"住宿费",
"业务招待费",
"办公费",
"其他费用",
]
assert response.suggested_actions[0].payload["original_message"] == message
def test_user_agent_continues_identification_after_expense_type_selection() -> None:
session_factory = build_session_factory()
with session_factory() as db:
message = "业务发生时间:2026-02-20 至 2026-02-23去上海支持上海电力部署项目申请报销"
ontology = SemanticOntologyService(db).parse(
OntologyParseRequest(
query=f"{message}\n用户选择报销场景:差旅费",
user_id="pytest-selected-type@example.com",
context_json={
"expense_scene_selection": {
"expense_type": "travel",
"expense_type_label": "差旅费",
"original_message": message,
},
"review_form_values": {
"expense_type": "差旅费",
},
"user_input_text": message,
},
)
)
response = UserAgentService(db).respond(
UserAgentRequest(
run_id=ontology.run_id,
user_id="pytest-selected-type@example.com",
message=f"{message}\n用户选择报销场景:差旅费",
ontology=ontology,
context_json={
"expense_scene_selection": {
"expense_type": "travel",
"expense_type_label": "差旅费",
"original_message": message,
},
"review_form_values": {
"expense_type": "差旅费",
},
"user_input_text": message,
},
tool_payload={"draft_only": True},
)
)
assert response.review_payload is not None
slot_map = {item.key: item for item in response.review_payload.slot_cards}
assert slot_map["expense_type"].value == ""
assert slot_map["expense_type"].status == "missing"
assert slot_map["expense_type"].value == "差旅费"
assert slot_map["expense_type"].normalized_value == "travel"
assert slot_map["time_range"].value == "2026-02-20 至 2026-02-23"
assert slot_map["location"].value == "上海"
assert response.review_payload.can_proceed is False
assert "报销类型" in response.review_payload.missing_slots
assert "选择报销类型" in response.review_payload.body_message
assert "不会重新改判报销类型" in response.review_payload.body_message
edit_action = next(
item for item in response.review_payload.confirmation_actions if item.action_type == "edit_review"
)
assert edit_action.label == "选择报销类型"
assert edit_action.emphasis == "primary"
assert [item.action_type for item in response.review_payload.confirmation_actions] == [
"cancel_review",
"edit_review",
]
def test_user_agent_guides_implicit_expense_draft_request() -> None: