from __future__ import annotations from datetime import UTC, datetime from sqlalchemy import select from app.core.agent_enums import ( AgentAssetContentType, AgentAssetDomain, AgentAssetStatus, AgentAssetType, AgentReviewStatus, ) from app.core.logging import get_logger from app.models.agent_asset import AgentAsset from app.services.agent_asset_spreadsheet import ( COMPANY_COMMUNICATION_EXPENSE_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_TRAVEL_RULE_SCENARIO_JSON, COMPANY_TRAVEL_RULE_VERSION, DIGITAL_EMPLOYEE_SKILL_CATEGORIES, DIGITAL_EMPLOYEE_TASK_CATEGORY_MAP, ) logger = get_logger("app.services.agent_foundation") class AgentFoundationAssetTopUpMixin: def _sync_digital_employee_skill_categories(self) -> None: category_options = list(DIGITAL_EMPLOYEE_SKILL_CATEGORIES) has_changes = False for code, category in DIGITAL_EMPLOYEE_TASK_CATEGORY_MAP.items(): asset = self.db.scalar(select(AgentAsset).where(AgentAsset.code == code)) if asset is None: continue config_json = dict(asset.config_json or {}) changed = False if config_json.get("skill_category") != category: config_json["skill_category"] = category changed = True if config_json.get("skill_category_options") != category_options: config_json["skill_category_options"] = category_options changed = True if changed: asset.config_json = config_json self.db.add(asset) has_changes = True if has_changes: self.db.commit() def _top_up_agent_assets(self, existing_codes: set[str]) -> None: self._remove_legacy_rule_assets() existing_codes = set(self.db.scalars(select(AgentAsset.code)).all()) self._sync_digital_employee_skill_categories() attachment_rule = self.db.scalar( select(AgentAsset).where(AgentAsset.code == ATTACHMENT_RULE_ASSET_CODE) ) scene_submission_rule = self.db.scalar( select(AgentAsset).where(AgentAsset.code == "rule.expense.scene_submission_standard") ) travel_policy_rule = self.db.scalar( select(AgentAsset).where(AgentAsset.code == "rule.expense.travel_risk_control_standard") ) company_travel_rule = self.db.scalar( select(AgentAsset).where(AgentAsset.code == COMPANY_TRAVEL_EXPENSE_RULE_CODE) ) company_communication_rule = self.db.scalar( select(AgentAsset).where(AgentAsset.code == COMPANY_COMMUNICATION_EXPENSE_RULE_CODE) ) 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 attachment_rule is not None: if not str(attachment_rule.current_version or "").strip(): attachment_rule.current_version = "v1.0.0" if not str(attachment_rule.working_version or "").strip(): attachment_rule.working_version = attachment_rule.current_version attachment_rule.status = attachment_rule.status or 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( 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="高嘉禾", ) self._ensure_asset_version( 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="高嘉禾", ) 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: if not str(scene_submission_rule.current_version or "").strip(): scene_submission_rule.current_version = "v1.0.0" if not str(scene_submission_rule.working_version or "").strip(): scene_submission_rule.working_version = scene_submission_rule.current_version if not str(scene_submission_rule.published_version or "").strip(): scene_submission_rule.published_version = scene_submission_rule.current_version scene_submission_rule.status = ( scene_submission_rule.status or 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: if not str(travel_policy_rule.current_version or "").strip(): travel_policy_rule.current_version = "v1.1.0" if not str(travel_policy_rule.working_version or "").strip(): travel_policy_rule.working_version = travel_policy_rule.current_version if not str(travel_policy_rule.published_version or "").strip(): travel_policy_rule.published_version = travel_policy_rule.current_version travel_policy_rule.status = travel_policy_rule.status or 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), ) self.sync_platform_risk_rules_from_library() if COMPANY_TRAVEL_EXPENSE_RULE_CODE not in existing_codes: company_travel_rule = self._create_seed_asset( 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, config_json={ "severity": "medium", "enabled": True, "tag": "财务规则", "detail_mode": "spreadsheet", "scenario_category": COMPANY_TRAVEL_RULE_SCENARIO_JSON[0], "ai_review_category": COMPANY_TRAVEL_RULE_SCENARIO_JSON[0], "rule_template_label": "差旅报销 Excel 模板", }, ) if COMPANY_COMMUNICATION_EXPENSE_RULE_CODE not in existing_codes: company_communication_rule = self._create_seed_asset( 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, config_json={ "severity": "medium", "enabled": True, "tag": "财务规则", "detail_mode": "spreadsheet", "scenario_category": COMPANY_COMMUNICATION_RULE_SCENARIO_JSON[0], "ai_review_category": COMPANY_COMMUNICATION_RULE_SCENARIO_JSON[0], "rule_template_label": "通信费报销 Excel 模板", }, ) if company_travel_rule is not None: company_travel_rule.scenario_json = list(COMPANY_TRAVEL_RULE_SCENARIO_JSON) if not str(company_travel_rule.current_version or "").strip(): company_travel_rule.current_version = COMPANY_TRAVEL_RULE_VERSION if not str(company_travel_rule.working_version or "").strip(): company_travel_rule.working_version = company_travel_rule.current_version if not str(company_travel_rule.published_version or "").strip(): company_travel_rule.published_version = company_travel_rule.current_version if not str(company_travel_rule.status or "").strip(): company_travel_rule.status = AgentAssetStatus.ACTIVE.value company_travel_rule.description = ( "通过 Excel 明细表维护差旅费报销标准、票据要求和审批口径。" ) company_travel_rule.config_json = { **(company_travel_rule.config_json or {}), "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 模板", } company_travel_rule_meta = self._ensure_company_travel_rule_spreadsheet_seed( company_travel_rule, version=str(company_travel_rule.current_version or COMPANY_TRAVEL_RULE_VERSION), actor_name="系统初始化", ) self._ensure_asset_version( company_travel_rule, version=str(company_travel_rule.current_version or COMPANY_TRAVEL_RULE_VERSION), content=AgentAssetSpreadsheetManager.build_version_markdown( rule_name=company_travel_rule.name, version=str(company_travel_rule.current_version or COMPANY_TRAVEL_RULE_VERSION), metadata=company_travel_rule_meta, ), content_type=AgentAssetContentType.MARKDOWN.value, change_note="初始化差旅费报销 Excel 规则表。", created_by="系统初始化", ) if ( str(company_travel_rule.current_version or "").strip() == COMPANY_TRAVEL_RULE_VERSION ): self._ensure_asset_review( company_travel_rule, version=COMPANY_TRAVEL_RULE_VERSION, reviewer="顾承宇", review_status=AgentReviewStatus.APPROVED.value, review_note="首版 Excel 规则表已确认,可作为财务规则使用。", reviewed_at=datetime.now(UTC), ) if company_communication_rule is not None: company_communication_rule.scenario_json = list( COMPANY_COMMUNICATION_RULE_SCENARIO_JSON ) if not str(company_communication_rule.current_version or "").strip(): company_communication_rule.current_version = COMPANY_COMMUNICATION_RULE_VERSION if not str(company_communication_rule.working_version or "").strip(): company_communication_rule.working_version = ( company_communication_rule.current_version ) if not str(company_communication_rule.published_version or "").strip(): company_communication_rule.published_version = ( company_communication_rule.current_version ) if not str(company_communication_rule.status or "").strip(): company_communication_rule.status = AgentAssetStatus.ACTIVE.value company_communication_rule.description = ( "通过 Excel 明细表维护员工通信费报销标准、专项补充口径和审批要求。" ) company_communication_rule.config_json = { **(company_communication_rule.config_json or {}), "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_communication_rule_meta = ( self._ensure_company_communication_rule_spreadsheet_seed( company_communication_rule, version=str( company_communication_rule.current_version or COMPANY_COMMUNICATION_RULE_VERSION ), actor_name="系统初始化", ) ) self._ensure_asset_version( company_communication_rule, version=str( company_communication_rule.current_version or COMPANY_COMMUNICATION_RULE_VERSION ), content=AgentAssetSpreadsheetManager.build_version_markdown( rule_name=company_communication_rule.name, version=str( company_communication_rule.current_version or COMPANY_COMMUNICATION_RULE_VERSION ), metadata=company_communication_rule_meta, ), content_type=AgentAssetContentType.MARKDOWN.value, change_note="初始化通信费报销 Excel 规则表。", created_by="系统初始化", ) if ( str(company_communication_rule.current_version or "").strip() == COMPANY_COMMUNICATION_RULE_VERSION ): self._ensure_asset_review( company_communication_rule, version=COMPANY_COMMUNICATION_RULE_VERSION, reviewer="顾承宇", review_status=AgentReviewStatus.APPROVED.value, review_note="首版 Excel 规则表已确认,可作为财务规则使用。", reviewed_at=datetime.now(UTC), ) self._hide_deprecated_finance_rule_assets() if "skill.ar.aging_summary" not in existing_codes: asset = self._create_seed_asset( 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", config_json={"input_schema": ["customer", "aging_bucket", "status"]}, ) self._ensure_asset_version( 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="系统初始化", ) if "mcp.ledger.snapshot_mock" not in existing_codes: asset = self._create_seed_asset( 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", config_json={"endpoint": "mock://ledger/snapshot", "timeout_ms": 1500}, ) self._ensure_asset_version( 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="系统初始化", ) if "task.hermes.weekly_ar_summary" not in existing_codes: asset = self._create_seed_asset( asset_type=AgentAssetType.TASK.value, code="task.hermes.weekly_ar_summary", name="Hermes 每周应收账龄汇总", description="每周汇总逾期应收、账龄分布和客户风险变化。", domain=AgentAssetDomain.SYSTEM.value, scenario_json=["schedule", "accounts_receivable", "summary"], owner="风控与审计部", reviewer="顾承宇", status=AgentAssetStatus.ACTIVE.value, current_version="v1.0.0", config_json=self._digital_employee_task_config("task.hermes.weekly_ar_summary", "0 10 * * 1"), ) self._ensure_asset_version( asset, version="v1.0.0", content=self._digital_employee_task_content( "task.hermes.weekly_ar_summary", "weekly_ar_summary", "0 10 * * 1", ), content_type=AgentAssetContentType.JSON.value, change_note="初始化应收账龄汇总任务。", created_by="系统初始化", ) if "task.hermes.rule_review_digest" not in existing_codes: asset = self._create_seed_asset( asset_type=AgentAssetType.TASK.value, code="task.hermes.rule_review_digest", name="Hermes 规则待审摘要", description="每天汇总待审规则、待补样例和被拒规则修订建议。", domain=AgentAssetDomain.SYSTEM.value, scenario_json=["schedule", "rule_center", "review_digest"], owner="风控与审计部", reviewer="顾承宇", status=AgentAssetStatus.ACTIVE.value, current_version="v1.0.0", config_json=self._digital_employee_task_config("task.hermes.rule_review_digest", "0 18 * * *"), ) self._ensure_asset_version( asset, version="v1.0.0", content=self._digital_employee_task_content( "task.hermes.rule_review_digest", "rule_review_digest", "0 18 * * *", ), content_type=AgentAssetContentType.JSON.value, change_note="初始化规则待审摘要任务。", created_by="系统初始化", ) if "task.hermes.knowledge_index_sync" not in existing_codes: asset = self._create_seed_asset( asset_type=AgentAssetType.TASK.value, code="task.hermes.knowledge_index_sync", name="Hermes ??????", description="?????????? LightRAG ???????", domain=AgentAssetDomain.SYSTEM.value, scenario_json=["schedule", "knowledge", "rule_center"], owner="财务制度管理组", reviewer="顾承宇", status=AgentAssetStatus.ACTIVE.value, current_version="v1.0.0", config_json=self._digital_employee_task_config("task.hermes.knowledge_index_sync", "0 0 * * *"), ) self._ensure_asset_version( asset, version="v1.0.0", content=self._digital_employee_task_content( "task.hermes.knowledge_index_sync", "knowledge_index_sync", "0 0 * * *", folder="报销制度", changed_only=True, ), content_type=AgentAssetContentType.JSON.value, change_note="初始化制度知识与规则草稿形成任务。", created_by="系统初始化", )