from __future__ import annotations from typing import Annotated from fastapi import APIRouter, Depends, Query from sqlalchemy import func, or_, select from sqlalchemy.orm import Session from app.api.deps import CurrentUserContext, get_current_user, get_db from app.models.employee import Employee from app.schemas.employee_profile import EmployeeProfileLatestRead from app.services.account_behavior_profile import AccountBehaviorProfileService from app.services.employee_behavior_profile_service import EmployeeBehaviorProfileService router = APIRouter(prefix="/employee-profiles") DbSession = Annotated[Session, Depends(get_db)] CurrentUser = Annotated[CurrentUserContext, Depends(get_current_user)] @router.get( "/me/latest", response_model=EmployeeProfileLatestRead, summary="读取当前登录人的最新用户画像", description="按当前登录用户的邮箱或姓名匹配员工目录,返回个人工作台使用的综合用户画像。", ) def get_current_employee_latest_profile( db: DbSession, current_user: CurrentUser, scene: Annotated[str, Query(max_length=50)] = "operations", window_days: Annotated[int, Query(ge=1, le=365)] = 90, expense_type_scope: Annotated[str, Query(max_length=50)] = "overall", ) -> EmployeeProfileLatestRead: employee = _resolve_current_employee(db, current_user) if employee is None: return AccountBehaviorProfileService(db).get_latest_account_profile( account_id=current_user.username, account_name=current_user.name, identifiers=_current_account_identifiers(current_user), scene=scene, window_days=window_days, expense_type_scope=expense_type_scope, ) service = EmployeeBehaviorProfileService(db) latest = service.get_latest_profile( employee_id=employee.id, scene=scene, window_days=window_days, expense_type_scope=expense_type_scope, ) if latest.empty_reason or _missing_usage_duration_metric(latest): service.refresh_employee_profiles( employee_id=employee.id, window_days=(window_days,), expense_type_scope=expense_type_scope, source_task_type="workbench_on_demand", ) latest = service.get_latest_profile( employee_id=employee.id, scene=scene, window_days=window_days, expense_type_scope=expense_type_scope, ) return latest @router.get( "/{employee_id}/latest", response_model=EmployeeProfileLatestRead, summary="读取员工最新业务行为画像", description="返回员工在指定场景下的最新画像快照,审批场景默认只展示费用支出和流程质量画像。", ) def get_employee_latest_profile( employee_id: str, db: DbSession, current_user: CurrentUser, scene: Annotated[str, Query(max_length=50)] = "approval", claim_id: Annotated[str | None, Query(max_length=80)] = None, window_days: Annotated[int, Query(ge=1, le=365)] = 90, expense_type_scope: Annotated[str, Query(max_length=50)] = "overall", ) -> EmployeeProfileLatestRead: del current_user return EmployeeBehaviorProfileService(db).get_latest_profile( employee_id=employee_id, scene=scene, claim_id=claim_id, window_days=window_days, expense_type_scope=expense_type_scope, ) def _resolve_current_employee( db: Session, current_user: CurrentUserContext, ) -> Employee | None: identities = [ str(current_user.username or "").strip(), str(current_user.name or "").strip(), ] normalized = [item for item in dict.fromkeys(identities) if item] if not normalized: return None email_values = [item.lower() for item in normalized if "@" in item] exact_values = [item for item in normalized if "@" not in item] conditions = [] if email_values: conditions.append(func.lower(Employee.email).in_(email_values)) if exact_values: conditions.append(Employee.name.in_(exact_values)) conditions.append(Employee.employee_no.in_(exact_values)) if not conditions: return None stmt = select(Employee).where(or_(*conditions)).order_by(Employee.created_at.asc()).limit(1) return db.scalars(stmt).first() def _missing_usage_duration_metric(latest: EmployeeProfileLatestRead) -> bool: if latest.scene != "operations": return False for profile in latest.profiles: if profile.profile_type == "ai_usage": return "usage_duration_ms" not in profile.metrics return False def _current_account_identifiers(current_user: CurrentUserContext) -> set[str]: return { item for item in ( current_user.username, current_user.name, ) if str(item or "").strip() }