PMFlow/backend/app/api/sstc_api/tasks_recommend_optimize.py

131 lines
4.2 KiB
Python

from __future__ import annotations
from .shared import *
from ...services.sstc.entity_lookup import _ensure_project, _ensure_task
from ...services.sstc.governance import _require_manager
from ...services.sstc.signal_core import _parse_node_id, _parse_project_id
router = APIRouter(tags=["sstc"])
@router.post("/projects/{project_id}/tasks/{task_id}/recommend-assignee")
async def recommend_assignee_for_task(
project_id: str,
task_id: str,
permission_user: User = Depends(require_project_member_or_manager),
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_current_user),
):
_ = permission_user
pid = _parse_project_id(project_id)
tid = _parse_node_id(task_id, "task_id")
tenant_id = int(current_user.tenant_id)
await _ensure_project(SSTCService(db, tenant_id=tenant_id), pid)
task = await _ensure_task(db, tenant_id=tenant_id, project_id=pid, task_id=tid)
required_skills = list(task.required_skill_tag_ids or [])
recommendations = await recommend_personnel(
db=db,
tenant_id=tenant_id,
required_skills=required_skills,
task_description=task.description or task.title,
task_id=str(task.id),
limit=5,
)
project_user_ids_result = await db.execute(
select(UserProject.user_id).where(
and_(
UserProject.tenant_id == tenant_id,
UserProject.project_id == pid,
)
)
)
project_user_ids = {int(uid) for uid in project_user_ids_result.scalars().all()}
active_users_result = await db.execute(
select(User.id).where(
and_(
User.tenant_id == tenant_id,
User.status == "active",
User.is_active.is_(True),
)
)
)
active_user_ids = {int(uid) for uid in active_users_result.scalars().all()}
filtered = []
for item in recommendations.get("recommendations", []):
try:
uid = int(item.get("user_id"))
except (TypeError, ValueError):
continue
if uid in project_user_ids and uid in active_user_ids:
filtered.append(item)
recommendations["recommendations"] = filtered
recommendations["total_count"] = len(filtered)
return success_response(recommendations)
@router.post("/projects/{project_id}/tasks/optimize-workload")
async def optimize_task_workload(
project_id: str,
body: WorkloadOptimizeRequest = Body(default_factory=WorkloadOptimizeRequest),
permission_user: User = Depends(require_project_member_or_manager),
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_current_user),
):
_ = permission_user
_require_manager(current_user)
pid = _parse_project_id(project_id)
tenant_id = int(current_user.tenant_id)
await _ensure_project(SSTCService(db, tenant_id=tenant_id), pid)
proposals = await workload_service.build_ai_optimize_preview(
db,
tenant_id=tenant_id,
project_id=pid,
max_moves=body.max_moves,
unassigned_only=body.mode == "unassigned",
)
if not proposals:
return success_response(
{
"updated": 0,
"message": "no tasks to optimize",
"mode": body.mode,
}
)
optimize_result = await workload_service.apply_ai_optimize_plan(
db,
tenant_id=tenant_id,
proposals=proposals,
)
updated = int(optimize_result.get("applied_count") or 0)
affected_users = int(optimize_result.get("affected_users") or 0)
await log_operation(
db,
tenant_id=tenant_id,
user_id=int(current_user.id),
project_id=pid,
entity_type="task",
entity_id=str(pid),
action="optimize_workload",
after_data={
"updated": updated,
"affected_users": affected_users,
"proposals": len(proposals),
"mode": body.mode,
},
)
await db.commit()
return success_response(
{
"updated": updated,
"affected_users": affected_users,
"proposals": len(proposals),
"mode": body.mode,
}
)