Files
X-Financial/server/tests/test_digital_employee_skill_catalog.py
caoxiaozhu 92444e7eae feat: 扩展风险规则体系、审批动态路由与预算中心列表化改造
- 新增 25+ 条风险规则(预算/报销/申请/通用类),完善风险规则模拟与反馈发布机制
- 引入费用审批动态路由、平台风险分级、预审与风险阶段管理
- 预算中心列表化改造,优化票据夹仪表盘与数字员工工作看板
- 新增 Hermes 风险线索收集器、Agent 链路追踪中心
- 扩展数字员工能力库(18 个领域 Skill)与交通费用自动预估
- 完善报销申请快速预览、权限控制与前端测试覆盖
2026-06-01 17:07:14 +08:00

131 lines
5.5 KiB
Python

from __future__ import annotations
from pathlib import Path
from typing import Any
from app.core.agent_enums import AgentName
from app.services.agent_foundation_constants import (
DIGITAL_EMPLOYEE_FINANCE_POLICY_TASK_CODE,
DIGITAL_EMPLOYEE_PROFILE_SCAN_TASK_CODE,
DIGITAL_EMPLOYEE_SKILL_CATEGORIES,
DIGITAL_EMPLOYEE_TASK_CATEGORY_MAP,
)
from app.services.agent_foundation_digital_employee_tasks import (
AgentFoundationDigitalEmployeeTaskMixin,
DIGITAL_EMPLOYEE_ANALYSIS_ROLE_BOUNDARY,
)
class _CatalogHarness(AgentFoundationDigitalEmployeeTaskMixin):
def _digital_employee_task_config(self, code: str, cron: str) -> dict[str, Any]:
return {
"cron": cron,
"agent": AgentName.HERMES.value,
"task_type": code.replace("task.hermes.", "").replace(".", "_"),
"skill_category": DIGITAL_EMPLOYEE_TASK_CATEGORY_MAP.get(code, "整理"),
"skill_category_options": list(DIGITAL_EMPLOYEE_SKILL_CATEGORIES),
}
def _read_domain_skill_markdown(
self,
skill_name: str,
fallback_lines: list[str],
) -> str:
skill_path = _skill_root() / skill_name / "SKILL.md"
if skill_path.exists():
return skill_path.read_text(encoding="utf-8")
return "\n".join(fallback_lines)
def _financial_risk_graph_scan_skill_markdown(self) -> str:
return self._read_domain_skill_markdown("financial-risk-graph-scanner", [])
def _employee_behavior_profile_scan_skill_markdown(self) -> str:
return self._read_domain_skill_markdown("employee-behavior-profile-scanner", [])
def _risk_rule_discovery_skill_markdown(self) -> str:
return self._read_domain_skill_markdown("risk-rule-discovery", [])
def _risk_clue_collector_skill_markdown(self) -> str:
return self._read_domain_skill_markdown("risk-clue-collector", [])
def test_digital_employee_skill_catalog_has_complete_categories_and_packages() -> None:
harness = _CatalogHarness()
specs = harness._runtime_digital_employee_task_specs()
codes = [str(spec["code"]) for spec in specs]
categories = [str(spec["skill_category"]) for spec in specs]
skill_names = [str(dict(spec["config"])["skill_name"]) for spec in specs]
assert len(specs) == 16
assert len(set(codes)) == len(codes)
assert set(categories) == set(DIGITAL_EMPLOYEE_SKILL_CATEGORIES)
assert DIGITAL_EMPLOYEE_TASK_CATEGORY_MAP[DIGITAL_EMPLOYEE_PROFILE_SCAN_TASK_CODE] == "积累"
assert len(set(codes + [DIGITAL_EMPLOYEE_FINANCE_POLICY_TASK_CODE])) == 17
for skill_name in ["finance-policy-knowledge-organizer", *skill_names]:
skill_file = _skill_root() / skill_name / "SKILL.md"
assert skill_file.exists(), skill_name
content = skill_file.read_text(encoding="utf-8")
assert f"name: {skill_name}" in content
def test_digital_employee_runtime_specs_build_display_ready_config() -> None:
harness = _CatalogHarness()
forbidden_rule_execution = "执行" + "规则"
for spec in harness._runtime_digital_employee_task_specs():
config = harness._build_runtime_digital_employee_config(spec)
spec_config = dict(spec["config"])
markdown = spec["markdown"]()
assert config["agent"] == AgentName.HERMES.value
assert config["skill_category"] == spec["skill_category"]
assert config["skill_category_options"] == list(DIGITAL_EMPLOYEE_SKILL_CATEGORIES)
assert config["skill_name"] == spec_config["skill_name"]
assert config["output_format"] == spec_config["output_format"]
assert config["schedule"] == spec["cron"]
assert config["cron_expression"] == spec["cron"]
assert config["writes_rules"] is False
assert isinstance(config["role_boundary"], str)
assert config["role_boundary"] == DIGITAL_EMPLOYEE_ANALYSIS_ROLE_BOUNDARY
assert "主流程由外层智能体执行" in config["role_boundary"]
assert forbidden_rule_execution not in config["role_boundary"]
assert "human_review_required" in config["allowed_outputs"]
assert f"name: {config['skill_name']}" in markdown
assert str(spec["name"]) in markdown
def test_digital_employee_skills_do_not_cross_rule_governance_boundary() -> None:
harness = _CatalogHarness()
forbidden_rule_execution = "执行" + "规则"
specs = harness._runtime_digital_employee_task_specs()
skill_names = {str(dict(spec["config"])["skill_name"]) for spec in specs}
output_formats = {str(dict(spec["config"])["output_format"]) for spec in specs}
text_contract = "\n".join(
str(value)
for spec in specs
for value in (
spec["name"],
spec["description"],
dict(spec["config"])["skill_name"],
dict(spec["config"])["output_format"],
dict(spec["config"])["role_boundary"],
)
)
assert "risk-clue-collector" in skill_names
assert "rule-execution-case-organizer" in skill_names
assert "policy-reference-gap-hinter" in skill_names
assert "risk-rule-discovery" not in skill_names
assert "risk-rule-template-organizer" not in skill_names
assert "policy-gap-rule-optimizer" not in skill_names
assert "candidate_risk_rules" not in output_formats
assert "risk_rule_template_library" not in output_formats
assert "policy_gap_rule_optimization_report" not in output_formats
assert "auto_publish" not in text_contract
assert forbidden_rule_execution not in text_contract
def _skill_root() -> Path:
return Path(__file__).resolve().parents[1] / "src" / "app" / "skills" / "domain"