feat: add relevanceScore to EntityCard and update entity extraction logic

This commit is contained in:
Leivur Djurhuus 2026-03-12 17:27:11 -05:00
parent 438b330b97
commit aa0bcc91d9
5 changed files with 30 additions and 5 deletions

View file

@ -18,6 +18,7 @@ export interface EntityCard {
projectName?: string;
projectId?: string;
link: string;
relevanceScore?: number;
}
/** Tools whose results may contain navigable entities */
@ -77,6 +78,7 @@ function extractEntities(toolName: string, data: any): EntityCard[] {
priority: item.priority,
code: item.projectCode || item.code,
link: `/projects/${item.id}`,
relevanceScore: item._relevanceScore,
});
} else if (itemType === "deliverable") {
const projId = item.projectId || item.project?.id;
@ -92,6 +94,7 @@ function extractEntities(toolName: string, data: any): EntityCard[] {
link: projId
? `/projects/${projId}/deliverables/${item.id}`
: `/projects`,
relevanceScore: item._relevanceScore,
});
} else if (itemType === "user") {
entities.push({
@ -100,6 +103,7 @@ function extractEntities(toolName: string, data: any): EntityCard[] {
name: item.name || "Unnamed User",
status: item.department || item.role || "",
link: `/workload`,
relevanceScore: item._relevanceScore,
});
} else if (
toolName === "list_overdue" ||
@ -174,6 +178,7 @@ function extractWorkloadEntities(data: {
status: assignment.stageStatus || "",
priority: assignment.priority,
link: `/projects/${assignment.projectId}`,
relevanceScore: 0.9,
});
}
}
@ -391,7 +396,7 @@ export async function POST(req: NextRequest) {
}
}
// Deduplicate, prioritize (projects/deliverables first, then users), cap at 10
// Deduplicate, prioritize by relevance score first, then type (projects/deliverables first, then users), cap at 10
const TYPE_PRIORITY: Record<string, number> = {
project: 0,
deliverable: 1,
@ -399,7 +404,14 @@ export async function POST(req: NextRequest) {
user: 3,
};
const entities = deduplicateEntities(allEntities)
.sort((a, b) => (TYPE_PRIORITY[a.type] ?? 9) - (TYPE_PRIORITY[b.type] ?? 9))
.sort((a, b) => {
const scoreA = a.relevanceScore ?? 0;
const scoreB = b.relevanceScore ?? 0;
if (scoreA !== scoreB) {
return scoreB - scoreA;
}
return (TYPE_PRIORITY[a.type] ?? 9) - (TYPE_PRIORITY[b.type] ?? 9);
})
.slice(0, 10);
// Send final response

View file

@ -27,6 +27,7 @@ export interface EntityCard {
projectName?: string;
projectId?: string;
link: string;
relevanceScore?: number;
}
export interface ChatMessage {

View file

@ -146,7 +146,7 @@ export const TOOL_DEFINITIONS: ToolDefinition[] = [
{
name: "get_workload",
description:
"Get workload data for all team members showing assignments per week, utilization, and capacity.",
"Get workload data for team members showing assignments per week, utilization, and capacity. Can be filtered by a specific user.",
input_schema: {
type: "object",
properties: {
@ -158,6 +158,10 @@ export const TOOL_DEFINITIONS: ToolDefinition[] = [
type: "string",
description: "Filter to a specific project (optional)",
},
userId: {
type: "string",
description: "Filter to a specific user (optional)",
},
},
},
},

View file

@ -243,6 +243,7 @@ export async function executeTool(
priority: p.priority,
dueDate: p.dueDate,
_matchedTerms: [] as string[],
_relevanceScore: 1.0,
});
}
projectMap.get(p.id)._matchedTerms.push(term);
@ -295,6 +296,7 @@ export async function executeTool(
subStatus: s.subStatus,
})),
_matchedTerms: [] as string[],
_relevanceScore: 1.0,
});
}
deliverableMap.get(d.id)._matchedTerms.push(term);
@ -335,6 +337,7 @@ export async function executeTool(
email: u.email,
role: u.role,
department: u.department,
_relevanceScore: 1.0,
});
}
}
@ -422,6 +425,7 @@ export async function executeTool(
data = await getWorkloadData(context.organizationId, {
numWeeks: input.numWeeks,
projectId: input.projectId,
userId: input.userId,
});
break;
}

View file

@ -95,14 +95,18 @@ export async function getWorkloadData(
numWeeks?: number;
projectId?: string;
stageType?: string;
userId?: string;
} = {}
): Promise<{ users: UserWorkload[]; weeks: WeekBucket[] }> {
const { numWeeks = 8, projectId, stageType } = options;
const { numWeeks = 8, projectId, stageType, userId } = options;
const weeks = generateWeekBuckets(numWeeks);
// Get all users in the organization with their assignments
const users = await prisma.user.findMany({
where: { organizationId },
where: {
organizationId,
...(userId ? { id: userId } : {}),
},
select: {
id: true,
name: true,