feat: 重构报销单AI预审流程并添加平台风险规则引擎
- 将AI验审改为AI预审,高风险不再拦截而是随单流转给审批人复核 - 新增平台风险规则评估引擎,支持事由过短、票据异常、重复发票等多种评估器 - 用户上下文增加部门信息(department_name),认证流程同步关联组织架构 - 规则scenario_json改为中文标签(差旅/费用科目),统一场景分类 - 新增orchestrator审核流程测试用例 - 前端更新审计视图、差旅报销等相关页面
This commit is contained in:
@@ -120,11 +120,13 @@ EXPLAIN_KEYWORDS = ("为什么", "依据", "原因", "怎么处理", "是否可
|
||||
COMPARE_KEYWORDS = ("对比", "比较", "相比", "差异", "变化")
|
||||
RISK_KEYWORDS = ("风险", "异常", "重复", "超标", "超预算", "逾期", "验真", "巡检")
|
||||
DRAFT_KEYWORDS = ("生成", "草稿", "起草", "拟一份", "创建", "发起", "准备")
|
||||
DRAFT_FOLLOW_UP_KEYWORDS = (
|
||||
"继续",
|
||||
"补充",
|
||||
"补一下",
|
||||
"修改",
|
||||
DRAFT_FOLLOW_UP_KEYWORDS = (
|
||||
"继续",
|
||||
"下一步",
|
||||
"核对",
|
||||
"补充",
|
||||
"补一下",
|
||||
"修改",
|
||||
"改成",
|
||||
"改为",
|
||||
"换成",
|
||||
@@ -136,9 +138,16 @@ DRAFT_FOLLOW_UP_KEYWORDS = (
|
||||
"地点是",
|
||||
"金额是",
|
||||
"日期是",
|
||||
"时间是",
|
||||
)
|
||||
OPERATE_KEYWORDS = (
|
||||
"时间是",
|
||||
)
|
||||
EXPENSE_REVIEW_ACTIONS = {
|
||||
"save_draft",
|
||||
"next_step",
|
||||
"edit_review",
|
||||
"link_to_existing_draft",
|
||||
"create_new_claim_from_documents",
|
||||
}
|
||||
OPERATE_KEYWORDS = (
|
||||
"直接付款",
|
||||
"帮我付款",
|
||||
"安排付款",
|
||||
@@ -636,12 +645,17 @@ class SemanticOntologyService:
|
||||
def _compact(text: str) -> str:
|
||||
return re.sub(r"\s+", "", text).lower()
|
||||
|
||||
@staticmethod
|
||||
def _resolve_context_scenario(context_json: dict[str, Any]) -> str | None:
|
||||
value = str(context_json.get("conversation_scenario") or "").strip()
|
||||
if value in CONTEXTUAL_SCENARIOS:
|
||||
return value
|
||||
return None
|
||||
@staticmethod
|
||||
def _resolve_context_scenario(context_json: dict[str, Any]) -> str | None:
|
||||
value = str(context_json.get("conversation_scenario") or "").strip()
|
||||
if value in CONTEXTUAL_SCENARIOS:
|
||||
return value
|
||||
review_action = str(context_json.get("review_action") or "").strip()
|
||||
if review_action in EXPENSE_REVIEW_ACTIONS:
|
||||
return "expense"
|
||||
if str(context_json.get("draft_claim_id") or "").strip():
|
||||
return "expense"
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def _resolve_session_type_scenario(context_json: dict[str, Any]) -> str | None:
|
||||
@@ -728,19 +742,22 @@ class SemanticOntologyService:
|
||||
)
|
||||
return len(compact_query) <= 12 and not has_domain_keyword
|
||||
|
||||
def _should_inherit_expense_draft(
|
||||
self,
|
||||
compact_query: str,
|
||||
def _should_inherit_expense_draft(
|
||||
self,
|
||||
compact_query: str,
|
||||
*,
|
||||
scenario: str,
|
||||
entities: list[OntologyEntity],
|
||||
time_range: OntologyTimeRange,
|
||||
context_json: dict[str, Any],
|
||||
) -> bool:
|
||||
context_scenario = self._resolve_context_scenario(context_json)
|
||||
draft_claim_id = str(context_json.get("draft_claim_id") or "").strip()
|
||||
if context_scenario != "expense" and not draft_claim_id:
|
||||
return False
|
||||
context_json: dict[str, Any],
|
||||
) -> bool:
|
||||
context_scenario = self._resolve_context_scenario(context_json)
|
||||
draft_claim_id = str(context_json.get("draft_claim_id") or "").strip()
|
||||
review_action = str(context_json.get("review_action") or "").strip()
|
||||
if review_action in EXPENSE_REVIEW_ACTIONS:
|
||||
return True
|
||||
if context_scenario != "expense" and not draft_claim_id:
|
||||
return False
|
||||
|
||||
if any(keyword in compact_query for keyword in DRAFT_FOLLOW_UP_KEYWORDS):
|
||||
return True
|
||||
@@ -1674,15 +1691,16 @@ class SemanticOntologyService:
|
||||
return False, None
|
||||
|
||||
@staticmethod
|
||||
def _allow_incomplete_draft(
|
||||
context_json: dict[str, Any],
|
||||
*,
|
||||
scenario: str,
|
||||
intent: str,
|
||||
def _allow_incomplete_draft(
|
||||
context_json: dict[str, Any],
|
||||
*,
|
||||
scenario: str,
|
||||
intent: str,
|
||||
) -> bool:
|
||||
if scenario != "expense" or intent != "draft":
|
||||
return False
|
||||
return str(context_json.get("review_action") or "").strip() == "save_draft"
|
||||
if scenario != "expense" or intent != "draft":
|
||||
return False
|
||||
review_action = str(context_json.get("review_action") or "").strip()
|
||||
return review_action in EXPENSE_REVIEW_ACTIONS
|
||||
|
||||
@staticmethod
|
||||
def _display_slot_label(slot: str) -> str:
|
||||
|
||||
Reference in New Issue
Block a user