perf(db): batch skill-words + reports queries for scoreCandidate (P1-18)
This commit is contained in:
1 parent
bb397dff02
commit
3deb1c00ac
1 file changed
+47
-35
@@ -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<string, string[]> {
|
||||
const result = new Map<string, string[]>();
|
||||
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<string, string[]>();
|
||||
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<string> {
|
||||
const result = new Set<string>();
|
||||
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 &&
|
||||
|
||||
Reference in new issue
Block a user