from __future__ import annotations from datetime import UTC, datetime from sqlalchemy import select from app.core.agent_enums import ( AgentAssetContentType, AgentAssetDomain, AgentAssetStatus, AgentAssetType, AgentName, AgentReviewStatus, ) from app.core.config import SERVER_DIR from app.core.logging import get_logger from app.models.agent_asset import AgentAsset, AgentAssetReview, AgentAssetVersion from app.services.agent_asset_spreadsheet import ( COMPANY_COMMUNICATION_EXPENSE_RULE_CODE, COMPANY_PREAPPROVAL_RULE_CODE, COMPANY_TRAVEL_EXPENSE_RULE_CODE, FINANCE_RULES_LIBRARY, AgentAssetSpreadsheetManager, ) from app.services.agent_foundation_constants import ( ATTACHMENT_RULE_ASSET_CODE, ATTACHMENT_RULE_RUNTIME_CONFIG, COMPANY_COMMUNICATION_RULE_SCENARIO_JSON, COMPANY_COMMUNICATION_RULE_VERSION, COMPANY_PREAPPROVAL_RULE_SCENARIO_JSON, COMPANY_PREAPPROVAL_RULE_VERSION, COMPANY_TRAVEL_RULE_SCENARIO_JSON, COMPANY_TRAVEL_RULE_VERSION, DIGITAL_EMPLOYEE_FINANCE_POLICY_TASK_CODE, DIGITAL_EMPLOYEE_PROFILE_SCAN_TASK_CODE, DIGITAL_EMPLOYEE_RISK_GRAPH_SCAN_TASK_CODE, DIGITAL_EMPLOYEE_RULE_DISCOVERY_TASK_CODE, DIGITAL_EMPLOYEE_SKILL_CATEGORIES, DIGITAL_EMPLOYEE_TASK_CATEGORY_MAP, ) logger = get_logger("app.services.agent_foundation") class AgentFoundationAssetSeedMixin: def _digital_employee_task_config(self, code: str, cron: str) -> dict[str, object]: return { "cron": cron, "schedule": cron, "cron_expression": 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 = ( SERVER_DIR / "src" / "app" / "skills" / "domain" / skill_name / "SKILL.md" ) if skill_path.exists(): return skill_path.read_text(encoding="utf-8").strip() return "\n".join(fallback_lines) def _finance_policy_knowledge_skill_markdown(self) -> str: return self._read_domain_skill_markdown( "finance-policy-knowledge-organizer", [ "---", "name: finance-policy-knowledge-organizer", "description: 用于整理公司财务知识制度。", "---", "", "# 整理公司财务知识制度", "", "## 功能说明", "", "整理公司财务制度、报销口径、审批要求和知识库资料,输出可复核的结构化知识。", ], ) def _financial_risk_graph_scan_skill_markdown(self) -> str: return self._read_domain_skill_markdown( "financial-risk-graph-scanner", [ "---", "name: financial-risk-graph-scanner", "description: 用于巡检财务风险图谱,生成风险观察和可复核证据链。", "---", "", "# 财务风险图谱巡检", "", "## 功能说明", "", "扫描新增报销单、票据、审批链、员工画像和规则命中结果,输出统一风险观察。", ], ) def _employee_behavior_profile_scan_skill_markdown(self) -> str: return self._read_domain_skill_markdown( "employee-behavior-profile-scanner", [ "---", "name: employee-behavior-profile-scanner", "description: 用于更新员工行为画像,沉淀费用、流程质量和协作治理基线。", "---", "", "# 员工行为画像巡检", "", "## 功能说明", "", "汇总员工费用、审批、材料完整性和智能协作数据,生成可解释的画像快照。", ], ) def _risk_rule_discovery_skill_markdown(self) -> str: return self._read_domain_skill_markdown( "risk-rule-discovery", [ "---", "name: risk-rule-discovery", "description: 兼容别名。用于归集申请和报销事实中的潜在线索,不生成规则。", "---", "", "# 风险线索归集", "", "## 功能说明", "", "从申请、报销、规则命中和人工反馈中整理事实、证据和待复核线索。", ], ) def _risk_clue_collector_skill_markdown(self) -> str: return self._read_domain_skill_markdown( "risk-clue-collector", [ "---", "name: risk-clue-collector", "description: 用于归集申请和报销事实中的潜在线索,不生成规则、不发布规则、不替代人工确认。", "---", "", "# 风险线索归集", "", "## 功能说明", "", "从申请、报销、规则命中和人工反馈中整理事实、证据和待复核线索。", ], ) def _digital_employee_task_content( self, code: str, task_type: str, schedule: str, **extra: object, ) -> str: return self._json_content( { "task_type": task_type, "skill_category": DIGITAL_EMPLOYEE_TASK_CATEGORY_MAP.get(code, "整理"), "skill_category_options": list(DIGITAL_EMPLOYEE_SKILL_CATEGORIES), "schedule": schedule, "target_agent": AgentName.HERMES.value, **extra, } ) def _seed_agent_assets(self) -> None: existing_codes = set(self.db.scalars(select(AgentAsset.code)).all()) if existing_codes: self._top_up_agent_assets(existing_codes) return attachment_rule = AgentAsset( 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", published_version=None, working_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, }, ) scene_submission_rule = AgentAsset( 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", published_version="v1.0.0", working_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", published_version="v1.1.0", working_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": "差旅标准模板", }, ) company_travel_rule = AgentAsset( asset_type=AgentAssetType.RULE.value, code=COMPANY_TRAVEL_EXPENSE_RULE_CODE, name="公司差旅费报销规则", description="通过 Excel 明细表维护差旅费报销标准、票据要求和审批口径。", domain=AgentAssetDomain.EXPENSE.value, scenario_json=list(COMPANY_TRAVEL_RULE_SCENARIO_JSON), owner="财务制度管理组", reviewer="顾承宇", status=AgentAssetStatus.ACTIVE.value, current_version=COMPANY_TRAVEL_RULE_VERSION, published_version=COMPANY_TRAVEL_RULE_VERSION, working_version=COMPANY_TRAVEL_RULE_VERSION, config_json={ "severity": "medium", "enabled": True, "tag": "基础规则", "detail_mode": "spreadsheet", "rule_library": FINANCE_RULES_LIBRARY, "scenario_category": COMPANY_TRAVEL_RULE_SCENARIO_JSON[0], "ai_review_category": COMPANY_TRAVEL_RULE_SCENARIO_JSON[0], "rule_template_label": "差旅报销 Excel 模板", }, ) platform_risk_assets = self._build_platform_risk_seed_assets() company_communication_rule = AgentAsset( asset_type=AgentAssetType.RULE.value, code=COMPANY_COMMUNICATION_EXPENSE_RULE_CODE, name="公司通信费报销规则", description="通过 Excel 明细表维护员工通信费报销标准、专项补充口径和审批要求。", domain=AgentAssetDomain.EXPENSE.value, scenario_json=list(COMPANY_COMMUNICATION_RULE_SCENARIO_JSON), owner="财务制度管理组", reviewer="顾承宇", status=AgentAssetStatus.ACTIVE.value, current_version=COMPANY_COMMUNICATION_RULE_VERSION, published_version=COMPANY_COMMUNICATION_RULE_VERSION, working_version=COMPANY_COMMUNICATION_RULE_VERSION, config_json={ "severity": "medium", "enabled": True, "tag": "基础规则", "detail_mode": "spreadsheet", "rule_library": FINANCE_RULES_LIBRARY, "scenario_category": COMPANY_COMMUNICATION_RULE_SCENARIO_JSON[0], "ai_review_category": COMPANY_COMMUNICATION_RULE_SCENARIO_JSON[0], "rule_template_label": "通信费报销 Excel 模板", }, ) company_preapproval_rule = AgentAsset( asset_type=AgentAssetType.RULE.value, code=COMPANY_PREAPPROVAL_RULE_CODE, name="公司费用申请审批规则", description="通过 Excel 明细表维护业务招待、办公用品和通用大额费用的事前申请与审批阈值。", domain=AgentAssetDomain.EXPENSE.value, scenario_json=list(COMPANY_PREAPPROVAL_RULE_SCENARIO_JSON), owner="财务制度管理组", reviewer="顾承宣", status=AgentAssetStatus.ACTIVE.value, current_version=COMPANY_PREAPPROVAL_RULE_VERSION, published_version=COMPANY_PREAPPROVAL_RULE_VERSION, working_version=COMPANY_PREAPPROVAL_RULE_VERSION, config_json={ "severity": "high", "enabled": True, "tag": "申请规则", "detail_mode": "spreadsheet", "rule_library": FINANCE_RULES_LIBRARY, "scenario_category": COMPANY_PREAPPROVAL_RULE_SCENARIO_JSON[0], "ai_review_category": COMPANY_PREAPPROVAL_RULE_SCENARIO_JSON[0], "finance_rule_code": "expense.preapproval.policy", "finance_rule_sheet": "费用申请审批规则", "expense_types": ["meal", "entertainment", "office", "all"], "business_stage": ["expense_application", "reimbursement"], "budget_required": True, "rule_template_label": "费用申请审批 Excel 模板", }, ) skill_expense_asset = AgentAsset( asset_type=AgentAssetType.SKILL.value, code="skill.expense.summary_lookup", name="报销汇总查询技能", description="根据时间、员工和部门汇总报销金额与单据数量。", domain=AgentAssetDomain.EXPENSE.value, scenario_json=["expense", "query", "summary"], owner="平台研发组", reviewer="陈硕", status=AgentAssetStatus.ACTIVE.value, current_version="v1.0.0", published_version="v1.0.0", working_version="v1.0.0", config_json={"input_schema": ["time_range", "employee", "department"]}, ) skill_ar_asset = AgentAsset( asset_type=AgentAssetType.SKILL.value, code="skill.ar.aging_summary", name="应收账龄汇总技能", description="按客户、账龄和逾期状态汇总应收风险分布。", domain=AgentAssetDomain.AR.value, scenario_json=["accounts_receivable", "query", "aging_summary"], owner="平台研发组", reviewer="陈硕", status=AgentAssetStatus.ACTIVE.value, current_version="v1.0.0", published_version="v1.0.0", working_version="v1.0.0", config_json={"input_schema": ["customer", "aging_bucket", "status"]}, ) invoice_mcp_asset = AgentAsset( asset_type=AgentAssetType.MCP.value, code="mcp.invoice.verify_mock", name="发票验真 Mock 服务", description="模拟发票验真、发票状态查询和异常降级说明。", domain=AgentAssetDomain.SYSTEM.value, scenario_json=["expense", "invoice_validation"], owner="平台研发组", reviewer="周悦宁", status=AgentAssetStatus.ACTIVE.value, current_version="v1.0.0", published_version="v1.0.0", working_version="v1.0.0", config_json={"endpoint": "mock://invoice/verify", "timeout_ms": 1200}, ) ledger_mcp_asset = AgentAsset( asset_type=AgentAssetType.MCP.value, code="mcp.ledger.snapshot_mock", name="总账快照 Mock 服务", description="模拟返回应收、应付和费用汇总快照,供 Agent 查询和巡检。", domain=AgentAssetDomain.SYSTEM.value, scenario_json=["expense", "accounts_receivable", "accounts_payable"], owner="平台研发组", reviewer="周悦宁", status=AgentAssetStatus.ACTIVE.value, current_version="v1.0.0", published_version="v1.0.0", working_version="v1.0.0", config_json={"endpoint": "mock://ledger/snapshot", "timeout_ms": 1500}, ) finance_policy_knowledge_task = AgentAsset( asset_type=AgentAssetType.TASK.value, code=DIGITAL_EMPLOYEE_FINANCE_POLICY_TASK_CODE, name="整理公司财务知识制度", description="按计划整理公司财务制度、报销口径、审批要求和知识库资料,形成可复核的结构化知识。", domain=AgentAssetDomain.SYSTEM.value, scenario_json=["schedule", "knowledge", "rule_center"], owner="财务制度管理组", reviewer="顾承宇", status=AgentAssetStatus.ACTIVE.value, current_version="v1.0.0", published_version="v1.0.0", working_version="v1.0.0", config_json={ **self._digital_employee_task_config( DIGITAL_EMPLOYEE_FINANCE_POLICY_TASK_CODE, "0 3 * * *", ), "skill_name": "finance-policy-knowledge-organizer", "folder": "财务制度", "changed_only": True, "output_format": "knowledge_organizing_report", "allowed_outputs": [ "facts", "policy_refs", "evidence_refs", "knowledge_items", "human_review_required", ], "role_boundary": "规则由人定义,风险由人确认,数字员工只整理人提供的制度和报销事实。", "writes_rules": False, }, ) risk_graph_scan_task = AgentAsset( asset_type=AgentAssetType.TASK.value, code=DIGITAL_EMPLOYEE_RISK_GRAPH_SCAN_TASK_CODE, name="财务风险图谱巡检", description="按计划扫描报销单、票据、审批链、员工画像和规则命中结果,生成风险观察与可复核证据链。", domain=AgentAssetDomain.SYSTEM.value, scenario_json=["schedule", "expense", "risk_graph", "risk_observation"], owner="风控与审计部", reviewer="顾承宇", status=AgentAssetStatus.ACTIVE.value, current_version="v1.0.0", published_version="v1.0.0", working_version="v1.0.0", config_json={ **self._digital_employee_task_config( DIGITAL_EMPLOYEE_RISK_GRAPH_SCAN_TASK_CODE, "0 9 * * *", ), "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, }, ) employee_profile_scan_task = AgentAsset( asset_type=AgentAssetType.TASK.value, code=DIGITAL_EMPLOYEE_PROFILE_SCAN_TASK_CODE, name="员工行为画像巡检", description="按计划更新员工费用行为、材料完整性、审批效率和智能协作画像,为风险图谱提供画像基线。", domain=AgentAssetDomain.SYSTEM.value, scenario_json=["schedule", "employee_profile", "baseline", "risk_graph"], owner="风控与审计部", reviewer="顾承宇", status=AgentAssetStatus.ACTIVE.value, current_version="v1.0.0", published_version="v1.0.0", working_version="v1.0.0", config_json={ **self._digital_employee_task_config( DIGITAL_EMPLOYEE_PROFILE_SCAN_TASK_CODE, "30 8 * * 1", ), "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, }, ) self.db.add_all( [ attachment_rule, scene_submission_rule, travel_policy_rule, *platform_risk_assets, company_travel_rule, company_communication_rule, company_preapproval_rule, skill_expense_asset, skill_ar_asset, invoice_mcp_asset, ledger_mcp_asset, finance_policy_knowledge_task, risk_graph_scan_task, employee_profile_scan_task, ] ) self.db.flush() self._upsert_runtime_digital_employee_tasks( set(self.db.scalars(select(AgentAsset.code)).all()) ) self.db.flush() company_travel_rule_meta = self._ensure_company_travel_rule_spreadsheet_seed( company_travel_rule, version=COMPANY_TRAVEL_RULE_VERSION, actor_name="系统初始化", ) company_communication_rule_meta = self._ensure_company_communication_rule_spreadsheet_seed( company_communication_rule, version=COMPANY_COMMUNICATION_RULE_VERSION, actor_name="系统初始化", ) company_preapproval_rule_meta = self._ensure_company_preapproval_rule_spreadsheet_seed( company_preapproval_rule, version=COMPANY_PREAPPROVAL_RULE_VERSION, actor_name="系统初始化", ) self._hide_deprecated_finance_rule_assets() self.db.add_all( [ AgentAssetVersion( asset=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="首版草稿。", created_by="高嘉禾", ), AgentAssetVersion( asset=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="高嘉禾", ), AgentAssetVersion( asset=scene_submission_rule, version="v1.0.0", content=self._scene_submission_standard_markdown(), content_type=AgentAssetContentType.MARKDOWN.value, change_note="首版报销场景提交标准,覆盖附件类型、必填字段和金额阈值。", created_by="系统初始化", ), AgentAssetVersion( 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="系统初始化", ), 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=asset, version="v1.0.0", content=self._platform_risk_rule_markdown(asset), content_type=AgentAssetContentType.MARKDOWN.value, change_note=f"平台通用风险规则:{asset.name}", created_by="系统初始化", ) for asset in platform_risk_assets ], AgentAssetVersion( asset=company_travel_rule, version=COMPANY_TRAVEL_RULE_VERSION, content=AgentAssetSpreadsheetManager.build_version_markdown( rule_name=company_travel_rule.name, version=COMPANY_TRAVEL_RULE_VERSION, metadata=company_travel_rule_meta, ), content_type=AgentAssetContentType.MARKDOWN.value, change_note="初始化差旅费报销 Excel 规则表。", created_by="系统初始化", ), AgentAssetVersion( asset=company_communication_rule, version=COMPANY_COMMUNICATION_RULE_VERSION, content=AgentAssetSpreadsheetManager.build_version_markdown( rule_name=company_communication_rule.name, version=COMPANY_COMMUNICATION_RULE_VERSION, metadata=company_communication_rule_meta, ), content_type=AgentAssetContentType.MARKDOWN.value, change_note="初始化通信费报销 Excel 规则表。", created_by="系统初始化", ), AgentAssetVersion( asset=company_preapproval_rule, version=COMPANY_PREAPPROVAL_RULE_VERSION, content=AgentAssetSpreadsheetManager.build_version_markdown( rule_name=company_preapproval_rule.name, version=COMPANY_PREAPPROVAL_RULE_VERSION, metadata=company_preapproval_rule_meta, ), content_type=AgentAssetContentType.MARKDOWN.value, change_note="初始化费用申请审批 Excel 规则表。", created_by="系统初始化", ), AgentAssetVersion( asset=skill_expense_asset, version="v1.0.0", content=self._json_content( { "inputs": ["time_range", "employee", "department"], "outputs": ["total_amount", "claim_count"], "dependencies": ["database.expense_claims"], } ), content_type=AgentAssetContentType.JSON.value, change_note="初始化技能快照。", created_by="系统初始化", ), AgentAssetVersion( asset=skill_ar_asset, version="v1.0.0", content=self._json_content( { "inputs": ["customer", "aging_bucket", "status"], "outputs": ["receivable_total", "overdue_total", "customer_count"], "dependencies": ["database.accounts_receivable"], } ), content_type=AgentAssetContentType.JSON.value, change_note="初始化应收账龄技能快照。", created_by="系统初始化", ), AgentAssetVersion( asset=invoice_mcp_asset, version="v1.0.0", content=self._json_content( { "service_type": "mock", "auth_mode": "none", "degrade_strategy": "return_stub_with_warning", } ), content_type=AgentAssetContentType.JSON.value, change_note="初始化 MCP 快照。", created_by="系统初始化", ), AgentAssetVersion( asset=ledger_mcp_asset, version="v1.0.0", content=self._json_content( { "service_type": "mock", "auth_mode": "service_account", "degrade_strategy": "return_cached_snapshot_with_warning", } ), content_type=AgentAssetContentType.JSON.value, change_note="初始化总账快照 MCP。", created_by="系统初始化", ), AgentAssetVersion( asset=finance_policy_knowledge_task, version="v1.0.0", content=self._finance_policy_knowledge_skill_markdown(), content_type=AgentAssetContentType.MARKDOWN.value, change_note="初始化整理公司财务知识制度能力。", created_by="系统初始化", ), ] ) self.db.add_all( [ AgentAssetReview( asset=attachment_rule, version="v1.0.0", reviewer="高嘉禾", review_status=AgentReviewStatus.PENDING.value, review_note="等待制度管理员确认收据替代与补件时限口径。", reviewed_at=None, ), AgentAssetReview( 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), ), AgentAssetReview( asset=company_travel_rule, version=COMPANY_TRAVEL_RULE_VERSION, reviewer="顾承宇", review_status=AgentReviewStatus.APPROVED.value, review_note="首版 Excel 规则表已确认,可作为基础规则使用。", reviewed_at=datetime.now(UTC), ), AgentAssetReview( asset=company_communication_rule, version=COMPANY_COMMUNICATION_RULE_VERSION, reviewer="顾承宇", review_status=AgentReviewStatus.APPROVED.value, review_note="首版 Excel 规则表已确认,可作为基础规则使用。", reviewed_at=datetime.now(UTC), ), ] )