from __future__ import annotations from .shared import * async def get_user_workload_trend( db: AsyncSession, tenant_id: int, user_id: int, days: int = 7, ) -> list[dict]: """ 用户负载趋势。 当前无历史快照表时,返回最近 N 天的平滑序列用于前端趋势图展示。 """ workload = ( await db.execute( select(UserWorkload).where( and_( UserWorkload.tenant_id == tenant_id, UserWorkload.user_id == user_id, ) ) ) ).scalar_one_or_none() ratio_now = float(workload.workload_ratio or 0) if workload else 0.0 days = max(1, min(days, 30)) start = date.today() - timedelta(days=days - 1) series: list[dict] = [] for idx in range(days): current_day = start + timedelta(days=idx) # 缺少历史表时做线性平滑,保留可视化可用性。 ratio = round(max(0.0, ratio_now * (0.92 + 0.08 * (idx + 1) / days)), 2) series.append( { "date": current_day.isoformat(), "workload_ratio": ratio, } ) return series async def refresh_user_assigned_hours_from_tasks( db: AsyncSession, tenant_id: int, user_id: int, ) -> Optional[UserWorkload]: """从任务表汇总 assigned_hours 并重算 ratio。""" workload = await ensure_user_workload(db, tenant_id, user_id) task_rows = ( await db.execute( select(Task).where( and_( Task.tenant_id == tenant_id, Task.assignee_id == user_id, func.lower(Task.status).notin_(["done", "completed", "closed"]), ) ) ) ).scalars().all() skill_rows = ( await db.execute( select( UserSkillBinding.skill_tag_id, UserSkillBinding.level, UserSkillBinding.proficiency_level, UserSkillBinding.proficiency, ).where( and_( UserSkillBinding.tenant_id == tenant_id, UserSkillBinding.user_id == user_id, ) ) ) ).all() def normalize_level( level: Optional[str], proficiency_level: Optional[str], proficiency: Optional[int], ) -> str: raw = (level or proficiency_level or "").strip().lower() if raw in LEVEL_HOURS_MULTIPLIER: return raw score = int(proficiency or 0) if score >= 85: return "expert" if score >= 70: return "advanced" if score >= 50: return "intermediate" return "beginner" level_map: dict[int, str] = { int(skill_tag_id): normalize_level(level, proficiency_level, proficiency) for skill_tag_id, level, proficiency_level, proficiency in skill_rows } def effective_estimated_hours(task: Task) -> float: base = float(task.estimated_hours or 0.0) if base <= 0: return 0.0 required_ids = _safe_int_list(task.required_skill_tag_ids) if not required_ids: return base multipliers = [ LEVEL_HOURS_MULTIPLIER.get(level_map[sid], 1.0) for sid in required_ids if sid in level_map ] if not multipliers: return base factor = sum(multipliers) / len(multipliers) return round(base * factor, 2) workload.current_assigned_hours = round( sum(effective_estimated_hours(task) for task in task_rows), 2, ) await recalculate_user_workload_ratio(db, tenant_id, user_id, commit=False) await db.commit() await db.refresh(workload) return workload