feat: 数字员工财务报告体系与定时提醒及看板快照调度
- 新增数字员工财务报告生成、邮件投递与渲染调度器 - 引入员工画像扫描调度与定时提醒任务 - 完善财务看板快照、排行口径与部门人员占比计算 - 优化数字员工工作看板仪表盘与技能目录 - 增强前端总览页图表、工作台摘要与顶部导航栏交互 - 新增差旅申请规划推动提醒与报销创建会话状态管理 - 补充财务报告、看板调度、数字员工工作记录测试覆盖
This commit is contained in:
123
server/src/app/services/employee_profile_scan_task.py
Normal file
123
server/src/app/services/employee_profile_scan_task.py
Normal file
@@ -0,0 +1,123 @@
|
||||
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 {},
|
||||
}
|
||||
Reference in New Issue
Block a user