feat(api): include skill gaps in dashboard detail
This commit is contained in:
@@ -9,6 +9,7 @@ import {
|
|||||||
getDashboardOverview,
|
getDashboardOverview,
|
||||||
getDashboardPeakTimes,
|
getDashboardPeakTimes,
|
||||||
getDashboardProjectHealth,
|
getDashboardProjectHealth,
|
||||||
|
getDashboardSkillGapSummary,
|
||||||
getDashboardTopValueResources,
|
getDashboardTopValueResources,
|
||||||
} from "./assistant-tools-dashboard-test-helpers.js";
|
} 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(
|
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,
|
getDashboardOverview,
|
||||||
getDashboardPeakTimes,
|
getDashboardPeakTimes,
|
||||||
getDashboardProjectHealth,
|
getDashboardProjectHealth,
|
||||||
|
getDashboardSkillGapSummary,
|
||||||
getDashboardTopValueResources,
|
getDashboardTopValueResources,
|
||||||
} from "@capakraken/application";
|
} from "@capakraken/application";
|
||||||
import { cacheGet } from "../lib/cache.js";
|
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 {
|
try {
|
||||||
const result = await getDashboardDetail(createContext(), { section: "all" });
|
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(
|
expect(getDashboardChargeabilityOverview).toHaveBeenCalledWith(
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ vi.mock("@capakraken/application", async (importOriginal) => {
|
|||||||
getDashboardTopValueResources: vi.fn(),
|
getDashboardTopValueResources: vi.fn(),
|
||||||
getDashboardChargeabilityOverview: vi.fn(),
|
getDashboardChargeabilityOverview: vi.fn(),
|
||||||
getDashboardBudgetForecast: vi.fn(),
|
getDashboardBudgetForecast: vi.fn(),
|
||||||
|
getDashboardSkillGapSummary: vi.fn(),
|
||||||
getDashboardProjectHealth: vi.fn(),
|
getDashboardProjectHealth: vi.fn(),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@@ -33,6 +34,7 @@ import {
|
|||||||
getDashboardChargeabilityOverview,
|
getDashboardChargeabilityOverview,
|
||||||
getDashboardBudgetForecast,
|
getDashboardBudgetForecast,
|
||||||
getDashboardProjectHealth,
|
getDashboardProjectHealth,
|
||||||
|
getDashboardSkillGapSummary,
|
||||||
} from "@capakraken/application";
|
} from "@capakraken/application";
|
||||||
import { dashboardRouter } from "../router/dashboard.js";
|
import { dashboardRouter } from "../router/dashboard.js";
|
||||||
import { createCallerFactory } from "../trpc.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([
|
vi.mocked(getDashboardTopValueResources).mockResolvedValue([
|
||||||
{
|
{
|
||||||
id: "res_1",
|
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(getDashboardPeakTimes).toHaveBeenCalledWith(
|
||||||
expect.anything(),
|
expect.anything(),
|
||||||
@@ -1066,6 +1098,7 @@ describe("dashboard router", () => {
|
|||||||
watchlistThreshold: 15,
|
watchlistThreshold: 15,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
expect(getDashboardSkillGapSummary).toHaveBeenCalledWith(expect.anything());
|
||||||
expect(getDashboardProjectHealth).toHaveBeenCalledTimes(1);
|
expect(getDashboardProjectHealth).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -54,13 +54,13 @@ export const dashboardInsightsReportsToolDefinitions: ToolDef[] = withToolAccess
|
|||||||
type: "function",
|
type: "function",
|
||||||
function: {
|
function: {
|
||||||
name: "get_dashboard_detail",
|
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: {
|
parameters: {
|
||||||
type: "object",
|
type: "object",
|
||||||
properties: {
|
properties: {
|
||||||
section: {
|
section: {
|
||||||
type: "string",
|
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;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user