"""Data loading helpers for signal planner service.""" from __future__ import annotations from .shared import ( Any, AsyncSession, Optional, Project, Signal, SkillTag, User, UserProject, UserSkillBinding, UserWorkload, and_, select, ) class SignalPlannerLoadersMixin: """Load signal/project/member contexts for planner prompt.""" @classmethod async def _load_signal( cls, db: AsyncSession, *, tenant_id: int, signal_id: int, ) -> Signal | None: return ( await db.execute( select(Signal) .where( and_( Signal.tenant_id == int(tenant_id), Signal.id == int(signal_id), ) ) .limit(1) ) ).scalar_one_or_none() @classmethod async def _load_project_title( cls, db: AsyncSession, *, tenant_id: int, project_id: Optional[int], ) -> str: if project_id is None: return "" row = ( await db.execute( select(Project.title) .where( and_( Project.tenant_id == int(tenant_id), Project.id == int(project_id), ) ) .limit(1) ) ).scalar_one_or_none() return str(row or "") @classmethod async def _load_recent_signal_summaries( cls, db: AsyncSession, *, tenant_id: int, project_id: Optional[int], exclude_signal_id: int, ) -> list[dict[str, Any]]: if project_id is None: return [] rows = ( await db.execute( select(Signal.id, Signal.title, Signal.summary, Signal.priority) .where( and_( Signal.tenant_id == int(tenant_id), Signal.matched_project_id == int(project_id), Signal.id != int(exclude_signal_id), ) ) .order_by(Signal.created_at.desc()) .limit(8) ) ).all() items: list[dict[str, Any]] = [] for row in rows: items.append( { "id": int(row[0]), "title": str(row[1] or "")[:120], "summary": str(row[2] or "")[:180], "priority": str(row[3] or "")[:20], } ) return items @classmethod async def _load_member_profiles( cls, db: AsyncSession, *, tenant_id: int, project_id: Optional[int], ) -> list[dict[str, Any]]: if project_id is None: return [] rows = ( await db.execute( select( User.id, User.name, User.role, UserWorkload.workload_ratio, SkillTag.name, UserSkillBinding.proficiency, ) .join( UserProject, and_( UserProject.tenant_id == int(tenant_id), UserProject.user_id == User.id, UserProject.project_id == int(project_id), ), ) .join( UserWorkload, and_( UserWorkload.tenant_id == int(tenant_id), UserWorkload.user_id == User.id, ), isouter=True, ) .join( UserSkillBinding, and_( UserSkillBinding.tenant_id == int(tenant_id), UserSkillBinding.user_id == User.id, ), isouter=True, ) .join( SkillTag, and_( SkillTag.tenant_id == int(tenant_id), SkillTag.id == UserSkillBinding.skill_tag_id, ), isouter=True, ) .where( and_( User.tenant_id == int(tenant_id), User.is_active == True, ) ) .order_by(User.id.asc()) ) ).all() by_user: dict[int, dict[str, Any]] = {} for row in rows: user_id = cls._safe_int(row[0]) if user_id is None: continue profile = by_user.setdefault( int(user_id), { "user_id": int(user_id), "name": str(row[1] or "成员"), "role": str(row[2] or ""), "workload_ratio": float(row[3] or 0.0), "skills": [], }, ) skill_name = str(row[4] or "").strip() proficiency = cls._safe_int(row[5]) if skill_name: profile["skills"].append( { "name": skill_name, "proficiency": proficiency, } ) items = list(by_user.values()) for item in items: item["skills"] = item["skills"][:10] return items