feat(server): 扩展智能体基础服务,新增端点测试和资产服务测试用例
This commit is contained in:
@@ -32,6 +32,10 @@ from app.models.financial_record import (
|
||||
ExpenseClaim,
|
||||
ExpenseClaimItem,
|
||||
)
|
||||
from app.services.expense_rule_runtime import (
|
||||
build_scene_submission_standard_markdown,
|
||||
build_travel_risk_control_standard_markdown,
|
||||
)
|
||||
|
||||
logger = get_logger("app.services.agent_foundation")
|
||||
|
||||
@@ -67,6 +71,75 @@ DEMO_PAYABLE_SIGNATURES = {
|
||||
("AP-202605-002", "供应商B", "96000.00", "overdue"),
|
||||
}
|
||||
|
||||
LEGACY_RULE_CODES = (
|
||||
"rule.expense.duplicate_expense_check",
|
||||
"rule.expense.travel_receipt_requirements",
|
||||
"rule.ap.payment_dual_review",
|
||||
)
|
||||
|
||||
ATTACHMENT_RULE_ASSET_CODE = "rule.expense.attachment_submission_requirements"
|
||||
|
||||
ATTACHMENT_RULE_RUNTIME_CONFIG = {
|
||||
"kind": "policy_rule_draft",
|
||||
"version": 1,
|
||||
"template_key": "attachment_requirement_v1",
|
||||
"rule_name": "报销附件与单据完整性规则",
|
||||
"scenario": "attachment_policy",
|
||||
"source_document_name": "报销制度 / 单据与附件要求",
|
||||
"review_required": True,
|
||||
"target": {
|
||||
"expense_types": [
|
||||
"travel",
|
||||
"hotel",
|
||||
"transport",
|
||||
"meal",
|
||||
"office",
|
||||
"meeting",
|
||||
"training",
|
||||
"communication",
|
||||
"welfare",
|
||||
"other",
|
||||
],
|
||||
"scene_codes": ["expense", "attachment_policy", "invoice_anomaly"],
|
||||
},
|
||||
"attachment_requirements": {
|
||||
"min_attachment_count": 1,
|
||||
"items": [
|
||||
{
|
||||
"document_type": "vat_invoice",
|
||||
"required": True,
|
||||
"min_count": 1,
|
||||
"description": "金额类报销原则上必须提供合法票据。",
|
||||
},
|
||||
{
|
||||
"document_type": "receipt",
|
||||
"required": False,
|
||||
"min_count": 1,
|
||||
"description": "特殊场景无发票时需补充收据与情况说明。",
|
||||
},
|
||||
{
|
||||
"document_type": "flight_itinerary",
|
||||
"required": False,
|
||||
"min_count": 1,
|
||||
"description": "差旅交通报销需提供行程单或等效凭证。",
|
||||
},
|
||||
{
|
||||
"document_type": "hotel_invoice",
|
||||
"required": False,
|
||||
"min_count": 1,
|
||||
"description": "住宿报销需提供酒店票据或等效住宿凭证。",
|
||||
},
|
||||
],
|
||||
"manual_fill_required": False,
|
||||
},
|
||||
"missing_attachment_action": "block",
|
||||
"output": {
|
||||
"risk_code": "invoice_anomaly",
|
||||
"action": "block",
|
||||
"message": "附件或单据不完整,需补件后再提交。",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def prepare_agent_foundation() -> None:
|
||||
settings = get_settings()
|
||||
@@ -107,44 +180,65 @@ class AgentFoundationService:
|
||||
self._top_up_agent_assets(existing_codes)
|
||||
return
|
||||
|
||||
approved_rule = AgentAsset(
|
||||
attachment_rule = AgentAsset(
|
||||
asset_type=AgentAssetType.RULE.value,
|
||||
code="rule.expense.duplicate_expense_check",
|
||||
name="重复报销识别规则",
|
||||
description="识别同一员工短时间内同金额、同地点、同理由的重复报销风险。",
|
||||
code=ATTACHMENT_RULE_ASSET_CODE,
|
||||
name="报销附件与单据完整性规则",
|
||||
description="统一定义报销提交时的附件数量、票据类型和补件处理口径,作为上线前待审核规则。",
|
||||
domain=AgentAssetDomain.EXPENSE.value,
|
||||
scenario_json=["expense", "risk_check", "duplicate_expense"],
|
||||
owner="财务共享中心",
|
||||
reviewer="张晓晴",
|
||||
status=AgentAssetStatus.ACTIVE.value,
|
||||
current_version="v1.1.0",
|
||||
config_json={"severity": "high", "enabled": True},
|
||||
)
|
||||
pending_rule = AgentAsset(
|
||||
asset_type=AgentAssetType.RULE.value,
|
||||
code="rule.expense.travel_receipt_requirements",
|
||||
name="差旅票据完整性规则",
|
||||
description="检查差旅报销是否附齐发票、行程单和住宿凭证。",
|
||||
domain=AgentAssetDomain.EXPENSE.value,
|
||||
scenario_json=["expense", "explain", "invoice_anomaly"],
|
||||
owner="费用运营组",
|
||||
scenario_json=["expense", "risk_check", "attachment_policy", "invoice_anomaly"],
|
||||
owner="财务制度管理组",
|
||||
reviewer="高嘉禾",
|
||||
status=AgentAssetStatus.REVIEW.value,
|
||||
current_version="v1.0.0",
|
||||
config_json={"severity": "medium", "enabled": False},
|
||||
config_json={
|
||||
"severity": "high",
|
||||
"enabled": False,
|
||||
"runtime_kind": "policy_rule_draft",
|
||||
"rule_template_key": "attachment_requirement_v1",
|
||||
"rule_template_label": "附件要求模板",
|
||||
"runtime_rule": ATTACHMENT_RULE_RUNTIME_CONFIG,
|
||||
},
|
||||
)
|
||||
rejected_rule = AgentAsset(
|
||||
scene_submission_rule = AgentAsset(
|
||||
asset_type=AgentAssetType.RULE.value,
|
||||
code="rule.ap.payment_dual_review",
|
||||
name="付款双人复核规则",
|
||||
description="大额付款必须由两名财务人员复核后再进入付款建议。",
|
||||
domain=AgentAssetDomain.AP.value,
|
||||
scenario_json=["accounts_payable", "approval_required"],
|
||||
owner="付款管理组",
|
||||
reviewer="孙楠",
|
||||
status=AgentAssetStatus.DRAFT.value,
|
||||
current_version="v0.9.0",
|
||||
config_json={"amount_threshold": 50000},
|
||||
code="rule.expense.scene_submission_standard",
|
||||
name="报销场景提交与附件标准",
|
||||
description="统一定义各报销场景的必填字段、附件类型要求和金额阈值。",
|
||||
domain=AgentAssetDomain.EXPENSE.value,
|
||||
scenario_json=["expense", "risk_check", "scene_policy", "attachment_policy"],
|
||||
owner="费用运营组",
|
||||
reviewer="顾承宇",
|
||||
status=AgentAssetStatus.ACTIVE.value,
|
||||
current_version="v1.0.0",
|
||||
config_json={
|
||||
"severity": "high",
|
||||
"enabled": True,
|
||||
"runtime_kind": "scene_matrix",
|
||||
"rule_template_label": "系统内置场景矩阵规则",
|
||||
},
|
||||
)
|
||||
travel_policy_rule = AgentAsset(
|
||||
asset_type=AgentAssetType.RULE.value,
|
||||
code="rule.expense.travel_risk_control_standard",
|
||||
name="差旅报销风险管控制度",
|
||||
description="统一定义差旅报销的行程闭环、酒店地点一致性、职级差标和风险处置口径。",
|
||||
domain=AgentAssetDomain.EXPENSE.value,
|
||||
scenario_json=["expense", "risk_check", "travel_policy", "travel_standard"],
|
||||
owner="风控与审计部",
|
||||
reviewer="顾承宇",
|
||||
status=AgentAssetStatus.ACTIVE.value,
|
||||
current_version="v1.1.0",
|
||||
config_json={
|
||||
"severity": "high",
|
||||
"enabled": True,
|
||||
"block_on_high_risk": True,
|
||||
"warning_on_medium_risk": True,
|
||||
"source_doc": "document/development/risks/travel-risk-control-standard.md",
|
||||
"runtime_kind": "travel_policy",
|
||||
"rule_template_key": "travel_standard_v1",
|
||||
"rule_template_label": "差旅标准模板",
|
||||
},
|
||||
)
|
||||
skill_expense_asset = AgentAsset(
|
||||
asset_type=AgentAssetType.SKILL.value,
|
||||
@@ -237,12 +331,25 @@ class AgentFoundationService:
|
||||
current_version="v1.0.0",
|
||||
config_json={"cron": "0 18 * * *", "agent": AgentName.HERMES.value},
|
||||
)
|
||||
llm_wiki_task = AgentAsset(
|
||||
asset_type=AgentAssetType.TASK.value,
|
||||
code="task.hermes.llm_wiki_rule_formation",
|
||||
name="Hermes 制度知识与规则草稿形成",
|
||||
description="按知识库变化增量重建报销制度 LLM Wiki,并形成知识候选与规则草稿。",
|
||||
domain=AgentAssetDomain.SYSTEM.value,
|
||||
scenario_json=["schedule", "knowledge", "rule_center"],
|
||||
owner="财务制度管理组",
|
||||
reviewer="顾承宇",
|
||||
status=AgentAssetStatus.ACTIVE.value,
|
||||
current_version="v1.0.0",
|
||||
config_json={"cron": "0 3 * * *", "agent": AgentName.HERMES.value},
|
||||
)
|
||||
|
||||
self.db.add_all(
|
||||
[
|
||||
approved_rule,
|
||||
pending_rule,
|
||||
rejected_rule,
|
||||
attachment_rule,
|
||||
scene_submission_rule,
|
||||
travel_policy_rule,
|
||||
skill_expense_asset,
|
||||
skill_ar_asset,
|
||||
invoice_mcp_asset,
|
||||
@@ -250,6 +357,7 @@ class AgentFoundationService:
|
||||
task_asset,
|
||||
ar_summary_task,
|
||||
rule_digest_task,
|
||||
llm_wiki_task,
|
||||
]
|
||||
)
|
||||
self.db.flush()
|
||||
@@ -257,79 +365,50 @@ class AgentFoundationService:
|
||||
self.db.add_all(
|
||||
[
|
||||
AgentAssetVersion(
|
||||
asset=approved_rule,
|
||||
version="v1.0.0",
|
||||
content=self._markdown_content(
|
||||
"# 重复报销识别规则\n\n"
|
||||
"- 检查员工、金额、地点、发生日期是否高度重复。\n"
|
||||
"- 命中后输出 `duplicate_expense` 风险标签。"
|
||||
),
|
||||
content_type=AgentAssetContentType.MARKDOWN.value,
|
||||
change_note="初始化生产规则版本。",
|
||||
created_by="系统初始化",
|
||||
),
|
||||
AgentAssetVersion(
|
||||
asset=approved_rule,
|
||||
version="v1.1.0",
|
||||
content=self._markdown_content(
|
||||
"# 重复报销识别规则\n\n"
|
||||
"- 检查员工、金额、地点、发生日期是否高度重复。\n"
|
||||
"- 新增对同项目、同金额、跨单重复提交的识别。\n"
|
||||
"- 命中后输出 `duplicate_expense` 风险标签。"
|
||||
),
|
||||
content_type=AgentAssetContentType.MARKDOWN.value,
|
||||
change_note="补充跨单重复提交判断。",
|
||||
created_by="系统初始化",
|
||||
),
|
||||
AgentAssetVersion(
|
||||
asset=pending_rule,
|
||||
asset=attachment_rule,
|
||||
version="v0.9.0",
|
||||
content=self._markdown_content(
|
||||
"# 差旅票据完整性规则\n\n"
|
||||
"- 差旅报销必须具备发票、行程单、住宿凭证。\n"
|
||||
"- 缺失时输出 `invoice_anomaly`。"
|
||||
content=self._attachment_submission_requirement_markdown(
|
||||
version_note="首版附件完整性规则草稿,覆盖基础票据与补件口径。",
|
||||
include_review_note=True,
|
||||
),
|
||||
content_type=AgentAssetContentType.MARKDOWN.value,
|
||||
change_note="首版草稿。",
|
||||
created_by="高嘉禾",
|
||||
),
|
||||
AgentAssetVersion(
|
||||
asset=pending_rule,
|
||||
asset=attachment_rule,
|
||||
version="v1.0.0",
|
||||
content=self._markdown_content(
|
||||
"# 差旅票据完整性规则\n\n"
|
||||
"- 差旅报销必须具备发票、行程单、住宿凭证。\n"
|
||||
"- 新增高铁改签和住宿分拆票据的补件说明。\n"
|
||||
"- 缺失时输出 `invoice_anomaly`。"
|
||||
content=self._attachment_submission_requirement_markdown(
|
||||
version_note="补充票据缺失、收据替代和差旅等效凭证口径,待审核。",
|
||||
include_review_note=True,
|
||||
),
|
||||
content_type=AgentAssetContentType.MARKDOWN.value,
|
||||
change_note="补充差旅特殊票据口径,待审核。",
|
||||
change_note="补充票据替代与差旅等效凭证口径,待审核。",
|
||||
created_by="高嘉禾",
|
||||
),
|
||||
AgentAssetVersion(
|
||||
asset=rejected_rule,
|
||||
version="v0.8.0",
|
||||
content=self._markdown_content(
|
||||
"# 付款双人复核规则\n\n"
|
||||
"- 单笔付款超过阈值时必须双人复核。\n"
|
||||
"- 本版本规则口径过宽,待修订。"
|
||||
),
|
||||
asset=scene_submission_rule,
|
||||
version="v1.0.0",
|
||||
content=self._scene_submission_standard_markdown(),
|
||||
content_type=AgentAssetContentType.MARKDOWN.value,
|
||||
change_note="首版方案。",
|
||||
created_by="孙楠",
|
||||
change_note="首版报销场景提交标准,覆盖附件类型、必填字段和金额阈值。",
|
||||
created_by="系统初始化",
|
||||
),
|
||||
AgentAssetVersion(
|
||||
asset=rejected_rule,
|
||||
version="v0.9.0",
|
||||
content=self._markdown_content(
|
||||
"# 付款双人复核规则\n\n"
|
||||
"- 单笔付款超过阈值时必须双人复核。\n"
|
||||
"- 新增跨币种付款也进入复核队列。\n"
|
||||
"- 当前阈值定义仍不清晰,需继续修订。"
|
||||
),
|
||||
asset=travel_policy_rule,
|
||||
version="v1.0.0",
|
||||
content=self._travel_risk_control_standard_markdown(version="v1.0.0"),
|
||||
content_type=AgentAssetContentType.MARKDOWN.value,
|
||||
change_note="补充跨币种场景,但阈值仍待明确。",
|
||||
created_by="孙楠",
|
||||
change_note="首版差旅制度执行规则,覆盖行程闭环与基础差标校验。",
|
||||
created_by="系统初始化",
|
||||
),
|
||||
AgentAssetVersion(
|
||||
asset=travel_policy_rule,
|
||||
version="v1.1.0",
|
||||
content=self._travel_risk_control_standard_markdown(version="v1.1.0"),
|
||||
content_type=AgentAssetContentType.MARKDOWN.value,
|
||||
change_note="补充可执行规则块,供审核引擎直接消费差旅制度标准。",
|
||||
created_by="系统初始化",
|
||||
),
|
||||
AgentAssetVersion(
|
||||
asset=skill_expense_asset,
|
||||
@@ -429,32 +508,48 @@ class AgentFoundationService:
|
||||
change_note="初始化规则待审摘要任务。",
|
||||
created_by="系统初始化",
|
||||
),
|
||||
AgentAssetVersion(
|
||||
asset=llm_wiki_task,
|
||||
version="v1.0.0",
|
||||
content=self._json_content(
|
||||
{
|
||||
"task_type": "llm_wiki_rule_formation",
|
||||
"schedule": "0 3 * * *",
|
||||
"target_agent": AgentName.HERMES.value,
|
||||
"folder": "报销制度",
|
||||
"changed_only": True,
|
||||
}
|
||||
),
|
||||
content_type=AgentAssetContentType.JSON.value,
|
||||
change_note="初始化制度知识与规则草稿形成任务。",
|
||||
created_by="系统初始化",
|
||||
),
|
||||
]
|
||||
)
|
||||
self.db.add_all(
|
||||
[
|
||||
AgentAssetReview(
|
||||
asset=approved_rule,
|
||||
version="v1.1.0",
|
||||
reviewer="张晓晴",
|
||||
review_status=AgentReviewStatus.APPROVED.value,
|
||||
review_note="规则口径清晰,可上线。",
|
||||
reviewed_at=datetime.now(UTC),
|
||||
),
|
||||
AgentAssetReview(
|
||||
asset=pending_rule,
|
||||
asset=attachment_rule,
|
||||
version="v1.0.0",
|
||||
reviewer="高嘉禾",
|
||||
review_status=AgentReviewStatus.PENDING.value,
|
||||
review_note="等待补充票据异常样例。",
|
||||
review_note="等待制度管理员确认收据替代与补件时限口径。",
|
||||
reviewed_at=None,
|
||||
),
|
||||
AgentAssetReview(
|
||||
asset=rejected_rule,
|
||||
version="v0.9.0",
|
||||
reviewer="孙楠",
|
||||
review_status=AgentReviewStatus.REJECTED.value,
|
||||
review_note="阈值定义不清,暂不通过。",
|
||||
asset=scene_submission_rule,
|
||||
version="v1.0.0",
|
||||
reviewer="顾承宇",
|
||||
review_status=AgentReviewStatus.APPROVED.value,
|
||||
review_note="可作为报销场景统一审核标准正式执行。",
|
||||
reviewed_at=datetime.now(UTC),
|
||||
),
|
||||
AgentAssetReview(
|
||||
asset=travel_policy_rule,
|
||||
version="v1.1.0",
|
||||
reviewer="顾承宇",
|
||||
review_status=AgentReviewStatus.APPROVED.value,
|
||||
review_note="制度口径已确认,并已补充可执行配置供审核引擎读取。",
|
||||
reviewed_at=datetime.now(UTC),
|
||||
),
|
||||
]
|
||||
@@ -772,26 +867,26 @@ class AgentFoundationService:
|
||||
actor="系统初始化",
|
||||
action="save_rule_markdown",
|
||||
resource_type="rule",
|
||||
resource_id="rule.expense.duplicate_expense_check",
|
||||
resource_id=ATTACHMENT_RULE_ASSET_CODE,
|
||||
before_json=None,
|
||||
after_json={"version": "v1.0.0"},
|
||||
request_id="seed-audit-001",
|
||||
),
|
||||
AuditLog(
|
||||
actor="张晓晴",
|
||||
actor="高嘉禾",
|
||||
action="review_rule",
|
||||
resource_type="rule",
|
||||
resource_id="rule.expense.duplicate_expense_check",
|
||||
resource_id=ATTACHMENT_RULE_ASSET_CODE,
|
||||
before_json={"review_status": "pending"},
|
||||
after_json={"review_status": "approved"},
|
||||
after_json={"review_status": "pending"},
|
||||
request_id="seed-audit-002",
|
||||
),
|
||||
AuditLog(
|
||||
actor="系统初始化",
|
||||
action="activate_rule",
|
||||
resource_type="rule",
|
||||
resource_id="rule.expense.duplicate_expense_check",
|
||||
before_json={"status": "review"},
|
||||
resource_id="rule.expense.scene_submission_standard",
|
||||
before_json={"status": "draft"},
|
||||
after_json={"status": "active"},
|
||||
request_id="seed-audit-003",
|
||||
),
|
||||
@@ -808,59 +903,191 @@ class AgentFoundationService:
|
||||
)
|
||||
|
||||
def _top_up_agent_assets(self, existing_codes: set[str]) -> None:
|
||||
approved_rule = self.db.scalar(
|
||||
select(AgentAsset).where(AgentAsset.code == "rule.expense.duplicate_expense_check")
|
||||
self._remove_legacy_rule_assets()
|
||||
existing_codes = set(self.db.scalars(select(AgentAsset.code)).all())
|
||||
|
||||
attachment_rule = self.db.scalar(
|
||||
select(AgentAsset).where(AgentAsset.code == ATTACHMENT_RULE_ASSET_CODE)
|
||||
)
|
||||
pending_rule = self.db.scalar(
|
||||
select(AgentAsset).where(AgentAsset.code == "rule.expense.travel_receipt_requirements")
|
||||
scene_submission_rule = self.db.scalar(
|
||||
select(AgentAsset).where(AgentAsset.code == "rule.expense.scene_submission_standard")
|
||||
)
|
||||
rejected_rule = self.db.scalar(
|
||||
select(AgentAsset).where(AgentAsset.code == "rule.ap.payment_dual_review")
|
||||
travel_policy_rule = self.db.scalar(
|
||||
select(AgentAsset).where(AgentAsset.code == "rule.expense.travel_risk_control_standard")
|
||||
)
|
||||
|
||||
if approved_rule is not None:
|
||||
self._ensure_asset_version(
|
||||
approved_rule,
|
||||
version="v1.1.0",
|
||||
content=self._markdown_content(
|
||||
"# 重复报销识别规则\n\n"
|
||||
"- 检查员工、金额、地点、发生日期是否高度重复。\n"
|
||||
"- 新增对同项目、同金额、跨单重复提交的识别。\n"
|
||||
"- 命中后输出 `duplicate_expense` 风险标签。"
|
||||
),
|
||||
content_type=AgentAssetContentType.MARKDOWN.value,
|
||||
change_note="补充跨单重复提交判断。",
|
||||
created_by="系统初始化",
|
||||
if ATTACHMENT_RULE_ASSET_CODE not in existing_codes:
|
||||
attachment_rule = self._create_seed_asset(
|
||||
asset_type=AgentAssetType.RULE.value,
|
||||
code=ATTACHMENT_RULE_ASSET_CODE,
|
||||
name="报销附件与单据完整性规则",
|
||||
description="统一定义报销提交时的附件数量、票据类型和补件处理口径,作为上线前待审核规则。",
|
||||
domain=AgentAssetDomain.EXPENSE.value,
|
||||
scenario_json=["expense", "risk_check", "attachment_policy", "invoice_anomaly"],
|
||||
owner="财务制度管理组",
|
||||
reviewer="高嘉禾",
|
||||
status=AgentAssetStatus.REVIEW.value,
|
||||
current_version="v1.0.0",
|
||||
config_json={
|
||||
"severity": "high",
|
||||
"enabled": False,
|
||||
"runtime_kind": "policy_rule_draft",
|
||||
"rule_template_key": "attachment_requirement_v1",
|
||||
"rule_template_label": "附件要求模板",
|
||||
"runtime_rule": ATTACHMENT_RULE_RUNTIME_CONFIG,
|
||||
},
|
||||
)
|
||||
|
||||
if pending_rule is not None:
|
||||
if attachment_rule is not None:
|
||||
attachment_rule.current_version = "v1.0.0"
|
||||
attachment_rule.status = AgentAssetStatus.REVIEW.value
|
||||
attachment_rule.description = "统一定义报销提交时的附件数量、票据类型和补件处理口径,作为上线前待审核规则。"
|
||||
attachment_rule.config_json = {
|
||||
"severity": "high",
|
||||
"enabled": False,
|
||||
"runtime_kind": "policy_rule_draft",
|
||||
"rule_template_key": "attachment_requirement_v1",
|
||||
"rule_template_label": "附件要求模板",
|
||||
"runtime_rule": ATTACHMENT_RULE_RUNTIME_CONFIG,
|
||||
}
|
||||
self._ensure_asset_version(
|
||||
pending_rule,
|
||||
version="v1.0.0",
|
||||
content=self._markdown_content(
|
||||
"# 差旅票据完整性规则\n\n"
|
||||
"- 差旅报销必须具备发票、行程单、住宿凭证。\n"
|
||||
"- 新增高铁改签和住宿分拆票据的补件说明。\n"
|
||||
"- 缺失时输出 `invoice_anomaly`。"
|
||||
attachment_rule,
|
||||
version="v0.9.0",
|
||||
content=self._attachment_submission_requirement_markdown(
|
||||
version_note="首版附件完整性规则草稿,覆盖基础票据与补件口径。",
|
||||
include_review_note=True,
|
||||
),
|
||||
content_type=AgentAssetContentType.MARKDOWN.value,
|
||||
change_note="补充差旅特殊票据口径,待审核。",
|
||||
change_note="首版草稿。",
|
||||
created_by="高嘉禾",
|
||||
)
|
||||
|
||||
if rejected_rule is not None:
|
||||
self._ensure_asset_version(
|
||||
rejected_rule,
|
||||
version="v0.9.0",
|
||||
content=self._markdown_content(
|
||||
"# 付款双人复核规则\n\n"
|
||||
"- 单笔付款超过阈值时必须双人复核。\n"
|
||||
"- 新增跨币种付款也进入复核队列。\n"
|
||||
"- 当前阈值定义仍不清晰,需继续修订。"
|
||||
attachment_rule,
|
||||
version="v1.0.0",
|
||||
content=self._attachment_submission_requirement_markdown(
|
||||
version_note="补充票据缺失、收据替代和差旅等效凭证口径,待审核。",
|
||||
include_review_note=True,
|
||||
),
|
||||
content_type=AgentAssetContentType.MARKDOWN.value,
|
||||
change_note="补充跨币种场景,但阈值仍待明确。",
|
||||
created_by="孙楠",
|
||||
change_note="补充票据替代与差旅等效凭证口径,待审核。",
|
||||
created_by="高嘉禾",
|
||||
)
|
||||
self._ensure_asset_review(
|
||||
attachment_rule,
|
||||
version="v1.0.0",
|
||||
reviewer="高嘉禾",
|
||||
review_status=AgentReviewStatus.PENDING.value,
|
||||
review_note="等待制度管理员确认收据替代与补件时限口径。",
|
||||
reviewed_at=None,
|
||||
)
|
||||
|
||||
if "rule.expense.scene_submission_standard" not in existing_codes:
|
||||
scene_submission_rule = self._create_seed_asset(
|
||||
asset_type=AgentAssetType.RULE.value,
|
||||
code="rule.expense.scene_submission_standard",
|
||||
name="报销场景提交与附件标准",
|
||||
description="统一定义各报销场景的必填字段、附件类型要求和金额阈值。",
|
||||
domain=AgentAssetDomain.EXPENSE.value,
|
||||
scenario_json=["expense", "risk_check", "scene_policy", "attachment_policy"],
|
||||
owner="费用运营组",
|
||||
reviewer="顾承宇",
|
||||
status=AgentAssetStatus.ACTIVE.value,
|
||||
current_version="v1.0.0",
|
||||
config_json={
|
||||
"severity": "high",
|
||||
"enabled": True,
|
||||
"runtime_kind": "scene_matrix",
|
||||
"rule_template_label": "系统内置场景矩阵规则",
|
||||
},
|
||||
)
|
||||
|
||||
if scene_submission_rule is not None:
|
||||
scene_submission_rule.current_version = "v1.0.0"
|
||||
scene_submission_rule.status = AgentAssetStatus.ACTIVE.value
|
||||
scene_submission_rule.description = "统一定义各报销场景的必填字段、附件类型要求和金额阈值。"
|
||||
scene_submission_rule.config_json = {
|
||||
"severity": "high",
|
||||
"enabled": True,
|
||||
"runtime_kind": "scene_matrix",
|
||||
"rule_template_label": "系统内置场景矩阵规则",
|
||||
}
|
||||
self._ensure_asset_version(
|
||||
scene_submission_rule,
|
||||
version="v1.0.0",
|
||||
content=self._scene_submission_standard_markdown(),
|
||||
content_type=AgentAssetContentType.MARKDOWN.value,
|
||||
change_note="首版报销场景提交标准,覆盖附件类型、必填字段和金额阈值。",
|
||||
created_by="系统初始化",
|
||||
)
|
||||
self._ensure_asset_review(
|
||||
scene_submission_rule,
|
||||
version="v1.0.0",
|
||||
reviewer="顾承宇",
|
||||
review_status=AgentReviewStatus.APPROVED.value,
|
||||
review_note="可作为报销场景统一审核标准正式执行。",
|
||||
reviewed_at=datetime.now(UTC),
|
||||
)
|
||||
|
||||
if "rule.expense.travel_risk_control_standard" not in existing_codes:
|
||||
travel_policy_rule = self._create_seed_asset(
|
||||
asset_type=AgentAssetType.RULE.value,
|
||||
code="rule.expense.travel_risk_control_standard",
|
||||
name="差旅报销风险管控制度",
|
||||
description="统一定义差旅报销的行程闭环、酒店地点一致性、职级差标和风险处置口径。",
|
||||
domain=AgentAssetDomain.EXPENSE.value,
|
||||
scenario_json=["expense", "risk_check", "travel_policy", "travel_standard"],
|
||||
owner="风控与审计部",
|
||||
reviewer="顾承宇",
|
||||
status=AgentAssetStatus.ACTIVE.value,
|
||||
current_version="v1.1.0",
|
||||
config_json={
|
||||
"severity": "high",
|
||||
"enabled": True,
|
||||
"block_on_high_risk": True,
|
||||
"warning_on_medium_risk": True,
|
||||
"source_doc": "document/development/risks/travel-risk-control-standard.md",
|
||||
"runtime_kind": "travel_policy",
|
||||
"rule_template_key": "travel_standard_v1",
|
||||
"rule_template_label": "差旅标准模板",
|
||||
},
|
||||
)
|
||||
|
||||
if travel_policy_rule is not None:
|
||||
travel_policy_rule.current_version = "v1.1.0"
|
||||
travel_policy_rule.status = AgentAssetStatus.ACTIVE.value
|
||||
travel_policy_rule.config_json = {
|
||||
"severity": "high",
|
||||
"enabled": True,
|
||||
"block_on_high_risk": True,
|
||||
"warning_on_medium_risk": True,
|
||||
"source_doc": "document/development/risks/travel-risk-control-standard.md",
|
||||
"runtime_kind": "travel_policy",
|
||||
"rule_template_key": "travel_standard_v1",
|
||||
"rule_template_label": "差旅标准模板",
|
||||
}
|
||||
self._ensure_asset_version(
|
||||
travel_policy_rule,
|
||||
version="v1.0.0",
|
||||
content=self._travel_risk_control_standard_markdown(version="v1.0.0"),
|
||||
content_type=AgentAssetContentType.MARKDOWN.value,
|
||||
change_note="首版差旅制度执行规则,覆盖行程闭环与基础差标校验。",
|
||||
created_by="系统初始化",
|
||||
)
|
||||
self._ensure_asset_version(
|
||||
travel_policy_rule,
|
||||
version="v1.1.0",
|
||||
content=self._travel_risk_control_standard_markdown(version="v1.1.0"),
|
||||
content_type=AgentAssetContentType.MARKDOWN.value,
|
||||
change_note="补充可执行规则块,供审核引擎直接消费差旅制度标准。",
|
||||
created_by="系统初始化",
|
||||
)
|
||||
self._ensure_asset_review(
|
||||
travel_policy_rule,
|
||||
version="v1.1.0",
|
||||
reviewer="顾承宇",
|
||||
review_status=AgentReviewStatus.APPROVED.value,
|
||||
review_note="制度口径已确认,并已补充可执行配置供审核引擎读取。",
|
||||
reviewed_at=datetime.now(UTC),
|
||||
)
|
||||
|
||||
if "skill.ar.aging_summary" not in existing_codes:
|
||||
@@ -979,6 +1206,37 @@ class AgentFoundationService:
|
||||
created_by="系统初始化",
|
||||
)
|
||||
|
||||
if "task.hermes.llm_wiki_rule_formation" not in existing_codes:
|
||||
asset = self._create_seed_asset(
|
||||
asset_type=AgentAssetType.TASK.value,
|
||||
code="task.hermes.llm_wiki_rule_formation",
|
||||
name="Hermes 制度知识与规则草稿形成",
|
||||
description="按知识库变化增量重建报销制度 LLM Wiki,并形成知识候选与规则草稿。",
|
||||
domain=AgentAssetDomain.SYSTEM.value,
|
||||
scenario_json=["schedule", "knowledge", "rule_center"],
|
||||
owner="财务制度管理组",
|
||||
reviewer="顾承宇",
|
||||
status=AgentAssetStatus.ACTIVE.value,
|
||||
current_version="v1.0.0",
|
||||
config_json={"cron": "0 3 * * *", "agent": AgentName.HERMES.value},
|
||||
)
|
||||
self._ensure_asset_version(
|
||||
asset,
|
||||
version="v1.0.0",
|
||||
content=self._json_content(
|
||||
{
|
||||
"task_type": "llm_wiki_rule_formation",
|
||||
"schedule": "0 3 * * *",
|
||||
"target_agent": AgentName.HERMES.value,
|
||||
"folder": "报销制度",
|
||||
"changed_only": True,
|
||||
}
|
||||
),
|
||||
content_type=AgentAssetContentType.JSON.value,
|
||||
change_note="初始化制度知识与规则草稿形成任务。",
|
||||
created_by="系统初始化",
|
||||
)
|
||||
|
||||
def _create_seed_asset(
|
||||
self,
|
||||
*,
|
||||
@@ -1041,6 +1299,121 @@ class AgentFoundationService:
|
||||
)
|
||||
)
|
||||
|
||||
def _ensure_asset_review(
|
||||
self,
|
||||
asset: AgentAsset,
|
||||
*,
|
||||
version: str,
|
||||
reviewer: str,
|
||||
review_status: str,
|
||||
review_note: str,
|
||||
reviewed_at: datetime | None,
|
||||
) -> None:
|
||||
existing = self.db.scalar(
|
||||
select(AgentAssetReview).where(
|
||||
AgentAssetReview.asset_id == asset.id,
|
||||
AgentAssetReview.version == version,
|
||||
AgentAssetReview.review_status == review_status,
|
||||
)
|
||||
)
|
||||
if existing is not None:
|
||||
return
|
||||
|
||||
self.db.add(
|
||||
AgentAssetReview(
|
||||
asset_id=asset.id,
|
||||
version=version,
|
||||
reviewer=reviewer,
|
||||
review_status=review_status,
|
||||
review_note=review_note,
|
||||
reviewed_at=reviewed_at,
|
||||
)
|
||||
)
|
||||
|
||||
def _remove_legacy_rule_assets(self) -> None:
|
||||
assets = list(
|
||||
self.db.scalars(
|
||||
select(AgentAsset).where(AgentAsset.code.in_(LEGACY_RULE_CODES))
|
||||
).all()
|
||||
)
|
||||
for asset in assets:
|
||||
self.db.delete(asset)
|
||||
|
||||
obsolete_logs = list(
|
||||
self.db.scalars(
|
||||
select(AuditLog).where(AuditLog.resource_id.in_(LEGACY_RULE_CODES))
|
||||
).all()
|
||||
)
|
||||
for log in obsolete_logs:
|
||||
self.db.delete(log)
|
||||
|
||||
def _attachment_submission_requirement_markdown(
|
||||
self,
|
||||
*,
|
||||
version_note: str,
|
||||
include_review_note: bool,
|
||||
) -> str:
|
||||
sections = [
|
||||
"# 报销附件与单据完整性规则",
|
||||
"",
|
||||
"## 模板信息",
|
||||
"",
|
||||
"- 模板键:`attachment_requirement_v1`",
|
||||
"- 来源文档:报销制度 / 单据与附件要求",
|
||||
"- 审核状态:待审核",
|
||||
"",
|
||||
"## 目标",
|
||||
"",
|
||||
"统一约束报销提交时的票据、附件与替代凭证要求,避免缺件、错件和无依据流转。",
|
||||
"",
|
||||
"## 适用范围",
|
||||
"",
|
||||
"适用于员工报销提交场景,重点覆盖差旅、住宿、交通、餐费、办公和其他费用的附件校验。",
|
||||
"",
|
||||
"## 输入字段",
|
||||
"",
|
||||
"- expense_type",
|
||||
"- attachments",
|
||||
"- invoice_count",
|
||||
"- reason",
|
||||
"",
|
||||
"## 判断规则",
|
||||
"",
|
||||
"- 报销提交前至少需要 1 份有效附件。",
|
||||
"- 金额类报销原则上应提供合法票据;特殊场景无发票时,必须补充收据与情况说明。",
|
||||
"- 差旅交通报销需提供行程单或等效凭证;住宿报销需提供酒店票据或等效住宿凭证。",
|
||||
"- 缺少必要附件时直接拦截,并提示补件后重新提交。",
|
||||
"",
|
||||
"## 输出",
|
||||
"",
|
||||
"- 风险编码:`invoice_anomaly`",
|
||||
"- 默认动作:`block`",
|
||||
"- 处理说明:附件或单据不完整时退回补充。",
|
||||
"",
|
||||
"## 来源依据",
|
||||
"",
|
||||
"- 报销制度对票据、附件、替代凭证和补件要求的统一约束。",
|
||||
"",
|
||||
"## 审核约束",
|
||||
"",
|
||||
"- 当前规则属于真实业务规则,但仍处于待审核状态。",
|
||||
"- 上线前需由制度管理员确认收据替代、补件时限和特殊场景豁免口径。",
|
||||
f"- 当前版本说明:{version_note}",
|
||||
"",
|
||||
"## 管理员备注",
|
||||
"",
|
||||
"需要结合公司正式报销制度,补充各场景附件替代口径与例外审批要求。",
|
||||
]
|
||||
if include_review_note:
|
||||
sections.extend(["", "```expense-rule", json.dumps(ATTACHMENT_RULE_RUNTIME_CONFIG, ensure_ascii=False, indent=2), "```"])
|
||||
return "\n".join(sections)
|
||||
|
||||
def _scene_submission_standard_markdown(self) -> str:
|
||||
return self._markdown_content(build_scene_submission_standard_markdown())
|
||||
|
||||
def _travel_risk_control_standard_markdown(self, *, version: str = "v1.1.0") -> str:
|
||||
return self._markdown_content(build_travel_risk_control_standard_markdown())
|
||||
|
||||
@staticmethod
|
||||
def _markdown_content(content: str) -> str:
|
||||
return content
|
||||
|
||||
@@ -45,6 +45,19 @@ def test_agent_asset_service_seeds_assets_and_enforces_review_before_activation(
|
||||
|
||||
rules = service.list_assets(asset_type=AgentAssetType.RULE.value)
|
||||
assert len(rules) >= 3
|
||||
assert any(
|
||||
item.code == "rule.expense.travel_risk_control_standard" and item.status == AgentAssetStatus.ACTIVE.value
|
||||
for item in rules
|
||||
)
|
||||
assert all(
|
||||
item.code
|
||||
not in {
|
||||
"rule.expense.duplicate_expense_check",
|
||||
"rule.expense.travel_receipt_requirements",
|
||||
"rule.ap.payment_dual_review",
|
||||
}
|
||||
for item in rules
|
||||
)
|
||||
|
||||
pending_rule = next(item for item in rules if item.status == AgentAssetStatus.REVIEW.value)
|
||||
|
||||
@@ -118,17 +131,39 @@ def test_agent_asset_service_returns_recent_versions_for_rule_detail() -> None:
|
||||
rule = next(
|
||||
item
|
||||
for item in service.list_assets(asset_type=AgentAssetType.RULE.value)
|
||||
if item.code == "rule.expense.duplicate_expense_check"
|
||||
if item.code == "rule.expense.attachment_submission_requirements"
|
||||
)
|
||||
detail = service.get_asset(rule.id)
|
||||
|
||||
assert detail is not None
|
||||
assert detail.current_version == "v1.1.0"
|
||||
assert detail.current_version == "v1.0.0"
|
||||
assert detail.current_version_content_type == AgentAssetContentType.MARKDOWN.value
|
||||
assert isinstance(detail.current_version_content, str)
|
||||
assert len(detail.recent_versions) >= 2
|
||||
assert any(item.is_current for item in detail.recent_versions)
|
||||
assert {item.version for item in detail.recent_versions} >= {"v1.0.0", "v1.1.0"}
|
||||
assert {item.version for item in detail.recent_versions} >= {"v0.9.0", "v1.0.0"}
|
||||
assert detail.config_json["rule_template_key"] == "attachment_requirement_v1"
|
||||
assert "附件或单据不完整" in str(detail.current_version_content)
|
||||
|
||||
|
||||
def test_agent_asset_service_returns_travel_policy_rule_detail() -> None:
|
||||
with build_session() as db:
|
||||
service = AgentAssetService(db)
|
||||
|
||||
rule = next(
|
||||
item
|
||||
for item in service.list_assets(asset_type=AgentAssetType.RULE.value)
|
||||
if item.code == "rule.expense.travel_risk_control_standard"
|
||||
)
|
||||
detail = service.get_asset(rule.id)
|
||||
|
||||
assert detail is not None
|
||||
assert detail.status == AgentAssetStatus.ACTIVE.value
|
||||
assert detail.current_version == "v1.1.0"
|
||||
assert detail.latest_review is not None
|
||||
assert detail.latest_review.review_status == AgentReviewStatus.APPROVED.value
|
||||
assert "行程闭环" in str(detail.current_version_content)
|
||||
assert "住宿标准、飞机舱位和火车席别" in str(detail.current_version_content)
|
||||
|
||||
|
||||
def test_agent_run_service_lists_seeded_trace_data() -> None:
|
||||
|
||||
@@ -45,13 +45,18 @@ def test_list_agent_assets_endpoint_returns_seeded_items() -> None:
|
||||
payload = response.json()
|
||||
assert payload
|
||||
assert all(item["asset_type"] == "rule" for item in payload)
|
||||
assert any(item["code"] == "rule.expense.travel_risk_control_standard" for item in payload)
|
||||
|
||||
|
||||
def test_get_agent_asset_detail_endpoint_returns_version_history() -> None:
|
||||
client, _ = build_client()
|
||||
|
||||
list_response = client.get("/api/v1/agent-assets", params={"asset_type": "rule"})
|
||||
asset_id = list_response.json()[0]["id"]
|
||||
asset_id = next(
|
||||
item["id"]
|
||||
for item in list_response.json()
|
||||
if item["code"] == "rule.expense.travel_risk_control_standard"
|
||||
)
|
||||
|
||||
response = client.get(f"/api/v1/agent-assets/{asset_id}")
|
||||
|
||||
@@ -59,7 +64,8 @@ def test_get_agent_asset_detail_endpoint_returns_version_history() -> None:
|
||||
payload = response.json()
|
||||
assert payload["recent_versions"]
|
||||
assert payload["current_version_content_type"] == "markdown"
|
||||
assert len(payload["recent_versions"]) >= 2
|
||||
assert payload["current_version"] == "v1.1.0"
|
||||
assert "行程闭环" in payload["current_version_content"]
|
||||
|
||||
|
||||
def test_activate_pending_rule_endpoint_is_blocked() -> None:
|
||||
|
||||
Reference in New Issue
Block a user