From 3deb1c00acf1906ca8e4ee7e21ae79890addee11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=B7=E7=94=B5=E8=8A=BD=E8=A1=A3?= Date: Thu, 16 Apr 2026 22:53:39 -0400 Subject: [PATCH] perf(db): batch skill-words + reports queries for scoreCandidate (P1-18) --- src/main/database/task-assignment.ts | 82 ++++++++++++++++------------ 1 file changed, 47 insertions(+), 35 deletions(-) diff --git a/src/main/database/task-assignment.ts b/src/main/database/task-assignment.ts index f0dd69d1..88b09f63 100644 --- a/src/main/database/task-assignment.ts +++ b/src/main/database/task-assignment.ts @@ -38,51 +38,50 @@ function getProjectDepartment( return lead?.department ?? null; } -function getAgentSkillWords( - db: DatabaseSync, +function loadSkillWordsForAgents( readAll: ReadAll, companyId: string, - agentId: string, -): string[] { - const skills = readAll<{ name: string; description: string | null; slug: string }>( - `select cs.name, cs.description, cs.slug + agentIds: string[], +): Map { + const result = new Map(); + if (agentIds.length === 0) return result; + const placeholders = agentIds.map(() => "?").join(","); + const rows = readAll<{ agentId: string; name: string; description: string | null; slug: string }>( + `select asa.agent_id as agentId, cs.name, cs.description, cs.slug from company_skills cs join agent_skill_assignments asa on asa.skill_id = cs.id - where asa.agent_id = ? and cs.company_id = ?`, - agentId, + where cs.company_id = ? and asa.agent_id in (${placeholders})`, companyId, + ...agentIds, ); - return tokenize(skills.map((skill) => `${skill.name} ${skill.description ?? ""} ${skill.slug}`).join(" ")); + const grouped = new Map(); + for (const row of rows) { + const blob = `${row.name} ${row.description ?? ""} ${row.slug}`; + const existing = grouped.get(row.agentId); + if (existing) existing.push(blob); + else grouped.set(row.agentId, [blob]); + } + for (const agentId of agentIds) { + result.set(agentId, tokenize((grouped.get(agentId) ?? []).join(" "))); + } + return result; } -function scoreCandidate( - db: DatabaseSync, +function loadAgentsWithReports( readAll: ReadAll, companyId: string, - taskWords: string[], - projectDept: string | null, - candidate: AssignmentCandidate, -): number { - const capabilityWords = tokenize(candidate.capabilities); - const skillWords = getAgentSkillWords(db, readAll, companyId, candidate.id); - const capabilityOverlap = taskWords.filter((word) => capabilityWords.includes(word)).length; - const skillOverlap = taskWords.filter((word) => skillWords.includes(word)).length; - let score = capabilityOverlap * 10 + skillOverlap * 12 - candidate.taskCount * 5; - - if (projectDept && candidate.department === projectDept) { - score += 15; - } - - const hasReports = readAll<{ id: string }>( - "select id from agents where reports_to = ? and company_id = ? limit 1", - candidate.id, + agentIds: string[], +): Set { + const result = new Set(); + if (agentIds.length === 0) return result; + const placeholders = agentIds.map(() => "?").join(","); + const rows = readAll<{ reportsTo: string }>( + `select distinct reports_to as reportsTo from agents where company_id = ? and reports_to in (${placeholders})`, companyId, - ).length > 0; - if (!hasReports) { - score += 5; - } - - return score; + ...agentIds, + ); + for (const row of rows) result.add(row.reportsTo); + return result; } export function chooseBestTaskAssignee( @@ -105,12 +104,25 @@ export function chooseBestTaskAssignee( const taskWords = tokenize(`${task.title} ${task.description}`); const projectDept = getProjectDepartment(getDb(), companyId, task.projectId); + const eligibleIds = eligibleCandidates.map((candidate) => candidate.id); + const skillWordsByAgent = loadSkillWordsForAgents(readAll, companyId, eligibleIds); + const agentsWithReports = loadAgentsWithReports(readAll, companyId, eligibleIds); let bestAgentId: string | null = null; let bestScore = -Infinity; for (const candidate of eligibleCandidates) { - const score = scoreCandidate(getDb(), readAll, companyId, taskWords, projectDept, candidate); + const capabilityWords = tokenize(candidate.capabilities); + const skillWords = skillWordsByAgent.get(candidate.id) ?? []; + const capabilityOverlap = taskWords.filter((word) => capabilityWords.includes(word)).length; + const skillOverlap = taskWords.filter((word) => skillWords.includes(word)).length; + let score = capabilityOverlap * 10 + skillOverlap * 12 - candidate.taskCount * 5; + if (projectDept && candidate.department === projectDept) { + score += 15; + } + if (!agentsWithReports.has(candidate.id)) { + score += 5; + } if ( score > bestScore || (score === bestScore &&