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