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, } )