feat(api): include skill gaps in dashboard detail
This commit is contained in:
@@ -9,6 +9,7 @@ import {
|
||||
getDashboardOverview,
|
||||
getDashboardPeakTimes,
|
||||
getDashboardProjectHealth,
|
||||
getDashboardSkillGapSummary,
|
||||
getDashboardTopValueResources,
|
||||
} from "./assistant-tools-dashboard-test-helpers.js";
|
||||
|
||||
@@ -209,6 +210,21 @@ describe("assistant dashboard tools detail aggregation", () => {
|
||||
},
|
||||
},
|
||||
]);
|
||||
vi.mocked(getDashboardSkillGapSummary).mockResolvedValue({
|
||||
roleGaps: [
|
||||
{ role: "Pipeline TD", needed: 4, filled: 1, gap: 3, fillRate: 25 },
|
||||
{ role: "Lighting TD", needed: 2, filled: 1, gap: 1, fillRate: 50 },
|
||||
],
|
||||
totalOpenPositions: 4,
|
||||
skillSupplyTop10: [
|
||||
{ skill: "houdini", resourceCount: 5 },
|
||||
{ skill: "nuke", resourceCount: 4 },
|
||||
],
|
||||
resourcesByRole: [
|
||||
{ role: "Compositor", count: 6 },
|
||||
{ role: "Pipeline TD", count: 2 },
|
||||
],
|
||||
});
|
||||
|
||||
const ctx = createToolContext(
|
||||
{
|
||||
@@ -384,6 +400,21 @@ describe("assistant dashboard tools detail aggregation", () => {
|
||||
},
|
||||
},
|
||||
],
|
||||
skillGaps: {
|
||||
totalOpenPositions: 4,
|
||||
roleGaps: [
|
||||
{ role: "Pipeline TD", gap: 3, needed: 4, filled: 1, fillRate: 25 },
|
||||
{ role: "Lighting TD", gap: 1, needed: 2, filled: 1, fillRate: 50 },
|
||||
],
|
||||
topSkillsInSupply: [
|
||||
{ skill: "houdini", resourceCount: 5 },
|
||||
{ skill: "nuke", resourceCount: 4 },
|
||||
],
|
||||
resourcesByRole: [
|
||||
{ role: "Compositor", count: 6 },
|
||||
{ role: "Pipeline TD", count: 2 },
|
||||
],
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -34,6 +34,7 @@ import {
|
||||
getDashboardOverview,
|
||||
getDashboardPeakTimes,
|
||||
getDashboardProjectHealth,
|
||||
getDashboardSkillGapSummary,
|
||||
getDashboardTopValueResources,
|
||||
} from "@capakraken/application";
|
||||
import { cacheGet } from "../lib/cache.js";
|
||||
@@ -293,6 +294,21 @@ describe("dashboard procedure support", () => {
|
||||
},
|
||||
},
|
||||
]);
|
||||
vi.mocked(getDashboardSkillGapSummary).mockResolvedValue({
|
||||
roleGaps: [
|
||||
{ role: "Pipeline TD", needed: 4, filled: 1, gap: 3, fillRate: 25 },
|
||||
{ role: "Lighting TD", needed: 2, filled: 1, gap: 1, fillRate: 50 },
|
||||
],
|
||||
totalOpenPositions: 4,
|
||||
skillSupplyTop10: [
|
||||
{ skill: "houdini", resourceCount: 5 },
|
||||
{ skill: "nuke", resourceCount: 4 },
|
||||
],
|
||||
resourcesByRole: [
|
||||
{ role: "Compositor", count: 6 },
|
||||
{ role: "Pipeline TD", count: 2 },
|
||||
],
|
||||
});
|
||||
|
||||
try {
|
||||
const result = await getDashboardDetail(createContext(), { section: "all" });
|
||||
@@ -417,6 +433,21 @@ describe("dashboard procedure support", () => {
|
||||
},
|
||||
},
|
||||
],
|
||||
skillGaps: {
|
||||
totalOpenPositions: 4,
|
||||
roleGaps: [
|
||||
{ role: "Pipeline TD", gap: 3, needed: 4, filled: 1, fillRate: 25 },
|
||||
{ role: "Lighting TD", gap: 1, needed: 2, filled: 1, fillRate: 50 },
|
||||
],
|
||||
topSkillsInSupply: [
|
||||
{ skill: "houdini", resourceCount: 5 },
|
||||
{ skill: "nuke", resourceCount: 4 },
|
||||
],
|
||||
resourcesByRole: [
|
||||
{ role: "Compositor", count: 6 },
|
||||
{ role: "Pipeline TD", count: 2 },
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
expect(getDashboardChargeabilityOverview).toHaveBeenCalledWith(
|
||||
|
||||
@@ -11,6 +11,7 @@ vi.mock("@capakraken/application", async (importOriginal) => {
|
||||
getDashboardTopValueResources: vi.fn(),
|
||||
getDashboardChargeabilityOverview: vi.fn(),
|
||||
getDashboardBudgetForecast: vi.fn(),
|
||||
getDashboardSkillGapSummary: vi.fn(),
|
||||
getDashboardProjectHealth: vi.fn(),
|
||||
};
|
||||
});
|
||||
@@ -33,6 +34,7 @@ import {
|
||||
getDashboardChargeabilityOverview,
|
||||
getDashboardBudgetForecast,
|
||||
getDashboardProjectHealth,
|
||||
getDashboardSkillGapSummary,
|
||||
} from "@capakraken/application";
|
||||
import { dashboardRouter } from "../router/dashboard.js";
|
||||
import { createCallerFactory } from "../trpc.js";
|
||||
@@ -786,6 +788,21 @@ describe("dashboard router", () => {
|
||||
},
|
||||
},
|
||||
]);
|
||||
vi.mocked(getDashboardSkillGapSummary).mockResolvedValue({
|
||||
roleGaps: [
|
||||
{ role: "Pipeline TD", needed: 4, filled: 1, gap: 3, fillRate: 25 },
|
||||
{ role: "Lighting TD", needed: 2, filled: 1, gap: 1, fillRate: 50 },
|
||||
],
|
||||
totalOpenPositions: 4,
|
||||
skillSupplyTop10: [
|
||||
{ skill: "houdini", resourceCount: 5 },
|
||||
{ skill: "nuke", resourceCount: 4 },
|
||||
],
|
||||
resourcesByRole: [
|
||||
{ role: "Compositor", count: 6 },
|
||||
{ role: "Pipeline TD", count: 2 },
|
||||
],
|
||||
});
|
||||
vi.mocked(getDashboardTopValueResources).mockResolvedValue([
|
||||
{
|
||||
id: "res_1",
|
||||
@@ -1048,6 +1065,21 @@ describe("dashboard router", () => {
|
||||
},
|
||||
},
|
||||
],
|
||||
skillGaps: {
|
||||
totalOpenPositions: 4,
|
||||
roleGaps: [
|
||||
{ role: "Pipeline TD", gap: 3, needed: 4, filled: 1, fillRate: 25 },
|
||||
{ role: "Lighting TD", gap: 1, needed: 2, filled: 1, fillRate: 50 },
|
||||
],
|
||||
topSkillsInSupply: [
|
||||
{ skill: "houdini", resourceCount: 5 },
|
||||
{ skill: "nuke", resourceCount: 4 },
|
||||
],
|
||||
resourcesByRole: [
|
||||
{ role: "Compositor", count: 6 },
|
||||
{ role: "Pipeline TD", count: 2 },
|
||||
],
|
||||
},
|
||||
});
|
||||
expect(getDashboardPeakTimes).toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
@@ -1066,6 +1098,7 @@ describe("dashboard router", () => {
|
||||
watchlistThreshold: 15,
|
||||
}),
|
||||
);
|
||||
expect(getDashboardSkillGapSummary).toHaveBeenCalledWith(expect.anything());
|
||||
expect(getDashboardProjectHealth).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -54,13 +54,13 @@ export const dashboardInsightsReportsToolDefinitions: ToolDef[] = withToolAccess
|
||||
type: "function",
|
||||
function: {
|
||||
name: "get_dashboard_detail",
|
||||
description: "Get detailed dashboard data: peak allocation times, top-value resources, demand pipeline, chargeability overview, and project health risks.",
|
||||
description: "Get detailed dashboard data: peak allocation times, top-value resources, demand pipeline, chargeability overview, project health risks, and skill gap staffing pressure.",
|
||||
parameters: {
|
||||
type: "object",
|
||||
properties: {
|
||||
section: {
|
||||
type: "string",
|
||||
description: "Which section: peak_times, top_resources, demand_pipeline, chargeability_overview, project_health, or all",
|
||||
description: "Which section: peak_times, top_resources, demand_pipeline, chargeability_overview, project_health, skill_gaps, or all",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -477,6 +477,24 @@ export async function getDashboardDetail(ctx: DashboardProcedureContext, input:
|
||||
}));
|
||||
}
|
||||
|
||||
if (section === "all" || section === "skill_gaps") {
|
||||
const skillGapSummary = await getDashboardSkillGapSummaryRead(ctx);
|
||||
result.skillGaps = {
|
||||
totalOpenPositions: skillGapSummary.totalOpenPositions,
|
||||
roleGaps: skillGapSummary.roleGaps
|
||||
.slice(0, 10)
|
||||
.map((gap) => ({
|
||||
role: gap.role,
|
||||
gap: gap.gap,
|
||||
needed: gap.needed,
|
||||
filled: gap.filled,
|
||||
fillRate: gap.fillRate,
|
||||
})),
|
||||
topSkillsInSupply: skillGapSummary.skillSupplyTop10.slice(0, 5),
|
||||
resourcesByRole: skillGapSummary.resourcesByRole.slice(0, 5),
|
||||
};
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user