feat: 增强规则资产管理与审计页面运行时调试

后端新增规则资产版本管理和规则文件 CRUD 接口,优化风险
规则生成模板执行和员工数据模型字段,知识库 RAG 增强本
地回退和文档提取能力,清理旧风险规则文件统一由生成引擎
管理,前端审计页面增加运行时调试面板和规则资产编辑交互,
补充单元测试覆盖。
This commit is contained in:
caoxiaozhu
2026-05-24 21:44:17 +08:00
parent 575f093c74
commit 50b1c3f9a9
113 changed files with 13896 additions and 5044 deletions

View File

@@ -18,7 +18,12 @@ from app.services.ontology_rules import (
DATE_RANGE_PATTERN,
EXPLICIT_DATE_PATTERN,
EXPLICIT_MONTH_PATTERN,
EXPENSE_APPLICATION_ATTACHMENT_REQUIRED_TYPES,
EXPENSE_APPLICATION_CONTEXT_TYPES,
EXPENSE_APPLICATION_KEYWORDS,
EXPENSE_APPLICATION_REQUIRED_SLOT_KEYS,
EXPENSE_TYPE_KEYWORDS,
GENERIC_EXPENSE_APPLICATION_PROMPTS,
GENERIC_EXPENSE_PROMPTS,
LOCATION_KEYWORDS,
MONTH_DAY_PATTERN,
@@ -30,6 +35,21 @@ from app.services.ontology_rules import (
class OntologyExtractionMixin:
@staticmethod
def _is_expense_application_context_value(context_json: dict[str, Any]) -> bool:
document_type = str(context_json.get("document_type") or "").strip()
application_stage = str(context_json.get("application_stage") or "").strip()
entry_source = str(context_json.get("entry_source") or "").strip()
return (
document_type in EXPENSE_APPLICATION_CONTEXT_TYPES
or application_stage in EXPENSE_APPLICATION_CONTEXT_TYPES
or entry_source in {"documents_application", "expense_application"}
)
@staticmethod
def _has_expense_application_signal(compact_query: str) -> bool:
return any(keyword in compact_query for keyword in EXPENSE_APPLICATION_KEYWORDS)
def _infer_default_missing_slots(
self,
compact_query: str,
@@ -46,6 +66,44 @@ class OntologyExtractionMixin:
entity_types = {item.type for item in entities}
attachment_count = int(context_json.get("attachment_count") or 0)
missing_slots: list[str] = []
application_mode = (
self._is_expense_application_context_value(context_json)
or self._has_expense_application_signal(compact_query)
or any(
item.type == "document_type" and item.normalized_value == "expense_application"
for item in entities
)
)
if application_mode:
form_values = context_json.get("review_form_values")
if not isinstance(form_values, dict):
form_values = {}
expense_type_codes = {
str(item.normalized_value or item.value or "").strip()
for item in entities
if item.type == "expense_type"
}
if "expense_type" not in entity_types and not str(form_values.get("expense_type") or "").strip():
missing_slots.append("expense_type")
if "amount" not in entity_types and not str(form_values.get("amount") or "").strip():
missing_slots.append("amount")
if not time_range.start_date and not (
str(form_values.get("time_range") or form_values.get("business_time") or "").strip()
):
missing_slots.append("time_range")
reason_value = str(
form_values.get("reason")
or form_values.get("business_reason")
or form_values.get("reason_value")
or ""
).strip()
if not reason_value and compact_query in GENERIC_EXPENSE_APPLICATION_PROMPTS:
missing_slots.append("reason")
if attachment_count <= 0 and expense_type_codes & EXPENSE_APPLICATION_ATTACHMENT_REQUIRED_TYPES:
missing_slots.append("attachments")
ordered_keys = [*EXPENSE_APPLICATION_REQUIRED_SLOT_KEYS, "attachments"]
return [item for item in ordered_keys if item in missing_slots]
if self._is_generic_expense_prompt(compact_query):
if "expense_type" not in entity_types:
@@ -98,14 +156,40 @@ class OntologyExtractionMixin:
query: str,
compact_query: str,
reference: ReferenceCatalog,
*,
context_json: dict[str, Any] | None = None,
) -> list[OntologyEntity]:
entities: dict[tuple[str, str], OntologyEntity] = {}
context_json = context_json or {}
def upsert(entity: OntologyEntity) -> None:
key = (entity.type, entity.normalized_value)
if key not in entities:
entities[key] = entity
if (
self._is_expense_application_context_value(context_json)
or self._has_expense_application_signal(compact_query)
):
upsert(
self._make_entity(
"document_type",
"费用申请",
"expense_application",
role="target",
confidence=0.94,
)
)
upsert(
self._make_entity(
"workflow_stage",
"前置申请",
"pre_approval",
role="target",
confidence=0.9,
)
)
for match in re.finditer(r"客户\s*([A-Za-z0-9一二三四五六七八九十]+)", query):
suffix = match.group(1).strip()
normalized = f"客户{suffix}".replace(" ", "")
@@ -510,6 +594,8 @@ class OntologyExtractionMixin:
"project",
"location",
"expense_type",
"document_type",
"workflow_stage",
}:
upsert(
OntologyConstraint(