feat: 新增预算中心本体与风险规则评分回填

后端新增预算本体解析模块和风险规则评分回填服务,优化规则
生成本体对齐和提示词构建,增强费用类型关键词和本体验证,
完善报销查询和审计接口,前端预算中心页面增加对话框和本
体工具函数,重构审计页面元数据和视图模型,补充单元测试。
This commit is contained in:
caoxiaozhu
2026-05-26 12:16:20 +08:00
parent 0e861d8fa6
commit e1e515ecae
53 changed files with 4350 additions and 921 deletions

View File

@@ -22,6 +22,7 @@ from app.services.risk_rule_flow_diagram import (
from app.services.risk_rule_generation_ontology import (
BUSINESS_DOMAIN_LABELS,
DOMAIN_FIELD_PREFIXES,
EXPENSE_BUSINESS_STAGE_LABELS,
EXPENSE_RISK_CATEGORY_ALIASES,
EXPENSE_RISK_CATEGORY_LABELS,
FIELD_ONTOLOGY,
@@ -75,6 +76,8 @@ class RiskRuleGenerationService:
raise ValueError("规则标题至少需要 2 个字。")
requires_attachment = bool(body.requires_attachment)
business_stage = self._normalize_business_stage(body.business_stage, domain)
business_stage_label = EXPENSE_BUSINESS_STAGE_LABELS.get(business_stage, "费用报销")
expense_category = self._normalize_expense_category(body.expense_category, domain)
expense_category_label = EXPENSE_RISK_CATEGORY_LABELS.get(expense_category or "", "")
@@ -83,6 +86,8 @@ class RiskRuleGenerationService:
draft = self._compile_with_model(
natural_language=natural_language,
domain=domain,
business_stage=business_stage,
business_stage_label=business_stage_label,
expense_category=expense_category,
expense_category_label=expense_category_label,
fields=fields,
@@ -113,6 +118,8 @@ class RiskRuleGenerationService:
draft,
natural_language=natural_language,
domain=domain,
business_stage=business_stage,
business_stage_label=business_stage_label,
expense_category=expense_category,
expense_category_label=expense_category_label,
risk_level=risk_level,
@@ -155,6 +162,8 @@ class RiskRuleGenerationService:
"requires_attachment": requires_attachment,
"tag": "风险规则",
"detail_mode": "json_risk",
"business_stage": business_stage,
"business_stage_label": business_stage_label,
"expense_category": expense_category,
"expense_category_label": expense_category_label,
"risk_category": payload.get("risk_category"),
@@ -167,6 +176,11 @@ class RiskRuleGenerationService:
"evaluator": payload.get("evaluator"),
"generated_by": "natural_language",
"source_ref": "自然语言风险规则",
"last_operation": {
"action": "create",
"actor": actor,
"at": datetime.now(UTC).isoformat(),
},
},
)
self.db.add(asset)
@@ -192,6 +206,7 @@ class RiskRuleGenerationService:
"risk_level": risk_level,
"risk_score": risk_score["score"],
"domain": domain,
"business_stage": business_stage,
"expense_category": expense_category,
"requires_attachment": requires_attachment,
},
@@ -205,6 +220,8 @@ class RiskRuleGenerationService:
*,
natural_language: str,
domain: str,
business_stage: str,
business_stage_label: str,
expense_category: str | None,
expense_category_label: str,
fields: list[RiskRuleField],
@@ -221,6 +238,8 @@ class RiskRuleGenerationService:
messages = build_risk_rule_compiler_messages(
domain=domain,
domain_label=BUSINESS_DOMAIN_LABELS[domain],
business_stage=business_stage,
business_stage_label=business_stage_label,
expense_category=expense_category,
expense_category_label=expense_category_label,
natural_language=natural_language,
@@ -372,6 +391,8 @@ class RiskRuleGenerationService:
*,
natural_language: str,
domain: str,
business_stage: str,
business_stage_label: str,
expense_category: str | None,
expense_category_label: str,
risk_level: str,
@@ -408,6 +429,8 @@ class RiskRuleGenerationService:
"field_keys": field_keys,
"condition_summary": condition_summary,
"natural_language": natural_language,
"business_stage": business_stage,
"business_stage_label": business_stage_label,
}
semantic_type = str(draft.get("semantic_type") or "").strip()
if semantic_type:
@@ -431,6 +454,8 @@ class RiskRuleGenerationService:
params["keywords"] = keywords
params["search_fields"] = field_keys
applies_to: dict[str, Any] = {"domains": [domain]}
if business_stage:
applies_to["business_stages"] = [business_stage]
if expense_category:
applies_to["expense_categories"] = [expense_category]
@@ -485,6 +510,8 @@ class RiskRuleGenerationService:
"rule_title": rule_title,
"expense_category": expense_category,
"expense_category_label": expense_category_label,
"business_stage": business_stage,
"business_stage_label": business_stage_label,
"natural_language": natural_language,
"business_explanation": self._clean_text(draft.get("description")),
"condition_summary": condition_summary,
@@ -558,6 +585,19 @@ class RiskRuleGenerationService:
raise ValueError(f"费用领域仅支持:{allowed}")
return normalized
@staticmethod
def _normalize_business_stage(value: str | None, domain: str) -> str:
if domain != AgentAssetDomain.EXPENSE.value:
return "reimbursement"
normalized = str(value or "reimbursement").strip().lower()
if not normalized:
normalized = "reimbursement"
if normalized not in EXPENSE_BUSINESS_STAGE_LABELS:
allowed = "".join(EXPENSE_BUSINESS_STAGE_LABELS.values())
raise ValueError(f"业务环节仅支持:{allowed}")
return normalized
def _resolve_fields(self, text: str, *, domain: str) -> list[RiskRuleField]:
prefixes = DOMAIN_FIELD_PREFIXES.get(domain, ())
candidates = [field for field in FIELD_ONTOLOGY if field.key.startswith(prefixes)]