PMFlow/backend/app/services/workload/board_queries_b.py

128 lines
3.8 KiB
Python

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