2026-05-28 12:09:49 +08:00
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
from typing import Annotated
|
|
|
|
|
|
|
|
|
|
from fastapi import APIRouter, Depends, Query
|
2026-05-28 16:24:59 +08:00
|
|
|
from sqlalchemy import func, or_, select
|
2026-05-28 12:09:49 +08:00
|
|
|
from sqlalchemy.orm import Session
|
|
|
|
|
|
|
|
|
|
from app.api.deps import CurrentUserContext, get_current_user, get_db
|
2026-05-28 16:24:59 +08:00
|
|
|
from app.models.employee import Employee
|
2026-05-28 12:09:49 +08:00
|
|
|
from app.schemas.employee_profile import EmployeeProfileLatestRead
|
|
|
|
|
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)]
|
|
|
|
|
|
|
|
|
|
|
2026-05-28 16:24:59 +08:00
|
|
|
@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 EmployeeProfileLatestRead(
|
|
|
|
|
employee_id=current_user.username,
|
|
|
|
|
employee_name=current_user.name,
|
|
|
|
|
scene=scene,
|
|
|
|
|
window_days=window_days,
|
|
|
|
|
expense_type_scope=expense_type_scope,
|
|
|
|
|
empty_reason="当前登录用户未匹配到员工目录,暂无法形成用户画像。",
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
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:
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
|
2026-05-28 12:09:49 +08:00
|
|
|
@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,
|
|
|
|
|
)
|
2026-05-28 16:24:59 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
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()
|