feat: 新增预算中心本体与风险规则评分回填
后端新增预算本体解析模块和风险规则评分回填服务,优化规则 生成本体对齐和提示词构建,增强费用类型关键词和本体验证, 完善报销查询和审计接口,前端预算中心页面增加对话框和本 体工具函数,重构审计页面元数据和视图模型,补充单元测试。
This commit is contained in:
@@ -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)]
|
||||
|
||||
Reference in New Issue
Block a user