from __future__ import annotations from sqlalchemy import or_ from .shared import * async def update_user_workload( db: AsyncSession, tenant_id: int, user_id: int, total_tasks_delta: int = 0, completed_tasks_delta: int = 0, in_progress_delta: int = 0, estimated_hours_delta: float = 0, completed_hours_delta: float = 0, ) -> Optional[UserWorkload]: """ 更新用户工作量。 新 schema: current_assigned_hours, workload_ratio """ result = await db.execute( select(UserWorkload).where( and_( UserWorkload.tenant_id == tenant_id, UserWorkload.user_id == user_id, ) ) ) workload = result.scalar_one_or_none() if not workload: workload = UserWorkload( tenant_id=tenant_id, user_id=user_id, current_assigned_hours=0.0, max_available_hours_per_week=40.0, workload_ratio=0.0, ) db.add(workload) await db.flush() delta = estimated_hours_delta + completed_hours_delta workload.current_assigned_hours = max( 0, float(workload.current_assigned_hours or 0) + delta ) max_hours = float(workload.max_available_hours_per_week or 40) workload.workload_ratio = min( 100.0, (workload.current_assigned_hours / max_hours) * 100 ) await db.commit() await db.refresh(workload) user_result = await db.execute(select(User).where(User.id == user_id)) user = user_result.scalar_one_or_none() if user: user.workload = int(workload.workload_ratio) await db.commit() return workload async def recommend_personnel( db: AsyncSession, tenant_id: int, required_skills: List[str | int], task_description: Optional[str] = None, task_id: Optional[str] = None, limit: int = 5, ) -> dict: """ 根据所需技能推荐匹配人员。 返回匹配度评分、推荐理由等。 """ if not required_skills: return { "task_id": task_id, "task_description": task_description, "required_skills": [], "recommendations": [], "total_count": 0, } skill_ids: list[int] = [] skill_names: list[str] = [] for raw in required_skills: if isinstance(raw, bool): continue try: skill_id = int(str(raw).strip()) except (TypeError, ValueError): skill_id = 0 if skill_id > 0 and str(raw).strip() == str(skill_id): skill_ids.append(skill_id) continue text = str(raw or "").strip() if text: skill_names.append(text) conditions = [] if skill_ids: conditions.append(SkillTag.id.in_(skill_ids)) if skill_names: conditions.append(or_(SkillTag.slug.in_(skill_names), SkillTag.name.in_(skill_names))) if not conditions: return { "task_id": task_id, "task_description": task_description, "required_skills": list(required_skills), "recommendations": [], "total_count": 0, } skill_tags_result = await db.execute( select(SkillTag).where( and_( SkillTag.tenant_id == tenant_id, or_(*conditions), ) ) ) tags = skill_tags_result.scalars().all() tag_ids = [t.id for t in tags] if not tag_ids: return { "task_id": task_id, "task_description": task_description, "required_skills": required_skills, "recommendations": [], "total_count": 0, } # 查询拥有这些技能的用户及熟练度 bindings_result = await db.execute( select(UserSkillBinding, SkillTag, User) .join(SkillTag, UserSkillBinding.skill_tag_id == SkillTag.id) .join(User, UserSkillBinding.user_id == User.id) .where( and_( UserSkillBinding.tenant_id == tenant_id, UserSkillBinding.skill_tag_id.in_(tag_ids), User.status == UserStatus.ACTIVE, ) ) ) rows = bindings_result.all() # 按用户聚合 user_scores: dict = {} for b, st, u in rows: uid = str(u.id) if uid not in user_scores: user_scores[uid] = { "user": u, "skills": [], "proficiencies": [], "match_count": 0, } user_scores[uid]["skills"].append(st.name) user_scores[uid]["proficiencies"].append(b.proficiency) user_scores[uid]["match_count"] = len(user_scores[uid]["skills"]) # 计算匹配度并排序 recommendations = [] for uid, data in user_scores.items(): u = data["user"] avg_prof = ( sum(data["proficiencies"]) / len(data["proficiencies"]) if data["proficiencies"] else 0 ) match_ratio = len(data["skills"]) / len(required_skills) if required_skills else 0 match_score = min(100, (match_ratio * 60 + avg_prof * 0.4)) reason = f"匹配技能: {', '.join(data['skills'])}" recommendations.append( { "user_id": uid, "user_name": u.name, "match_score": round(match_score, 1), "reason": reason, "skill_match_count": data["match_count"], "avg_proficiency": round(avg_prof, 1), "workload_rate": float(u.workload) if u.workload else 0, "recommended_role": u.role.value if hasattr(u.role, "value") else str(u.role), } ) recommendations.sort(key=lambda x: (-x["match_score"], -x["skill_match_count"])) recommendations = recommendations[:limit] return { "task_id": task_id, "task_description": task_description, "required_skills": required_skills, "recommendations": recommendations, "total_count": len(recommendations), }