from __future__ import annotations from datetime import UTC, datetime from time import perf_counter from typing import Any from sqlalchemy.orm import Session from app.core.agent_enums import ( AgentName, AgentPermissionLevel, AgentRunSource, AgentRunStatus, AgentToolType, ) from app.services.agent_runs import AgentRunService from app.services.hermes_employee_profile_scanner import HermesEmployeeProfileScannerService EMPLOYEE_PROFILE_SCAN_TASK_TYPE = "employee_behavior_profile_scan" EMPLOYEE_PROFILE_SCAN_TOOL_NAME = "digital_employee.employee_behavior_profile.scan" class EmployeeProfileScanTaskService: def __init__(self, db: Session) -> None: self.db = db def refresh_profiles(self, *, source: str = AgentRunSource.SCHEDULE.value) -> dict[str, Any]: run_service = AgentRunService(self.db) run = run_service.create_run( agent=AgentName.HERMES.value, source=source, user_id="digital_employee", ontology_json={ "scenario": "employee_behavior_profile", "intent": "scan", }, route_json={ "task_type": EMPLOYEE_PROFILE_SCAN_TASK_TYPE, "job_type": EMPLOYEE_PROFILE_SCAN_TASK_TYPE, "selected_agent": AgentName.HERMES.value, "phase": "running", "heartbeat_at": datetime.now(UTC).isoformat(), }, permission_level=AgentPermissionLevel.READ.value, status=AgentRunStatus.RUNNING.value, ) timer = perf_counter() try: # 画像快照表的 source_task_log_id 外键指向 Hermes 任务日志。 # 这里用 agent_runs 记录数字员工轨迹,因此不写入该外键,避免错误关联。 summary = HermesEmployeeProfileScannerService(self.db).scan_employee_profiles( log_id=None ) duration_ms = int((perf_counter() - timer) * 1000) report = self._build_report(summary) response = { "task_type": EMPLOYEE_PROFILE_SCAN_TASK_TYPE, "summary": summary, "report": report, } run_service.record_tool_call( run_id=run.run_id, tool_type=AgentToolType.DATABASE.value, tool_name=EMPLOYEE_PROFILE_SCAN_TOOL_NAME, request_json={"task_type": EMPLOYEE_PROFILE_SCAN_TASK_TYPE}, response_json=response, status=AgentRunStatus.SUCCEEDED.value, duration_ms=duration_ms, ) run_service.merge_route_json( run.run_id, { "phase": "succeeded", "summary": summary, "report": report, "heartbeat_at": datetime.now(UTC).isoformat(), }, status=AgentRunStatus.SUCCEEDED.value, result_summary=( "员工行为画像已生成:" f"覆盖 {summary.get('target_employee_count', 0)} 人," f"快照 {summary.get('snapshot_count', 0)} 条," f"重点关注 {summary.get('high_attention_employee_count', 0)} 人。" ), finished_at=datetime.now(UTC), ) return response except Exception as exc: run_service.record_tool_call( run_id=run.run_id, tool_type=AgentToolType.DATABASE.value, tool_name=EMPLOYEE_PROFILE_SCAN_TOOL_NAME, request_json={"task_type": EMPLOYEE_PROFILE_SCAN_TASK_TYPE}, response_json={}, status=AgentRunStatus.FAILED.value, duration_ms=int((perf_counter() - timer) * 1000), error_message=str(exc), ) run_service.merge_route_json( run.run_id, { "phase": "failed", "heartbeat_at": datetime.now(UTC).isoformat(), }, status=AgentRunStatus.FAILED.value, error_message=str(exc), finished_at=datetime.now(UTC), ) raise @staticmethod def _build_report(summary: dict[str, Any]) -> dict[str, Any]: return { "title": "员工财务行为画像扫描报告", "targetEmployeeCount": int(summary.get("target_employee_count") or 0), "profileSnapshotCount": int(summary.get("snapshot_count") or 0), "highAttentionEmployeeCount": int( summary.get("high_attention_employee_count") or 0 ), "windowDays": list(summary.get("window_days") or []), "algorithmVersion": str(summary.get("algorithm_version") or ""), "baselineSummary": summary.get("baseline_summary") or {}, }