refactor: 重构 AuditView 和 TravelReimbursementCreateView 相关代码
- 优化 agent_assets、agent_foundation、user_agent 服务层结构 - 更新 AuditView 视图和脚本 - 更新 TravelReimbursementCreateView 脚本 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from collections import defaultdict
|
||||
from dataclasses import dataclass
|
||||
from datetime import UTC, datetime
|
||||
from pathlib import Path
|
||||
@@ -30,6 +31,8 @@ from app.schemas.agent_asset import (
|
||||
AgentAssetRead,
|
||||
AgentAssetReviewCreate,
|
||||
AgentAssetReviewRead,
|
||||
AgentAssetRuleJsonRead,
|
||||
AgentAssetRuleJsonWrite,
|
||||
AgentAssetSpreadsheetChangeRecordRead,
|
||||
AgentAssetSpreadsheetDiffCellRead,
|
||||
AgentAssetSpreadsheetDiffSheetRead,
|
||||
@@ -39,9 +42,15 @@ from app.schemas.agent_asset import (
|
||||
AgentAssetVersionRead,
|
||||
AgentAssetVersionTimelineItemRead,
|
||||
)
|
||||
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,
|
||||
RULE_LIBRARY_NAMES,
|
||||
RuleSpreadsheetMeta,
|
||||
SPREADSHEET_MIME_TYPE,
|
||||
@@ -74,6 +83,7 @@ class AgentAssetService:
|
||||
self.repository = AgentAssetRepository(db)
|
||||
self.audit_service = AuditLogService(db)
|
||||
self.spreadsheet_manager = AgentAssetSpreadsheetManager()
|
||||
self.rule_library_manager = AgentAssetRuleLibraryManager()
|
||||
|
||||
def list_assets(
|
||||
self,
|
||||
@@ -84,10 +94,16 @@ class AgentAssetService:
|
||||
keyword: str | None = None,
|
||||
) -> list[AgentAssetListItem]:
|
||||
self._ensure_ready()
|
||||
items = self.repository.list(
|
||||
if asset_type in {None, "", AgentAssetType.RULE.value}:
|
||||
self.sync_platform_risk_rules_from_library()
|
||||
assets = self.repository.list(
|
||||
asset_type=asset_type, status=status, domain=domain, keyword=keyword
|
||||
)
|
||||
return [AgentAssetListItem.model_validate(item) for item in items]
|
||||
version_stats = self._collect_version_stats(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()
|
||||
@@ -110,8 +126,9 @@ class AgentAssetService:
|
||||
if working_version
|
||||
else None
|
||||
)
|
||||
version_stats = self._collect_version_stats([asset]).get(asset.id)
|
||||
return AgentAssetRead(
|
||||
**AgentAssetListItem.model_validate(asset).model_dump(),
|
||||
**self._serialize_list_item(asset, version_stats).model_dump(),
|
||||
current_version_content=self._deserialize_content(current_version)
|
||||
if current_version
|
||||
else None,
|
||||
@@ -500,8 +517,8 @@ class AgentAssetService:
|
||||
)
|
||||
|
||||
asset = self._require_spreadsheet_rule(asset_id)
|
||||
resolved_version, metadata = self._resolve_spreadsheet_version_meta(asset, version=version)
|
||||
editable = self._can_edit_spreadsheet_version(asset, current_user, resolved_version)
|
||||
resolved_version, metadata = self._resolve_current_spreadsheet_meta(asset)
|
||||
editable = self._can_edit_current_spreadsheet(current_user)
|
||||
return self._build_onlyoffice_spreadsheet_config(
|
||||
asset_id=asset.id,
|
||||
current_user=current_user,
|
||||
@@ -525,7 +542,7 @@ class AgentAssetService:
|
||||
return file_path, metadata.mime_type, metadata.file_name
|
||||
|
||||
asset = self._require_spreadsheet_rule(asset_id)
|
||||
_, metadata = self._resolve_spreadsheet_version_meta(asset, version=version)
|
||||
_, metadata = self._resolve_current_spreadsheet_meta(asset)
|
||||
file_path = self.spreadsheet_manager.resolve_storage_path(metadata.storage_key)
|
||||
if not file_path.exists():
|
||||
raise FileNotFoundError(metadata.file_name)
|
||||
@@ -575,58 +592,31 @@ class AgentAssetService:
|
||||
if not content:
|
||||
raise ValueError("规则表文件内容不能为空。")
|
||||
|
||||
next_version = self._increment_version(self._resolve_working_version(asset))
|
||||
metadata = self.spreadsheet_manager.store_spreadsheet(
|
||||
asset_id=asset.id,
|
||||
version=next_version,
|
||||
file_name=normalized_name,
|
||||
_, current_metadata = self._resolve_current_spreadsheet_meta(asset)
|
||||
file_name = current_metadata.file_name or self._resolve_default_spreadsheet_file_name(asset)
|
||||
metadata = self._store_current_rule_spreadsheet(
|
||||
asset,
|
||||
file_name=file_name,
|
||||
content=content,
|
||||
actor_name=actor,
|
||||
actor=actor,
|
||||
source=source,
|
||||
)
|
||||
markdown = self.spreadsheet_manager.build_version_markdown(
|
||||
rule_name=asset.name,
|
||||
version=next_version,
|
||||
metadata=metadata,
|
||||
)
|
||||
self.create_version(
|
||||
asset.id,
|
||||
AgentAssetVersionCreate(
|
||||
version=next_version,
|
||||
content=markdown,
|
||||
content_type=AgentAssetContentType.MARKDOWN,
|
||||
change_note=change_note or f"上传 Excel 规则表:{normalized_name}",
|
||||
created_by=actor,
|
||||
),
|
||||
self.audit_service.log_action(
|
||||
actor=actor,
|
||||
action="edit_rule_spreadsheet",
|
||||
resource_type=asset.asset_type,
|
||||
resource_id=asset.id,
|
||||
before_json={"storage_key": current_metadata.storage_key},
|
||||
after_json={
|
||||
"summary": change_note or f"上传并覆盖当前规则表:{normalized_name}",
|
||||
"changed_sheet_count": 0,
|
||||
"changed_cell_count": 0,
|
||||
"sheet_changes": [],
|
||||
"cell_changes": [],
|
||||
"storage_key": metadata.storage_key,
|
||||
},
|
||||
request_id=request_id,
|
||||
)
|
||||
|
||||
refreshed = self.repository.get(asset.id)
|
||||
if refreshed is None:
|
||||
raise LookupError("Asset not found")
|
||||
|
||||
config_json = dict(refreshed.config_json or {})
|
||||
config_json["detail_mode"] = "spreadsheet"
|
||||
config_json["tag"] = str(config_json.get("tag") or "财务规则").strip() or "财务规则"
|
||||
current_document_meta = metadata
|
||||
rule_library = str(config_json.get("rule_library") or "").strip()
|
||||
if rule_library in RULE_LIBRARY_NAMES:
|
||||
current_document_meta = self.spreadsheet_manager.store_rule_library_spreadsheet(
|
||||
library=rule_library,
|
||||
file_name=normalized_name,
|
||||
content=content,
|
||||
actor_name=actor,
|
||||
source=source,
|
||||
)
|
||||
rule_document = self.spreadsheet_manager.build_rule_document_config(
|
||||
current_document_meta,
|
||||
asset_version=next_version,
|
||||
)
|
||||
rule_document["storage_key"] = current_document_meta.storage_key
|
||||
config_json["rule_document"] = rule_document
|
||||
refreshed.config_json = config_json
|
||||
self.repository.save_asset(refreshed)
|
||||
return self.get_asset(asset.id) # type: ignore[return-value]
|
||||
|
||||
def import_rule_spreadsheet_content(
|
||||
@@ -646,10 +636,7 @@ class AgentAssetService:
|
||||
if Path(normalized_name).suffix.lower() != ".xlsx":
|
||||
raise ValueError("当前仅支持导入 .xlsx 格式的规则表。")
|
||||
|
||||
_, current_metadata = self._resolve_spreadsheet_version_meta(
|
||||
asset,
|
||||
version=self._resolve_working_version(asset),
|
||||
)
|
||||
_, current_metadata = self._resolve_current_spreadsheet_meta(asset)
|
||||
imported_content = self.spreadsheet_manager.rebuild_from_uploaded_content(content)
|
||||
return self.upload_rule_spreadsheet(
|
||||
asset.id,
|
||||
@@ -681,10 +668,10 @@ class AgentAssetService:
|
||||
callback = self._parse_onlyoffice_callback(payload)
|
||||
if callback.status not in {2, 6} or not callback.download_url:
|
||||
return
|
||||
if self._resolve_working_version(asset) != str(version or "").strip():
|
||||
if str(version or "").strip() not in {"", "current", self._resolve_working_version(asset)}:
|
||||
return
|
||||
|
||||
_, current_metadata = self._resolve_spreadsheet_version_meta(asset, version=version)
|
||||
_, current_metadata = self._resolve_current_spreadsheet_meta(asset)
|
||||
request = Request(
|
||||
callback.download_url,
|
||||
headers={"User-Agent": "x-financial-onlyoffice-agent-asset"},
|
||||
@@ -723,12 +710,11 @@ class AgentAssetService:
|
||||
resolved_actor_name = str(actor_name or "").strip() or (
|
||||
callback.users[0] if callback.users else "ONLYOFFICE"
|
||||
)
|
||||
self.upload_rule_spreadsheet(
|
||||
asset.id,
|
||||
filename=current_metadata.file_name,
|
||||
self._store_current_rule_spreadsheet(
|
||||
asset,
|
||||
file_name=current_metadata.file_name,
|
||||
content=content,
|
||||
actor=resolved_actor_name,
|
||||
change_note=change_note,
|
||||
source="onlyoffice",
|
||||
)
|
||||
if changed_sheet_count > 0 or changed_cell_count > 0:
|
||||
@@ -737,7 +723,7 @@ class AgentAssetService:
|
||||
action="edit_rule_spreadsheet",
|
||||
resource_type=asset.asset_type,
|
||||
resource_id=asset.id,
|
||||
before_json={"version": version},
|
||||
before_json={"storage_key": current_metadata.storage_key},
|
||||
after_json={
|
||||
"summary": change_note,
|
||||
"changed_sheet_count": changed_sheet_count,
|
||||
@@ -750,6 +736,11 @@ class AgentAssetService:
|
||||
def _ensure_ready(self) -> None:
|
||||
AgentFoundationService(self.db).ensure_foundation_ready()
|
||||
|
||||
def sync_platform_risk_rules_from_library(self) -> int:
|
||||
manifest_count = AgentFoundationService(self.db).sync_platform_risk_rules_from_library()
|
||||
self.db.commit()
|
||||
return manifest_count
|
||||
|
||||
def _validate_version_payload(
|
||||
self, asset: AgentAsset, payload: AgentAssetVersionCreate
|
||||
) -> None:
|
||||
@@ -1003,6 +994,7 @@ class AgentAssetService:
|
||||
)
|
||||
return [
|
||||
AgentAssetSpreadsheetChangeRecordRead(
|
||||
id=log.id,
|
||||
actor=log.actor,
|
||||
changed_at=log.created_at,
|
||||
summary=str((log.after_json or {}).get("summary") or "ONLYOFFICE 在线编辑保存。"),
|
||||
@@ -1046,6 +1038,80 @@ class AgentAssetService:
|
||||
),
|
||||
)
|
||||
|
||||
def _collect_version_stats(
|
||||
self, assets: list[AgentAsset]
|
||||
) -> dict[str, dict[str, int | str | None]]:
|
||||
asset_ids = [item.id for item in assets]
|
||||
versions = self.repository.list_versions_for_assets(asset_ids)
|
||||
spreadsheet_logs = self.audit_service.repository.list_for_resources(
|
||||
resource_type=AgentAssetType.RULE.value,
|
||||
resource_ids=[
|
||||
item.id
|
||||
for item in assets
|
||||
if item.asset_type == AgentAssetType.RULE.value
|
||||
and str((item.config_json or {}).get("detail_mode") or "").strip().lower()
|
||||
== "spreadsheet"
|
||||
],
|
||||
action="edit_rule_spreadsheet",
|
||||
)
|
||||
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}
|
||||
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)
|
||||
):
|
||||
modified_by[version.asset_id] = version.created_by
|
||||
|
||||
for log in spreadsheet_logs:
|
||||
spreadsheet_edit_counts[log.resource_id] += 1
|
||||
last_changed_at = spreadsheet_last_changed_at.get(log.resource_id)
|
||||
if last_changed_at is None or log.created_at >= last_changed_at:
|
||||
spreadsheet_last_changed_at[log.resource_id] = log.created_at
|
||||
spreadsheet_last_actor[log.resource_id] = log.actor
|
||||
|
||||
return {
|
||||
item.id: {
|
||||
"change_count": (
|
||||
spreadsheet_edit_counts.get(item.id, 0)
|
||||
if item.asset_type == AgentAssetType.RULE.value
|
||||
and str((item.config_json or {}).get("detail_mode") or "").strip().lower()
|
||||
== "spreadsheet"
|
||||
and spreadsheet_edit_counts.get(item.id, 0) > 0
|
||||
else max(version_counts.get(item.id, 0) - 1, 0)
|
||||
),
|
||||
"modified_by": (
|
||||
spreadsheet_last_actor.get(item.id)
|
||||
if item.asset_type == AgentAssetType.RULE.value
|
||||
and str((item.config_json or {}).get("detail_mode") or "").strip().lower()
|
||||
== "spreadsheet"
|
||||
and spreadsheet_last_actor.get(item.id)
|
||||
else modified_by.get(item.id)
|
||||
),
|
||||
}
|
||||
for item in assets
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _serialize_list_item(
|
||||
asset: AgentAsset,
|
||||
version_stats: dict[str, int | str | None] | None = None,
|
||||
) -> 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
|
||||
)
|
||||
return AgentAssetListItem.model_validate(payload)
|
||||
|
||||
@staticmethod
|
||||
def _sort_versions(
|
||||
versions: list[AgentAssetVersion], current_version: str | None
|
||||
@@ -1104,6 +1170,138 @@ class AgentAssetService:
|
||||
raise FileNotFoundError("规则表版本快照不存在。")
|
||||
return resolved_version, metadata
|
||||
|
||||
def _resolve_current_spreadsheet_meta(
|
||||
self,
|
||||
asset: AgentAsset,
|
||||
) -> tuple[str, RuleSpreadsheetMeta]:
|
||||
config_json = dict(asset.config_json or {})
|
||||
current_meta = self._read_current_rule_document_meta(asset)
|
||||
file_name = (
|
||||
current_meta.file_name
|
||||
if current_meta is not None and current_meta.file_name
|
||||
else self._resolve_default_spreadsheet_file_name(asset)
|
||||
)
|
||||
library = self._resolve_spreadsheet_rule_library(asset)
|
||||
storage_key = (Path("rules") / library / file_name).as_posix()
|
||||
file_path = self.spreadsheet_manager.resolve_storage_path(storage_key)
|
||||
|
||||
if not file_path.exists():
|
||||
content: bytes | None = None
|
||||
if current_meta is not None and current_meta.storage_key:
|
||||
try:
|
||||
legacy_path = self.spreadsheet_manager.resolve_storage_path(
|
||||
current_meta.storage_key
|
||||
)
|
||||
except FileNotFoundError:
|
||||
legacy_path = None
|
||||
if legacy_path is not None and legacy_path.exists():
|
||||
content = legacy_path.read_bytes()
|
||||
if content is None:
|
||||
content = AgentAssetSpreadsheetManager.build_blank_rule_workbook(
|
||||
Path(file_name).stem or "规则表"
|
||||
)
|
||||
meta = self.spreadsheet_manager.store_rule_library_spreadsheet(
|
||||
library=library,
|
||||
file_name=file_name,
|
||||
content=content,
|
||||
actor_name=(
|
||||
current_meta.updated_by
|
||||
if current_meta is not None and current_meta.updated_by
|
||||
else "system"
|
||||
),
|
||||
source="current-rule",
|
||||
)
|
||||
else:
|
||||
content = file_path.read_bytes()
|
||||
meta = RuleSpreadsheetMeta(
|
||||
file_name=file_name,
|
||||
storage_key=storage_key,
|
||||
mime_type=(
|
||||
current_meta.mime_type
|
||||
if current_meta is not None and current_meta.mime_type
|
||||
else SPREADSHEET_MIME_TYPE
|
||||
),
|
||||
size_bytes=file_path.stat().st_size,
|
||||
checksum=self._hash_bytes(content),
|
||||
updated_at=datetime.fromtimestamp(file_path.stat().st_mtime, UTC).isoformat(),
|
||||
updated_by=(
|
||||
current_meta.updated_by
|
||||
if current_meta is not None and current_meta.updated_by
|
||||
else "system"
|
||||
),
|
||||
source=(
|
||||
current_meta.source
|
||||
if current_meta is not None and current_meta.source
|
||||
else "current-rule"
|
||||
),
|
||||
)
|
||||
|
||||
expected_document = {
|
||||
**self.spreadsheet_manager.build_rule_document_config(
|
||||
meta,
|
||||
asset_version="current",
|
||||
),
|
||||
"storage_key": meta.storage_key,
|
||||
}
|
||||
if config_json.get("rule_document") != expected_document:
|
||||
config_json["detail_mode"] = "spreadsheet"
|
||||
config_json["tag"] = str(config_json.get("tag") or "财务规则").strip() or "财务规则"
|
||||
config_json["rule_library"] = library
|
||||
config_json["rule_document"] = expected_document
|
||||
asset.config_json = config_json
|
||||
self.repository.save_asset(asset)
|
||||
|
||||
return "current", meta
|
||||
|
||||
def _store_current_rule_spreadsheet(
|
||||
self,
|
||||
asset: AgentAsset,
|
||||
*,
|
||||
file_name: str,
|
||||
content: bytes,
|
||||
actor: str,
|
||||
source: str,
|
||||
) -> RuleSpreadsheetMeta:
|
||||
library = self._resolve_spreadsheet_rule_library(asset)
|
||||
metadata = self.spreadsheet_manager.store_rule_library_spreadsheet(
|
||||
library=library,
|
||||
file_name=file_name,
|
||||
content=content,
|
||||
actor_name=actor,
|
||||
source=source,
|
||||
)
|
||||
config_json = dict(asset.config_json or {})
|
||||
config_json["detail_mode"] = "spreadsheet"
|
||||
config_json["tag"] = str(config_json.get("tag") or "财务规则").strip() or "财务规则"
|
||||
config_json["rule_library"] = library
|
||||
config_json["rule_document"] = {
|
||||
**self.spreadsheet_manager.build_rule_document_config(
|
||||
metadata,
|
||||
asset_version="current",
|
||||
),
|
||||
"storage_key": metadata.storage_key,
|
||||
}
|
||||
asset.config_json = config_json
|
||||
self.repository.save_asset(asset)
|
||||
return metadata
|
||||
|
||||
@staticmethod
|
||||
def _resolve_spreadsheet_rule_library(asset: AgentAsset) -> str:
|
||||
config_json = dict(asset.config_json or {})
|
||||
library = str(config_json.get("rule_library") or FINANCE_RULES_LIBRARY).strip()
|
||||
if library not in RULE_LIBRARY_NAMES:
|
||||
return FINANCE_RULES_LIBRARY
|
||||
return library
|
||||
|
||||
@staticmethod
|
||||
def _resolve_default_spreadsheet_file_name(asset: AgentAsset) -> str:
|
||||
if asset.code == COMPANY_TRAVEL_EXPENSE_RULE_CODE:
|
||||
return COMPANY_TRAVEL_EXPENSE_RULE_FILENAME
|
||||
if asset.code == COMPANY_COMMUNICATION_EXPENSE_RULE_CODE:
|
||||
return COMPANY_COMMUNICATION_EXPENSE_RULE_FILENAME
|
||||
fallback = Path(str(asset.name or "规则表").strip()).name
|
||||
return fallback if fallback.lower().endswith(".xlsx") else f"{fallback}.xlsx"
|
||||
|
||||
def _build_onlyoffice_spreadsheet_config(
|
||||
self,
|
||||
*,
|
||||
@@ -1286,6 +1484,11 @@ class AgentAssetService:
|
||||
can_edit = current_user.is_admin or "manager" in role_codes or "finance" in role_codes
|
||||
return can_edit and AgentAssetService._resolve_working_version(asset) == str(version or "").strip()
|
||||
|
||||
@staticmethod
|
||||
def _can_edit_current_spreadsheet(current_user: CurrentUserContext) -> bool:
|
||||
role_codes = {str(item).strip() for item in current_user.role_codes}
|
||||
return current_user.is_admin or "manager" in role_codes or "finance" in role_codes
|
||||
|
||||
@staticmethod
|
||||
def _build_onlyoffice_document_key(
|
||||
asset_id: str,
|
||||
@@ -1428,3 +1631,93 @@ class AgentAssetService:
|
||||
if not normalized.startswith(prefix) or suffix not in normalized:
|
||||
return None
|
||||
return normalized.removeprefix(prefix).split(suffix, 1)[0].strip() or None
|
||||
|
||||
def _resolve_json_risk_rule_document(self, asset: AgentAsset) -> tuple[str, str]:
|
||||
config_json = dict(asset.config_json or {})
|
||||
detail_mode = str(config_json.get("detail_mode") or "").strip().lower()
|
||||
if detail_mode != "json_risk":
|
||||
raise ValueError("当前资产不是 JSON 风险规则。")
|
||||
|
||||
rule_library = str(config_json.get("rule_library") or RISK_RULES_LIBRARY).strip()
|
||||
if rule_library not in RULE_LIBRARY_NAMES:
|
||||
raise ValueError("规则库目录不合法。")
|
||||
|
||||
rule_document = config_json.get("rule_document")
|
||||
if not isinstance(rule_document, dict):
|
||||
raise ValueError("规则资产缺少 rule_document 配置。")
|
||||
|
||||
file_name = str(rule_document.get("file_name") or "").strip()
|
||||
if not file_name:
|
||||
raise ValueError("规则资产缺少 JSON 文件名。")
|
||||
return rule_library, file_name
|
||||
|
||||
def read_rule_json(self, asset_id: str) -> AgentAssetRuleJsonRead:
|
||||
asset = self.repository.get(asset_id)
|
||||
if asset is None:
|
||||
raise LookupError("资产不存在。")
|
||||
|
||||
rule_library, file_name = self._resolve_json_risk_rule_document(asset)
|
||||
payload = self.rule_library_manager.read_rule_library_json(
|
||||
library=rule_library,
|
||||
file_name=file_name,
|
||||
)
|
||||
return AgentAssetRuleJsonRead(
|
||||
file_name=file_name,
|
||||
rule_code=str(payload.get("rule_code") or asset.code or ""),
|
||||
name=str(payload.get("name") or asset.name or ""),
|
||||
description=str(payload.get("description") or asset.description or "").strip(),
|
||||
evaluator=str(payload.get("evaluator") or ""),
|
||||
ontology_signal=str(payload.get("ontology_signal") or "") or None,
|
||||
inputs=payload.get("inputs") if isinstance(payload.get("inputs"), dict) else {},
|
||||
outcomes=payload.get("outcomes") if isinstance(payload.get("outcomes"), dict) else {},
|
||||
payload=payload,
|
||||
)
|
||||
|
||||
def write_rule_json(
|
||||
self,
|
||||
asset_id: str,
|
||||
*,
|
||||
body: AgentAssetRuleJsonWrite,
|
||||
actor: str,
|
||||
request_id: str | None = None,
|
||||
) -> AgentAssetRuleJsonRead:
|
||||
asset = self.repository.get(asset_id)
|
||||
if asset is None:
|
||||
raise LookupError("资产不存在。")
|
||||
|
||||
rule_library, file_name = self._resolve_json_risk_rule_document(asset)
|
||||
payload = dict(body.payload or {})
|
||||
asset_code = str(asset.code or "").strip()
|
||||
if asset_code and str(payload.get("rule_code") or "").strip() not in {"", asset_code}:
|
||||
raise ValueError("规则 JSON 的 rule_code 必须与资产编码一致。")
|
||||
if asset_code and not str(payload.get("rule_code") or "").strip():
|
||||
payload["rule_code"] = asset_code
|
||||
|
||||
saved = self.rule_library_manager.write_rule_library_json(
|
||||
library=rule_library,
|
||||
file_name=file_name,
|
||||
payload=payload,
|
||||
)
|
||||
rule_description = str(saved.get("description") or "").strip()
|
||||
if rule_description:
|
||||
asset.description = rule_description
|
||||
rule_name = str(saved.get("name") or "").strip()
|
||||
if rule_name:
|
||||
asset.name = rule_name
|
||||
risk_category = str(saved.get("risk_category") or "").strip()
|
||||
if risk_category:
|
||||
config_json = dict(asset.config_json or {})
|
||||
config_json["risk_category"] = risk_category
|
||||
asset.config_json = config_json
|
||||
asset.scenario_json = [risk_category]
|
||||
self.audit_service.log_action(
|
||||
actor=actor,
|
||||
action="update_agent_asset_rule_json",
|
||||
resource_type=asset.asset_type,
|
||||
resource_id=asset.id,
|
||||
before_json={"file_name": file_name},
|
||||
after_json={"file_name": file_name, "rule_code": saved.get("rule_code")},
|
||||
request_id=request_id,
|
||||
)
|
||||
self.db.commit()
|
||||
return self.read_rule_json(asset_id)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user