from __future__ import annotations from .shared import * async def init_user_skill_bindings( db: AsyncSession, tenant_id: int, user_id: int, *, include_system_only: bool = True, commit: bool = False, ) -> int: """ 初始化用户技能绑定(空基线)。 为用户补齐未绑定的标签,默认 `beginner + proficiency=0`。 """ tags_query = select(SkillTag.id).where(SkillTag.tenant_id == tenant_id) if include_system_only: tags_query = tags_query.where(SkillTag.is_system == True) tag_rows = await db.execute(tags_query) tag_ids = [int(v) for v in tag_rows.scalars().all()] if not tag_ids: return 0 existing_rows = await db.execute( select(UserSkillBinding.skill_tag_id).where( and_( UserSkillBinding.tenant_id == tenant_id, UserSkillBinding.user_id == user_id, UserSkillBinding.skill_tag_id.in_(tag_ids), ) ) ) existing_ids = {int(v) for v in existing_rows.scalars().all()} created = 0 for skill_tag_id in tag_ids: if skill_tag_id in existing_ids: continue db.add( UserSkillBinding( tenant_id=tenant_id, user_id=user_id, skill_tag_id=skill_tag_id, proficiency_level="beginner", level="beginner", proficiency=0, quality_score=0.0, efficiency_score=0.0, ) ) created += 1 if created: await db.flush() if commit: await db.commit() return created async def batch_update_user_skill_bindings( db: AsyncSession, tenant_id: int, user_id: int, bindings: List[dict], ) -> List[UserSkillBinding]: """ 批量更新用户技能绑定。 若 skill_tag_id 已存在则更新,否则创建。 """ result_bindings = [] for item in bindings: skill_tag_id = item.get("skill_tag_id") if skill_tag_id is None: continue if isinstance(skill_tag_id, str): try: skill_tag_id = int(skill_tag_id) except ValueError: continue proficiency = item.get("proficiency", 50) level = item.get("level", "intermediate") years = item.get("years_experience") existing = await db.execute( select(UserSkillBinding).where( and_( UserSkillBinding.tenant_id == tenant_id, UserSkillBinding.user_id == user_id, UserSkillBinding.skill_tag_id == skill_tag_id, ) ) ) binding = existing.scalar_one_or_none() if binding: binding.proficiency = proficiency binding.level = level binding.years_experience = years binding.proficiency_level = level else: binding = UserSkillBinding( tenant_id=tenant_id, user_id=user_id, skill_tag_id=skill_tag_id, proficiency_level=level, proficiency=proficiency, level=level, years_experience=years, ) db.add(binding) result_bindings.append(binding) await db.commit() for b in result_bindings: await db.refresh(b) await AIPMOKernelService.invalidate_world_model_cache_for_user( db=db, tenant_id=tenant_id, user_id=user_id, ) return result_bindings async def get_user_skill_bindings( db: AsyncSession, tenant_id: int, user_id: int, ) -> List[UserSkillBinding]: """按用户查询技能绑定""" result = await db.execute( select(UserSkillBinding) .options(selectinload(UserSkillBinding.skill_tag)) .where( and_( UserSkillBinding.tenant_id == tenant_id, UserSkillBinding.user_id == user_id, ) ) .order_by(UserSkillBinding.proficiency.desc()) ) return list(result.scalars().all()) async def get_skill_tag_user_bindings( db: AsyncSession, tenant_id: int, skill_tag_id: int, ) -> List[UserSkillBinding]: """按技能标签查询用户绑定""" result = await db.execute( select(UserSkillBinding) .options( selectinload(UserSkillBinding.user), selectinload(UserSkillBinding.skill_tag), ) .where( and_( UserSkillBinding.tenant_id == tenant_id, UserSkillBinding.skill_tag_id == skill_tag_id, ) ) .order_by(UserSkillBinding.proficiency.desc()) ) return list(result.scalars().all()) async def get_user_skill_task_history( db: AsyncSession, tenant_id: int, user_id: int, skill_tag_id: int, limit: int = 50, ) -> List[dict]: """ 查询用户某技能下的历史任务。 口径: - assignee_id == user_id - required_skill_tag_ids 包含 skill_tag_id """ result = await db.execute( select(Task) .where( and_( Task.tenant_id == tenant_id, Task.assignee_id == user_id, Task.required_skill_tag_ids.contains([skill_tag_id]), ) ) .order_by(Task.updated_at.desc()) .limit(limit) ) filtered = list(result.scalars().all()) return [ { "id": str(t.id), "title": t.title, "description": t.description, "status": t.status, "priority": t.priority, "estimated_hours": float(t.estimated_hours) if t.estimated_hours is not None else None, "project_id": str(t.project_id) if t.project_id is not None else None, "truth_node_id": str(t.truth_node_id) if t.truth_node_id is not None else None, "updated_at": t.updated_at.isoformat() if t.updated_at else None, } for t in filtered ]