feat: 增强规则资产管理与审计页面运行时调试
后端新增规则资产版本管理和规则文件 CRUD 接口,优化风险 规则生成模板执行和员工数据模型字段,知识库 RAG 增强本 地回退和文档提取能力,清理旧风险规则文件统一由生成引擎 管理,前端审计页面增加运行时调试面板和规则资产编辑交互, 补充单元测试覆盖。
This commit is contained in:
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user