feat: 增强风险规则生成引擎与预算中心页面
后端拆分风险规则生成为解释器、语义分析、本体对齐等子模块, 优化模板执行和流程图生成,完善员工种子数据和导入逻辑,增强 报销单权限策略和草稿持久化,前端新增预算中心视图和趋势图 组件,重构审计页面和风险规则测试对话框交互,完善文档中心 和报销创建页面细节,补充单元测试覆盖。
This commit is contained in:
@@ -1,62 +1,25 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
from datetime import UTC, date, datetime
|
||||
from decimal import Decimal
|
||||
from pathlib import Path
|
||||
from datetime import UTC, datetime
|
||||
|
||||
from sqlalchemy import inspect, select, text
|
||||
from sqlalchemy import select
|
||||
|
||||
from app.core.agent_enums import (
|
||||
AgentAssetContentType,
|
||||
AgentAssetDomain,
|
||||
AgentAssetStatus,
|
||||
AgentAssetType,
|
||||
AgentName,
|
||||
AgentPermissionLevel,
|
||||
AgentReviewStatus,
|
||||
AgentRunSource,
|
||||
AgentRunStatus,
|
||||
AgentToolType,
|
||||
)
|
||||
from app.models.agent_asset import AgentAsset, AgentAssetReview, AgentAssetVersion
|
||||
from app.models.agent_run import AgentRun, AgentToolCall, SemanticParseLog
|
||||
from app.models.audit_log import AuditLog
|
||||
from app.models.financial_record import (
|
||||
AccountsPayableRecord,
|
||||
AccountsReceivableRecord,
|
||||
ExpenseClaim,
|
||||
ExpenseClaimItem,
|
||||
)
|
||||
from app.services.agent_asset_rule_library import AgentAssetRuleLibraryManager
|
||||
from app.services.agent_asset_spreadsheet import (
|
||||
AgentAssetSpreadsheetManager,
|
||||
COMPANY_COMMUNICATION_EXPENSE_RULE_CODE,
|
||||
COMPANY_COMMUNICATION_EXPENSE_RULE_FILENAME,
|
||||
COMPANY_TRAVEL_EXPENSE_RULE_CODE,
|
||||
COMPANY_TRAVEL_EXPENSE_RULE_FILENAME,
|
||||
FINANCE_RULES_LIBRARY,
|
||||
RISK_RULES_LIBRARY,
|
||||
)
|
||||
from app.services.expense_rule_runtime import (
|
||||
build_scene_submission_standard_markdown,
|
||||
build_travel_risk_control_standard_markdown,
|
||||
)
|
||||
from app.services.agent_foundation_constants import (
|
||||
ATTACHMENT_RULE_ASSET_CODE,
|
||||
ATTACHMENT_RULE_RUNTIME_CONFIG,
|
||||
COMPANY_COMMUNICATION_RULE_SCENARIO_JSON,
|
||||
COMPANY_COMMUNICATION_RULE_VERSION,
|
||||
COMPANY_TRAVEL_RULE_SCENARIO_JSON,
|
||||
COMPANY_TRAVEL_RULE_VERSION,
|
||||
DEMO_EXPENSE_CLAIM_SIGNATURES,
|
||||
DEMO_PAYABLE_SIGNATURES,
|
||||
DEMO_RECEIVABLE_SIGNATURES,
|
||||
LEGACY_RULE_CODES,
|
||||
PLATFORM_DESTINATION_LOCATION_RULE_FILENAME,
|
||||
)
|
||||
from app.core.logging import get_logger
|
||||
from app.models.agent_asset import AgentAsset
|
||||
from app.services.agent_asset_rule_library import AgentAssetRuleLibraryManager
|
||||
from app.services.agent_asset_spreadsheet import (
|
||||
RISK_RULES_LIBRARY,
|
||||
)
|
||||
from app.services.agent_foundation_constants import (
|
||||
PLATFORM_DESTINATION_LOCATION_RULE_FILENAME,
|
||||
)
|
||||
|
||||
logger = get_logger("app.services.agent_foundation")
|
||||
|
||||
@@ -67,20 +30,51 @@ class AgentFoundationRiskRuleMixin:
|
||||
|
||||
manifests: list[tuple[str, dict[str, object]]] = []
|
||||
|
||||
for file_name in sorted(manager.list_rule_library_json_files(library=RISK_RULES_LIBRARY)):
|
||||
for file_name in sorted(
|
||||
manager.list_rule_library_json_files(library=RISK_RULES_LIBRARY)
|
||||
):
|
||||
|
||||
payload = manager.read_rule_library_json(library=RISK_RULES_LIBRARY, file_name=file_name)
|
||||
payload = manager.read_rule_library_json(
|
||||
library=RISK_RULES_LIBRARY,
|
||||
file_name=file_name,
|
||||
)
|
||||
|
||||
if payload.get("enabled") is False:
|
||||
|
||||
continue
|
||||
|
||||
if self._is_user_generated_risk_manifest(payload):
|
||||
|
||||
continue
|
||||
|
||||
manifests.append((file_name, payload))
|
||||
|
||||
return manifests
|
||||
|
||||
@staticmethod
|
||||
|
||||
def _is_user_generated_risk_manifest(manifest: dict[str, object]) -> bool:
|
||||
|
||||
rule_code = str(manifest.get("rule_code") or "").strip().lower()
|
||||
|
||||
metadata = manifest.get("metadata") if isinstance(manifest.get("metadata"), dict) else {}
|
||||
|
||||
stability = str(metadata.get("stability") or "").strip().lower()
|
||||
|
||||
source_ref = str(metadata.get("source_ref") or "").strip()
|
||||
|
||||
if stability == "generated_draft":
|
||||
|
||||
return True
|
||||
|
||||
if source_ref == "自然语言风险规则":
|
||||
|
||||
return True
|
||||
|
||||
return ".generated_" in rule_code
|
||||
|
||||
@staticmethod
|
||||
|
||||
def _resolve_platform_risk_category(manifest: dict[str, object]) -> str:
|
||||
|
||||
explicit = str(manifest.get("risk_category") or "").strip()
|
||||
@@ -91,7 +85,9 @@ class AgentFoundationRiskRuleMixin:
|
||||
|
||||
rule_code = str(manifest.get("rule_code") or "").strip().lower()
|
||||
|
||||
applies_to = manifest.get("applies_to") if isinstance(manifest.get("applies_to"), dict) else {}
|
||||
applies_to = (
|
||||
manifest.get("applies_to") if isinstance(manifest.get("applies_to"), dict) else {}
|
||||
)
|
||||
|
||||
domains = {str(item or "").strip().lower() for item in applies_to.get("domains") or []}
|
||||
|
||||
@@ -133,7 +129,9 @@ class AgentFoundationRiskRuleMixin:
|
||||
|
||||
return [category] if category else ["通用"]
|
||||
|
||||
def _platform_risk_config_json(self, file_name: str, manifest: dict[str, object]) -> dict[str, object]:
|
||||
def _platform_risk_config_json(
|
||||
self, file_name: str, manifest: dict[str, object]
|
||||
) -> dict[str, object]:
|
||||
|
||||
outcomes = manifest.get("outcomes") if isinstance(manifest.get("outcomes"), dict) else {}
|
||||
|
||||
@@ -191,7 +189,9 @@ class AgentFoundationRiskRuleMixin:
|
||||
|
||||
continue
|
||||
|
||||
metadata = manifest.get("metadata") if isinstance(manifest.get("metadata"), dict) else {}
|
||||
metadata = (
|
||||
manifest.get("metadata") if isinstance(manifest.get("metadata"), dict) else {}
|
||||
)
|
||||
|
||||
source_ref = str(metadata.get("source_ref") or "").strip()
|
||||
|
||||
@@ -255,7 +255,11 @@ class AgentFoundationRiskRuleMixin:
|
||||
|
||||
"Platform risk rules synced from library",
|
||||
|
||||
extra={"manifest_count": manifest_count, "created_count": synced, "total": len(after_codes)},
|
||||
extra={
|
||||
"manifest_count": manifest_count,
|
||||
"created_count": synced,
|
||||
"total": len(after_codes),
|
||||
},
|
||||
|
||||
)
|
||||
|
||||
@@ -271,7 +275,9 @@ class AgentFoundationRiskRuleMixin:
|
||||
|
||||
continue
|
||||
|
||||
metadata = manifest.get("metadata") if isinstance(manifest.get("metadata"), dict) else {}
|
||||
metadata = (
|
||||
manifest.get("metadata") if isinstance(manifest.get("metadata"), dict) else {}
|
||||
)
|
||||
|
||||
source_ref = str(metadata.get("source_ref") or "").strip()
|
||||
|
||||
@@ -347,7 +353,11 @@ class AgentFoundationRiskRuleMixin:
|
||||
|
||||
version="v1.0.0",
|
||||
|
||||
content=self._platform_risk_rule_markdown(asset, manifest=manifest, file_name=file_name),
|
||||
content=self._platform_risk_rule_markdown(
|
||||
asset,
|
||||
manifest=manifest,
|
||||
file_name=file_name,
|
||||
),
|
||||
|
||||
content_type=AgentAssetContentType.MARKDOWN.value,
|
||||
|
||||
@@ -389,19 +399,25 @@ class AgentFoundationRiskRuleMixin:
|
||||
|
||||
config = asset.config_json if isinstance(asset.config_json, dict) else {}
|
||||
|
||||
rule_document = config.get("rule_document") if isinstance(config.get("rule_document"), dict) else {}
|
||||
rule_document = (
|
||||
config.get("rule_document") if isinstance(config.get("rule_document"), dict) else {}
|
||||
)
|
||||
|
||||
resolved_file_name = file_name or str(rule_document.get("file_name") or "").strip()
|
||||
|
||||
evaluator = str(config.get("evaluator") or (manifest or {}).get("evaluator") or "").strip()
|
||||
|
||||
ontology_signal = str(config.get("ontology_signal") or (manifest or {}).get("ontology_signal") or "").strip()
|
||||
ontology_signal = str(
|
||||
config.get("ontology_signal") or (manifest or {}).get("ontology_signal") or ""
|
||||
).strip()
|
||||
|
||||
source_ref = str(config.get("source_ref") or "").strip()
|
||||
|
||||
if not source_ref and isinstance(manifest, dict):
|
||||
|
||||
metadata = manifest.get("metadata") if isinstance(manifest.get("metadata"), dict) else {}
|
||||
metadata = (
|
||||
manifest.get("metadata") if isinstance(manifest.get("metadata"), dict) else {}
|
||||
)
|
||||
|
||||
source_ref = str(metadata.get("source_ref") or "").strip()
|
||||
|
||||
@@ -457,7 +473,10 @@ class AgentFoundationRiskRuleMixin:
|
||||
|
||||
return AgentFoundationRiskRuleMixin._platform_risk_rule_markdown(
|
||||
|
||||
AgentAsset(name="申报地点与票据地点一致", config_json={"evaluator": "location_consistency"}),
|
||||
AgentAsset(
|
||||
name="申报地点与票据地点一致",
|
||||
config_json={"evaluator": "location_consistency"},
|
||||
),
|
||||
|
||||
manifest={
|
||||
|
||||
|
||||
Reference in New Issue
Block a user