后端新增风险图谱算法模块、风险观察与反馈服务、规则 DSL 校验器和可解释性引擎,完善系统仪表盘和财务仪表盘统计, 优化 agent 运行和编排执行链路,清理旧开发文档,前端新增 系统趋势、负载热力图等多种仪表盘图表组件,完善操作反馈 对话框和工作台日期选择器,优化报销创建和审批详情交互, 补充单元测试覆盖。
199 lines
7.9 KiB
Python
199 lines
7.9 KiB
Python
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)
|