211 lines
6.0 KiB
Python
211 lines
6.0 KiB
Python
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
|
|
]
|
|
|