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

@@ -4,6 +4,7 @@ import json
from collections import defaultdict
from datetime import UTC, datetime
from typing import Any
from sqlalchemy.orm import Session
from app.core.agent_enums import (
@@ -27,13 +28,14 @@ from app.schemas.agent_asset import (
)
from app.services.agent_asset_json_rules import AgentAssetJsonRuleMixin
from app.services.agent_asset_onlyoffice import AgentAssetOnlyOfficeMixin
from app.services.agent_asset_risk_rule_simulation import AgentAssetRiskRuleSimulationMixin
from app.services.agent_asset_risk_rule_testing import AgentAssetRiskRuleTestingMixin
from app.services.agent_asset_rule_library import AgentAssetRuleLibraryManager
from app.services.agent_asset_spreadsheet import AgentAssetSpreadsheetManager
from app.services.agent_asset_spreadsheet_helpers import AgentAssetSpreadsheetHelperMixin
from app.services.agent_asset_timeline import AgentAssetTimelineMixin
from app.services.agent_asset_spreadsheet import AgentAssetSpreadsheetManager
from app.services.agent_foundation import AgentFoundationService
from app.services.audit import AuditLogService
from app.services.settings import resolve_onlyoffice_settings
logger = get_logger("app.services.agent_assets")
@@ -41,6 +43,8 @@ logger = get_logger("app.services.agent_assets")
class AgentAssetService(
AgentAssetOnlyOfficeMixin,
AgentAssetSpreadsheetHelperMixin,
AgentAssetRiskRuleTestingMixin,
AgentAssetRiskRuleSimulationMixin,
AgentAssetTimelineMixin,
AgentAssetJsonRuleMixin,
):
@@ -66,10 +70,7 @@ class AgentAssetService(
asset_type=asset_type, status=status, domain=domain, keyword=keyword
)
version_stats = self._collect_version_stats(assets)
return [
self._serialize_list_item(asset, version_stats.get(asset.id))
for asset in assets
]
return [self._serialize_list_item(asset, version_stats.get(asset.id)) for asset in assets]
def get_asset(self, asset_id: str) -> AgentAssetRead | None:
self._ensure_ready()
@@ -88,9 +89,7 @@ class AgentAssetService(
else next(iter(self.repository.list_reviews(asset_id, limit=1)), None)
)
current_version = (
self.repository.get_version(asset_id, working_version)
if working_version
else None
self.repository.get_version(asset_id, working_version) if working_version else None
)
version_stats = self._collect_version_stats([asset]).get(asset.id)
return AgentAssetRead(
@@ -100,12 +99,14 @@ class AgentAssetService(
else None,
current_version_content_type=current_version.content_type if current_version else None,
current_version_change_note=current_version.change_note if current_version else None,
recent_versions=[
self._serialize_version(item, asset) for item in recent_versions
],
recent_versions=[self._serialize_version(item, asset) for item in recent_versions],
latest_review=AgentAssetReviewRead.model_validate(latest_review)
if latest_review
else None,
latest_test_summary=self.get_latest_risk_rule_test_summary(asset)
if str((asset.config_json or {}).get("detail_mode") or "").strip().lower()
== "json_risk"
else None,
)
def create_asset(
@@ -301,6 +302,13 @@ class AgentAssetService(
if self.repository.get_version(asset_id, payload.version) is None:
raise LookupError(f"版本 {payload.version} 不存在")
if asset.asset_type == AgentAssetType.RULE.value:
if (
str((asset.config_json or {}).get("detail_mode") or "").strip().lower()
== "json_risk"
and payload.review_status == AgentReviewStatus.PENDING
and not self.get_latest_risk_rule_test_summary(asset).test_passed
):
raise PermissionError("当前规则版本尚未完成测试通过确认,不能提交审核。")
working_version = self._resolve_working_version(asset)
if payload.version != working_version:
raise ValueError("只能对当前工作版本发起审核。")
@@ -594,11 +602,10 @@ class AgentAssetService(
),
)
def _collect_version_stats(
self, assets: list[AgentAsset]
) -> dict[str, dict[str, int | str | None]]:
def _collect_version_stats(self, assets: list[AgentAsset]) -> dict[str, dict[str, Any]]:
asset_ids = [item.id for item in assets]
versions = self.repository.list_versions_for_assets(asset_ids)
reviews = self.repository.list_reviews_for_assets(asset_ids)
spreadsheet_logs = self.audit_service.repository.list_for_resources(
resource_type=AgentAssetType.RULE.value,
resource_ids=[
@@ -610,23 +617,33 @@ class AgentAssetService(
],
action="edit_rule_spreadsheet",
)
working_versions = {
item.id: self._resolve_working_version(item) for item in assets
}
working_versions = {item.id: self._resolve_working_version(item) for item in assets}
version_counts: dict[str, int] = defaultdict(int)
modified_by: dict[str, str | None] = {item.id: None for item in assets}
published_versions = {item.id: self._resolve_published_version(item) for item in assets}
published_by: dict[str, str | None] = {}
published_at: dict[str, datetime | None] = {}
spreadsheet_edit_counts: dict[str, int] = defaultdict(int)
spreadsheet_last_actor: dict[str, str | None] = {}
spreadsheet_last_changed_at: dict[str, datetime] = {}
for version in versions:
version_counts[version.asset_id] += 1
if (
modified_by.get(version.asset_id) is None
and version.version == working_versions.get(version.asset_id)
):
if modified_by.get(
version.asset_id
) is None and version.version == working_versions.get(version.asset_id):
modified_by[version.asset_id] = version.created_by
for review in reviews:
if review.asset_id in published_at:
continue
if review.version != published_versions.get(review.asset_id):
continue
if review.review_status != AgentReviewStatus.APPROVED.value:
continue
published_by[review.asset_id] = review.reviewer
published_at[review.asset_id] = review.reviewed_at or review.created_at
for log in spreadsheet_logs:
spreadsheet_edit_counts[log.resource_id] += 1
last_changed_at = spreadsheet_last_changed_at.get(log.resource_id)
@@ -652,6 +669,8 @@ class AgentAssetService(
and spreadsheet_last_actor.get(item.id)
else modified_by.get(item.id)
),
"published_by": published_by.get(item.id),
"published_at": published_at.get(item.id),
}
for item in assets
}
@@ -663,9 +682,11 @@ class AgentAssetService(
) -> AgentAssetListItem:
payload = AgentAssetListItem.model_validate(asset).model_dump()
payload["change_count"] = int((version_stats or {}).get("change_count") or 0)
payload["modified_by"] = (
str((version_stats or {}).get("modified_by") or "").strip() or None
payload["modified_by"] = str((version_stats or {}).get("modified_by") or "").strip() or None
payload["published_by"] = (
str((version_stats or {}).get("published_by") or "").strip() or None
)
payload["published_at"] = (version_stats or {}).get("published_at")
return AgentAssetListItem.model_validate(payload)
@staticmethod