feat: 新增风险图谱算法与系统仪表盘及操作反馈体系
后端新增风险图谱算法模块、风险观察与反馈服务、规则 DSL 校验器和可解释性引擎,完善系统仪表盘和财务仪表盘统计, 优化 agent 运行和编排执行链路,清理旧开发文档,前端新增 系统趋势、负载热力图等多种仪表盘图表组件,完善操作反馈 对话框和工作台日期选择器,优化报销创建和审批详情交互, 补充单元测试覆盖。
This commit is contained in:
@@ -0,0 +1,198 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from sqlalchemy import select
|
||||
|
||||
from app.core.agent_enums import (
|
||||
AgentAssetContentType,
|
||||
AgentAssetDomain,
|
||||
AgentAssetStatus,
|
||||
AgentAssetType,
|
||||
AgentName,
|
||||
)
|
||||
from app.models.agent_asset import AgentAsset
|
||||
from app.services.agent_foundation_constants import (
|
||||
DIGITAL_EMPLOYEE_PROFILE_SCAN_TASK_CODE,
|
||||
DIGITAL_EMPLOYEE_RISK_GRAPH_SCAN_TASK_CODE,
|
||||
DIGITAL_EMPLOYEE_RULE_DISCOVERY_TASK_CODE,
|
||||
DIGITAL_EMPLOYEE_SKILL_CATEGORIES,
|
||||
)
|
||||
|
||||
|
||||
class AgentFoundationDigitalEmployeeTaskMixin:
|
||||
def _runtime_digital_employee_task_specs(self) -> tuple[dict[str, object], ...]:
|
||||
return (
|
||||
{
|
||||
"code": DIGITAL_EMPLOYEE_RISK_GRAPH_SCAN_TASK_CODE,
|
||||
"name": "财务风险图谱巡检",
|
||||
"description": "按计划扫描报销单、票据、审批链、员工画像和规则命中结果,生成风险观察与可复核证据链。",
|
||||
"scenario_json": ["schedule", "expense", "risk_graph", "risk_observation"],
|
||||
"owner": "风控与审计部",
|
||||
"reviewer": "顾承宇",
|
||||
"cron": "0 9 * * *",
|
||||
"skill_category": "评估",
|
||||
"markdown": self._financial_risk_graph_scan_skill_markdown,
|
||||
"change_note": "初始化财务风险图谱巡检能力。",
|
||||
"config": {
|
||||
"skill_name": "financial-risk-graph-scanner",
|
||||
"scan_scope": [
|
||||
"expense_claims",
|
||||
"invoices",
|
||||
"approval_chain",
|
||||
"employee_profiles",
|
||||
"risk_rules",
|
||||
],
|
||||
"output_format": "risk_observation_report",
|
||||
"writes_risk_observations": True,
|
||||
},
|
||||
},
|
||||
{
|
||||
"code": DIGITAL_EMPLOYEE_PROFILE_SCAN_TASK_CODE,
|
||||
"name": "员工行为画像巡检",
|
||||
"description": "按计划更新员工费用行为、材料完整性、审批效率和智能协作画像,为风险图谱提供画像基线。",
|
||||
"scenario_json": ["schedule", "employee_profile", "baseline", "risk_graph"],
|
||||
"owner": "风控与审计部",
|
||||
"reviewer": "顾承宇",
|
||||
"cron": "30 8 * * 1",
|
||||
"skill_category": "评估",
|
||||
"markdown": self._employee_behavior_profile_scan_skill_markdown,
|
||||
"change_note": "初始化员工行为画像巡检能力。",
|
||||
"config": {
|
||||
"skill_name": "employee-behavior-profile-scanner",
|
||||
"profile_dimensions": [
|
||||
"expense_intensity",
|
||||
"material_completeness",
|
||||
"approval_efficiency",
|
||||
"ai_collaboration",
|
||||
],
|
||||
"output_format": "employee_behavior_profile_snapshot",
|
||||
"writes_profile_snapshots": True,
|
||||
},
|
||||
},
|
||||
{
|
||||
"code": DIGITAL_EMPLOYEE_RULE_DISCOVERY_TASK_CODE,
|
||||
"name": "风险规则候选发现",
|
||||
"description": "按计划复盘风险观察和人工反馈,生成带证据、来源和置信度的候选规则,不直接上线。",
|
||||
"scenario_json": ["schedule", "risk_observation", "feedback", "rule_candidate"],
|
||||
"owner": "风控与审计部",
|
||||
"reviewer": "顾承宇",
|
||||
"cron": "0 10 * * 1",
|
||||
"skill_category": "升级",
|
||||
"markdown": self._risk_rule_discovery_skill_markdown,
|
||||
"change_note": "初始化风险规则候选发现能力。",
|
||||
"config": {
|
||||
"skill_name": "risk-rule-discovery",
|
||||
"input_sources": [
|
||||
"risk_observations",
|
||||
"risk_observation_feedback",
|
||||
"algorithm_replay_sets",
|
||||
],
|
||||
"output_format": "candidate_risk_rules",
|
||||
"auto_publish": False,
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
def _upsert_runtime_digital_employee_tasks(self, existing_codes: set[str]) -> None:
|
||||
for spec in self._runtime_digital_employee_task_specs():
|
||||
self._upsert_runtime_digital_employee_task(existing_codes, spec)
|
||||
|
||||
def _upsert_runtime_digital_employee_task(
|
||||
self,
|
||||
existing_codes: set[str],
|
||||
spec: dict[str, object],
|
||||
) -> None:
|
||||
code = str(spec["code"])
|
||||
config = self._build_runtime_digital_employee_config(spec)
|
||||
|
||||
if code not in existing_codes:
|
||||
asset = self._create_seed_asset(
|
||||
asset_type=AgentAssetType.TASK.value,
|
||||
code=code,
|
||||
name=str(spec["name"]),
|
||||
description=str(spec["description"]),
|
||||
domain=AgentAssetDomain.SYSTEM.value,
|
||||
scenario_json=list(spec["scenario_json"]),
|
||||
owner=str(spec["owner"]),
|
||||
reviewer=str(spec["reviewer"]),
|
||||
status=AgentAssetStatus.ACTIVE.value,
|
||||
current_version="v1.0.0",
|
||||
config_json=config,
|
||||
)
|
||||
else:
|
||||
asset = self.db.scalar(select(AgentAsset).where(AgentAsset.code == code))
|
||||
if asset is None:
|
||||
return
|
||||
self._refresh_runtime_digital_employee_asset(asset, spec)
|
||||
|
||||
markdown_builder = spec["markdown"]
|
||||
if not callable(markdown_builder):
|
||||
return
|
||||
self._ensure_asset_version(
|
||||
asset,
|
||||
version="v1.0.0",
|
||||
content=markdown_builder(),
|
||||
content_type=AgentAssetContentType.MARKDOWN.value,
|
||||
change_note=str(spec["change_note"]),
|
||||
created_by="系统初始化",
|
||||
)
|
||||
|
||||
def _build_runtime_digital_employee_config(
|
||||
self,
|
||||
spec: dict[str, object],
|
||||
*,
|
||||
existing_config: dict[str, object] | None = None,
|
||||
) -> dict[str, object]:
|
||||
code = str(spec["code"])
|
||||
cron = str(spec["cron"])
|
||||
base = {
|
||||
**self._digital_employee_task_config(code, cron),
|
||||
"schedule": cron,
|
||||
"cron_expression": cron,
|
||||
**dict(spec["config"]),
|
||||
}
|
||||
if not existing_config:
|
||||
return base
|
||||
|
||||
existing_cron = (
|
||||
existing_config.get("cron")
|
||||
or existing_config.get("schedule")
|
||||
or existing_config.get("cron_expression")
|
||||
)
|
||||
schedule_config = (
|
||||
{"cron": existing_cron, "schedule": existing_cron, "cron_expression": existing_cron}
|
||||
if existing_cron
|
||||
else {}
|
||||
)
|
||||
return {
|
||||
**existing_config,
|
||||
"agent": AgentName.HERMES.value,
|
||||
"task_type": code.replace("task.hermes.", "").replace(".", "_"),
|
||||
"skill_category": str(spec["skill_category"]),
|
||||
"skill_category_options": list(DIGITAL_EMPLOYEE_SKILL_CATEGORIES),
|
||||
**dict(spec["config"]),
|
||||
**schedule_config,
|
||||
}
|
||||
|
||||
def _refresh_runtime_digital_employee_asset(
|
||||
self,
|
||||
asset: AgentAsset,
|
||||
spec: dict[str, object],
|
||||
) -> None:
|
||||
asset.name = str(spec["name"])
|
||||
asset.description = str(spec["description"])
|
||||
asset.owner = str(spec["owner"])
|
||||
asset.reviewer = str(spec["reviewer"])
|
||||
asset.domain = AgentAssetDomain.SYSTEM.value
|
||||
asset.scenario_json = list(spec["scenario_json"])
|
||||
if not str(asset.status or "").strip():
|
||||
asset.status = AgentAssetStatus.ACTIVE.value
|
||||
if not str(asset.current_version or "").strip():
|
||||
asset.current_version = "v1.0.0"
|
||||
if not str(asset.working_version or "").strip():
|
||||
asset.working_version = asset.current_version
|
||||
|
||||
asset.config_json = self._build_runtime_digital_employee_config(
|
||||
spec,
|
||||
existing_config=dict(asset.config_json or {}),
|
||||
)
|
||||
self.db.add(asset)
|
||||
Reference in New Issue
Block a user