from __future__ import annotations import hashlib import json from datetime import UTC, date, datetime from decimal import Decimal from pathlib import Path from sqlalchemy import inspect, select, text from app.core.agent_enums import ( AgentAssetContentType, AgentAssetDomain, AgentAssetStatus, AgentAssetType, AgentName, AgentPermissionLevel, AgentReviewStatus, AgentRunSource, AgentRunStatus, AgentToolType, ) from app.models.agent_asset import AgentAsset, AgentAssetReview, AgentAssetVersion from app.models.agent_run import AgentRun, AgentToolCall, SemanticParseLog from app.models.audit_log import AuditLog from app.models.financial_record import ( AccountsPayableRecord, AccountsReceivableRecord, ExpenseClaim, ExpenseClaimItem, ) from app.services.agent_asset_rule_library import AgentAssetRuleLibraryManager from app.services.agent_asset_spreadsheet import ( AgentAssetSpreadsheetManager, COMPANY_COMMUNICATION_EXPENSE_RULE_CODE, COMPANY_COMMUNICATION_EXPENSE_RULE_FILENAME, COMPANY_TRAVEL_EXPENSE_RULE_CODE, COMPANY_TRAVEL_EXPENSE_RULE_FILENAME, FINANCE_RULES_LIBRARY, RISK_RULES_LIBRARY, ) from app.services.expense_rule_runtime import ( build_scene_submission_standard_markdown, build_travel_risk_control_standard_markdown, ) 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, DEMO_EXPENSE_CLAIM_SIGNATURES, DEMO_PAYABLE_SIGNATURES, DEMO_RECEIVABLE_SIGNATURES, LEGACY_RULE_CODES, PLATFORM_DESTINATION_LOCATION_RULE_FILENAME, ) from app.core.logging import get_logger logger = get_logger("app.services.agent_foundation") class AgentFoundationSpreadsheetMixin: def _ensure_company_travel_rule_spreadsheet_seed( self, asset: AgentAsset, *, version: str, actor_name: str, ): manager = AgentAssetSpreadsheetManager() manager.ensure_rule_library_dirs() live_document = manager.store_rule_library_spreadsheet( library=FINANCE_RULES_LIBRARY, file_name=COMPANY_TRAVEL_EXPENSE_RULE_FILENAME, content=self._read_or_build_company_travel_rule_file(manager), actor_name=actor_name, source="rule-library", ) existing_document = ( asset.config_json.get("rule_document") if isinstance(asset.config_json, dict) else None ) storage_key = ( str(existing_document.get("storage_key") or "").strip() if isinstance(existing_document, dict) else "" ) if storage_key: try: existing_path = manager.resolve_storage_path(storage_key) except FileNotFoundError: existing_path = None if existing_path is not None and existing_path.exists(): asset.config_json = { **(asset.config_json or {}), "detail_mode": "spreadsheet", "tag": "财务规则", "rule_library": FINANCE_RULES_LIBRARY, "rule_document": { **AgentAssetSpreadsheetManager.build_rule_document_config( live_document, asset_version=version, ), "storage_key": live_document.storage_key, }, } return live_document asset.config_json = { **(asset.config_json or {}), "detail_mode": "spreadsheet", "tag": "财务规则", "rule_library": FINANCE_RULES_LIBRARY, "rule_document": { **AgentAssetSpreadsheetManager.build_rule_document_config( live_document, asset_version=version, ), "storage_key": live_document.storage_key, }, } return live_document def _ensure_company_communication_rule_spreadsheet_seed( self, asset: AgentAsset, *, version: str, actor_name: str, ): return self._ensure_finance_rule_spreadsheet_seed( asset, version=version, actor_name=actor_name, file_name=COMPANY_COMMUNICATION_EXPENSE_RULE_FILENAME, fallback_sheet_name="通信费报销规则", ) @staticmethod def _read_or_build_company_travel_rule_file( manager: AgentAssetSpreadsheetManager, ) -> bytes: live_key = ( Path("rules") / FINANCE_RULES_LIBRARY / COMPANY_TRAVEL_EXPENSE_RULE_FILENAME ).as_posix() live_path = manager.resolve_storage_path(live_key) if live_path.exists(): return live_path.read_bytes() return AgentAssetSpreadsheetManager.build_blank_rule_workbook("差旅费报销规则") def _ensure_finance_rule_spreadsheet_seed( self, asset: AgentAsset, *, version: str, actor_name: str, file_name: str, fallback_sheet_name: str, ): manager = AgentAssetSpreadsheetManager() manager.ensure_rule_library_dirs() live_document = manager.store_rule_library_spreadsheet( library=FINANCE_RULES_LIBRARY, file_name=file_name, content=self._read_or_build_finance_rule_file( manager, file_name=file_name, fallback_sheet_name=fallback_sheet_name, ), actor_name=actor_name, source="rule-library", ) existing_document = ( asset.config_json.get("rule_document") if isinstance(asset.config_json, dict) else None ) storage_key = ( str(existing_document.get("storage_key") or "").strip() if isinstance(existing_document, dict) else "" ) if storage_key: try: existing_path = manager.resolve_storage_path(storage_key) except FileNotFoundError: existing_path = None if existing_path is not None and existing_path.exists(): asset.config_json = { **(asset.config_json or {}), "detail_mode": "spreadsheet", "tag": "财务规则", "rule_library": FINANCE_RULES_LIBRARY, "rule_document": { **AgentAssetSpreadsheetManager.build_rule_document_config( live_document, asset_version=version, ), "storage_key": live_document.storage_key, }, } return live_document asset.config_json = { **(asset.config_json or {}), "detail_mode": "spreadsheet", "tag": "财务规则", "rule_library": FINANCE_RULES_LIBRARY, "rule_document": { **AgentAssetSpreadsheetManager.build_rule_document_config( live_document, asset_version=version, ), "storage_key": live_document.storage_key, }, } return live_document @staticmethod def _read_or_build_finance_rule_file( manager: AgentAssetSpreadsheetManager, *, file_name: str, fallback_sheet_name: str, ) -> bytes: live_key = ( Path("rules") / FINANCE_RULES_LIBRARY / file_name ).as_posix() live_path = manager.resolve_storage_path(live_key) if live_path.exists(): return live_path.read_bytes() return AgentAssetSpreadsheetManager.build_blank_rule_workbook(fallback_sheet_name)