feat(server): 扩展智能体基础服务,新增端点测试和资产服务测试用例

This commit is contained in:
caoxiaozhu
2026-05-15 06:56:14 +00:00
parent 68a448a551
commit c9cc0b0641
3 changed files with 569 additions and 155 deletions

View File

@@ -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

View File

@@ -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:

View File

@@ -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: