refactor(api): extract assistant dashboard insights slice
This commit is contained in:
@@ -39,12 +39,13 @@
|
|||||||
- the demand, staffing-suggestion, capacity, and resource-availability assistant helpers now live in their own domain module, keeping staffing orchestration out of the monolithic assistant router without changing the assistant contract
|
- the demand, staffing-suggestion, capacity, and resource-availability assistant helpers now live in their own domain module, keeping staffing orchestration out of the monolithic assistant router without changing the assistant contract
|
||||||
- the resource search, detail, and lifecycle assistant helpers now live in their own domain module, keeping resource CRUD orchestration out of the monolithic assistant router without changing the assistant contract
|
- the resource search, detail, and lifecycle assistant helpers now live in their own domain module, keeping resource CRUD orchestration out of the monolithic assistant router without changing the assistant contract
|
||||||
- the blueprint and rate-card read helpers now live in their own domain module, keeping reference-data and pricing lookups out of the monolithic assistant router without changing the assistant contract
|
- the blueprint and rate-card read helpers now live in their own domain module, keeping reference-data and pricing lookups out of the monolithic assistant router without changing the assistant contract
|
||||||
|
- the dashboard detail, insight summary, anomaly detection, and dynamic report read helpers now live in their own domain module, keeping controller-side analytics reads out of the monolithic assistant router without changing the assistant contract
|
||||||
|
|
||||||
## Next Up
|
## Next Up
|
||||||
|
|
||||||
Pin the next structural cleanup on the API side:
|
Pin the next structural cleanup on the API side:
|
||||||
continue splitting `packages/api/src/router/assistant-tools.ts` into domain-oriented tool modules without changing the public tool contract.
|
continue splitting `packages/api/src/router/assistant-tools.ts` into domain-oriented tool modules without changing the public tool contract.
|
||||||
The next clean slice should stay adjacent to the extracted domains and target one cohesive read-model block such as dashboard/insight/report helpers or another tightly bound cluster still in the monolithic router.
|
The next clean slice should stay adjacent to the extracted domains and target one cohesive collaboration or analytics block such as comments/audit helpers or scenario/narrative/rate-analysis helpers still living in the monolithic router.
|
||||||
|
|
||||||
## Remaining Major Themes
|
## Remaining Major Themes
|
||||||
|
|
||||||
|
|||||||
@@ -125,6 +125,10 @@ import {
|
|||||||
blueprintsRateCardsToolDefinitions,
|
blueprintsRateCardsToolDefinitions,
|
||||||
createBlueprintsRateCardsExecutors,
|
createBlueprintsRateCardsExecutors,
|
||||||
} from "./assistant-tools/blueprints-rate-cards.js";
|
} from "./assistant-tools/blueprints-rate-cards.js";
|
||||||
|
import {
|
||||||
|
createDashboardInsightsReportsExecutors,
|
||||||
|
dashboardInsightsReportsToolDefinitions,
|
||||||
|
} from "./assistant-tools/dashboard-insights-reports.js";
|
||||||
import {
|
import {
|
||||||
withToolAccess,
|
withToolAccess,
|
||||||
type ToolAccessRequirements,
|
type ToolAccessRequirements,
|
||||||
@@ -413,16 +417,9 @@ const LEGACY_MONOLITHIC_TOOL_ACCESS: Partial<Record<string, ToolAccessRequiremen
|
|||||||
get_entitlement_summary: { allowedSystemRoles: [...MANAGER_ASSISTANT_ROLES] },
|
get_entitlement_summary: { allowedSystemRoles: [...MANAGER_ASSISTANT_ROLES] },
|
||||||
set_entitlement: { allowedSystemRoles: [...MANAGER_ASSISTANT_ROLES] },
|
set_entitlement: { allowedSystemRoles: [...MANAGER_ASSISTANT_ROLES] },
|
||||||
get_country: { requiresResourceOverview: true },
|
get_country: { requiresResourceOverview: true },
|
||||||
get_dashboard_detail: { allowedSystemRoles: [...CONTROLLER_ASSISTANT_ROLES] },
|
|
||||||
delete_project: { requiredPermissions: [PermissionKey.MANAGE_PROJECTS] },
|
delete_project: { requiredPermissions: [PermissionKey.MANAGE_PROJECTS] },
|
||||||
generate_project_cover: { requiredPermissions: [PermissionKey.MANAGE_PROJECTS] },
|
generate_project_cover: { requiredPermissions: [PermissionKey.MANAGE_PROJECTS] },
|
||||||
remove_project_cover: { requiredPermissions: [PermissionKey.MANAGE_PROJECTS] },
|
remove_project_cover: { requiredPermissions: [PermissionKey.MANAGE_PROJECTS] },
|
||||||
detect_anomalies: { allowedSystemRoles: [...CONTROLLER_ASSISTANT_ROLES] },
|
|
||||||
get_skill_gaps: { allowedSystemRoles: [...CONTROLLER_ASSISTANT_ROLES] },
|
|
||||||
get_project_health: { allowedSystemRoles: [...CONTROLLER_ASSISTANT_ROLES] },
|
|
||||||
get_budget_forecast: { allowedSystemRoles: [...CONTROLLER_ASSISTANT_ROLES] },
|
|
||||||
get_insights_summary: { allowedSystemRoles: [...CONTROLLER_ASSISTANT_ROLES] },
|
|
||||||
run_report: { allowedSystemRoles: [...CONTROLLER_ASSISTANT_ROLES] },
|
|
||||||
lookup_rate: { allowedSystemRoles: [...CONTROLLER_ASSISTANT_ROLES] },
|
lookup_rate: { allowedSystemRoles: [...CONTROLLER_ASSISTANT_ROLES] },
|
||||||
simulate_scenario: { allowedSystemRoles: [...CONTROLLER_ASSISTANT_ROLES] },
|
simulate_scenario: { allowedSystemRoles: [...CONTROLLER_ASSISTANT_ROLES] },
|
||||||
generate_project_narrative: { allowedSystemRoles: [...CONTROLLER_ASSISTANT_ROLES] },
|
generate_project_narrative: { allowedSystemRoles: [...CONTROLLER_ASSISTANT_ROLES] },
|
||||||
@@ -2315,24 +2312,7 @@ export const TOOL_DEFINITIONS: ToolDef[] = withToolAccess([
|
|||||||
...userAdminToolDefinitions,
|
...userAdminToolDefinitions,
|
||||||
...userSelfServiceToolDefinitions,
|
...userSelfServiceToolDefinitions,
|
||||||
...notificationInboxToolDefinitions,
|
...notificationInboxToolDefinitions,
|
||||||
|
...dashboardInsightsReportsToolDefinitions,
|
||||||
// ── DASHBOARD DETAIL ──
|
|
||||||
{
|
|
||||||
type: "function",
|
|
||||||
function: {
|
|
||||||
name: "get_dashboard_detail",
|
|
||||||
description: "Get detailed dashboard data: peak allocation times, top-value resources, demand pipeline, chargeability overview.",
|
|
||||||
parameters: {
|
|
||||||
type: "object",
|
|
||||||
properties: {
|
|
||||||
section: {
|
|
||||||
type: "string",
|
|
||||||
description: "Which section: peak_times, top_resources, demand_pipeline, chargeability_overview, or all",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
// ── ORG UNIT MANAGEMENT ──
|
// ── ORG UNIT MANAGEMENT ──
|
||||||
...orgUnitMutationToolDefinitions,
|
...orgUnitMutationToolDefinitions,
|
||||||
@@ -2340,103 +2320,6 @@ export const TOOL_DEFINITIONS: ToolDef[] = withToolAccess([
|
|||||||
// ── TASK MANAGEMENT ──
|
// ── TASK MANAGEMENT ──
|
||||||
...notificationTaskToolDefinitions,
|
...notificationTaskToolDefinitions,
|
||||||
|
|
||||||
// ── INSIGHTS & ANOMALIES ──
|
|
||||||
{
|
|
||||||
type: "function",
|
|
||||||
function: {
|
|
||||||
name: "detect_anomalies",
|
|
||||||
description: "Detect anomalies across all active projects: budget burn rate issues, staffing gaps, utilization outliers, and timeline overruns.",
|
|
||||||
parameters: { type: "object", properties: {} },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "function",
|
|
||||||
function: {
|
|
||||||
name: "get_skill_gaps",
|
|
||||||
description: "Analyze skill supply vs demand across all active projects. Returns which skills are in short supply relative to demand requirements.",
|
|
||||||
parameters: { type: "object", properties: {} },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "function",
|
|
||||||
function: {
|
|
||||||
name: "get_project_health",
|
|
||||||
description: "Get health scores for all active projects based on budget utilization, staffing completeness, and timeline status.",
|
|
||||||
parameters: { type: "object", properties: {} },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "function",
|
|
||||||
function: {
|
|
||||||
name: "get_budget_forecast",
|
|
||||||
description: "Get budget utilization and burn rate per active project. Shows total budget, spent, remaining, and whether burn is ahead or behind schedule.",
|
|
||||||
parameters: { type: "object", properties: {} },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "function",
|
|
||||||
function: {
|
|
||||||
name: "get_insights_summary",
|
|
||||||
description: "Get a summary of anomaly counts by category (budget, staffing, timeline, utilization) plus critical count.",
|
|
||||||
parameters: { type: "object", properties: {} },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
// ── REPORTS & COMMENTS ──
|
|
||||||
{
|
|
||||||
type: "function",
|
|
||||||
function: {
|
|
||||||
name: "run_report",
|
|
||||||
description: "Run a dynamic report query on resources, projects, assignments, or resource-month rows with flexible column selection and filtering.",
|
|
||||||
parameters: {
|
|
||||||
type: "object",
|
|
||||||
properties: {
|
|
||||||
entity: {
|
|
||||||
type: "string",
|
|
||||||
enum: ["resource", "project", "assignment", "resource_month"],
|
|
||||||
description: "Entity type to query",
|
|
||||||
},
|
|
||||||
columns: {
|
|
||||||
type: "array",
|
|
||||||
items: { type: "string" },
|
|
||||||
description: "Column keys to include (e.g. 'displayName', 'chapter', 'country.name')",
|
|
||||||
},
|
|
||||||
filters: {
|
|
||||||
type: "array",
|
|
||||||
items: {
|
|
||||||
type: "object",
|
|
||||||
properties: {
|
|
||||||
field: { type: "string", description: "Field to filter on" },
|
|
||||||
op: { type: "string", enum: ["eq", "neq", "gt", "lt", "gte", "lte", "contains", "in"], description: "Filter operator" },
|
|
||||||
value: { type: "string", description: "Filter value (string)" },
|
|
||||||
},
|
|
||||||
required: ["field", "op", "value"],
|
|
||||||
},
|
|
||||||
description: "Filters to apply",
|
|
||||||
},
|
|
||||||
periodMonth: {
|
|
||||||
type: "string",
|
|
||||||
description: "Required for resource_month reports. Format: YYYY-MM",
|
|
||||||
},
|
|
||||||
groupBy: {
|
|
||||||
type: "string",
|
|
||||||
description: "Optional scalar field used to group result rows into labeled sections.",
|
|
||||||
},
|
|
||||||
sortBy: {
|
|
||||||
type: "string",
|
|
||||||
description: "Optional scalar field used to sort rows within the grouped result.",
|
|
||||||
},
|
|
||||||
sortDir: {
|
|
||||||
type: "string",
|
|
||||||
enum: ["asc", "desc"],
|
|
||||||
description: "Sort direction for sortBy. Default: asc",
|
|
||||||
},
|
|
||||||
limit: { type: "integer", description: "Max results. Default: 50" },
|
|
||||||
},
|
|
||||||
required: ["entity", "columns"],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
type: "function",
|
type: "function",
|
||||||
function: {
|
function: {
|
||||||
@@ -2971,6 +2854,13 @@ const executors = {
|
|||||||
parseOptionalIsoDate,
|
parseOptionalIsoDate,
|
||||||
fmtDate,
|
fmtDate,
|
||||||
}),
|
}),
|
||||||
|
...createDashboardInsightsReportsExecutors({
|
||||||
|
assertPermission,
|
||||||
|
createDashboardCaller,
|
||||||
|
createInsightsCaller,
|
||||||
|
createReportCaller,
|
||||||
|
createScopedCallerContext,
|
||||||
|
}),
|
||||||
|
|
||||||
async search_estimates(params: {
|
async search_estimates(params: {
|
||||||
projectCode?: string; query?: string; status?: string; limit?: number;
|
projectCode?: string; query?: string; status?: string; limit?: number;
|
||||||
@@ -3434,86 +3324,6 @@ const executors = {
|
|||||||
toAssistantNotificationCreationError,
|
toAssistantNotificationCreationError,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
// ── DASHBOARD DETAIL ──
|
|
||||||
|
|
||||||
async get_dashboard_detail(params: { section?: string }, ctx: ToolContext) {
|
|
||||||
const caller = createDashboardCaller(createScopedCallerContext(ctx));
|
|
||||||
return caller.getDetail({ ...(params.section ? { section: params.section } : {}) });
|
|
||||||
},
|
|
||||||
|
|
||||||
// ── ORG UNIT MANAGEMENT ──
|
|
||||||
// ── INSIGHTS & ANOMALIES ──────────────────────────────────────────────────
|
|
||||||
|
|
||||||
async detect_anomalies(_params: Record<string, never>, ctx: ToolContext) {
|
|
||||||
const caller = createInsightsCaller(createScopedCallerContext(ctx));
|
|
||||||
return caller.getAnomalyDetail();
|
|
||||||
},
|
|
||||||
|
|
||||||
async get_skill_gaps(_params: Record<string, never>, ctx: ToolContext) {
|
|
||||||
const caller = createDashboardCaller(createScopedCallerContext(ctx));
|
|
||||||
return caller.getSkillGapSummary();
|
|
||||||
},
|
|
||||||
|
|
||||||
async get_project_health(_params: Record<string, never>, ctx: ToolContext) {
|
|
||||||
const caller = createDashboardCaller(createScopedCallerContext(ctx));
|
|
||||||
return caller.getProjectHealthDetail();
|
|
||||||
},
|
|
||||||
|
|
||||||
async get_budget_forecast(_params: Record<string, never>, ctx: ToolContext) {
|
|
||||||
assertPermission(ctx, "viewCosts" as PermissionKey);
|
|
||||||
|
|
||||||
const caller = createDashboardCaller(createScopedCallerContext(ctx));
|
|
||||||
return caller.getBudgetForecastDetail();
|
|
||||||
},
|
|
||||||
|
|
||||||
async get_insights_summary(_params: Record<string, never>, ctx: ToolContext) {
|
|
||||||
const caller = createInsightsCaller(createScopedCallerContext(ctx));
|
|
||||||
return caller.getInsightsSummary();
|
|
||||||
},
|
|
||||||
|
|
||||||
async run_report(params: {
|
|
||||||
entity: string;
|
|
||||||
columns: string[];
|
|
||||||
filters?: Array<{
|
|
||||||
field: string;
|
|
||||||
op: "eq" | "neq" | "gt" | "lt" | "gte" | "lte" | "contains" | "in";
|
|
||||||
value: string;
|
|
||||||
}>;
|
|
||||||
periodMonth?: string;
|
|
||||||
groupBy?: string;
|
|
||||||
sortBy?: string;
|
|
||||||
sortDir?: "asc" | "desc";
|
|
||||||
limit?: number;
|
|
||||||
}, ctx: ToolContext) {
|
|
||||||
const entity = params.entity as "resource" | "project" | "assignment" | "resource_month";
|
|
||||||
if (!["resource", "project", "assignment", "resource_month"].includes(entity)) {
|
|
||||||
return {
|
|
||||||
error:
|
|
||||||
`Unknown entity: ${params.entity}. Use resource, project, assignment, or resource_month.`,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
const caller = createReportCaller(createScopedCallerContext(ctx));
|
|
||||||
const result = await caller.getReportData({
|
|
||||||
entity,
|
|
||||||
columns: params.columns,
|
|
||||||
filters: params.filters ?? [],
|
|
||||||
periodMonth: params.periodMonth,
|
|
||||||
groupBy: params.groupBy,
|
|
||||||
sortBy: params.sortBy,
|
|
||||||
sortDir: params.sortDir ?? "asc",
|
|
||||||
limit: Math.min(params.limit ?? 50, 200),
|
|
||||||
offset: 0,
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
rows: result.rows,
|
|
||||||
rowCount: result.rows.length,
|
|
||||||
totalCount: result.totalCount,
|
|
||||||
columns: result.columns,
|
|
||||||
groups: result.groups,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
async list_comments(params: { entityType: CommentEntityType; entityId: string }, ctx: ToolContext) {
|
async list_comments(params: { entityType: CommentEntityType; entityId: string }, ctx: ToolContext) {
|
||||||
const caller = createCommentCaller(createScopedCallerContext(ctx));
|
const caller = createCommentCaller(createScopedCallerContext(ctx));
|
||||||
const comments = await caller.list({
|
const comments = await caller.list({
|
||||||
|
|||||||
@@ -0,0 +1,261 @@
|
|||||||
|
import { PermissionKey, SystemRole } from "@capakraken/shared";
|
||||||
|
import type { TRPCContext } from "../../trpc.js";
|
||||||
|
import { withToolAccess, type ToolContext, type ToolDef, type ToolExecutor } from "./shared.js";
|
||||||
|
|
||||||
|
type ReportEntity = "resource" | "project" | "assignment" | "resource_month";
|
||||||
|
type ReportFilterOperator = "eq" | "neq" | "gt" | "lt" | "gte" | "lte" | "contains" | "in";
|
||||||
|
|
||||||
|
type ReportFilter = {
|
||||||
|
field: string;
|
||||||
|
op: ReportFilterOperator;
|
||||||
|
value: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type ReportQueryResult = {
|
||||||
|
rows: unknown[];
|
||||||
|
totalCount: number;
|
||||||
|
columns: unknown;
|
||||||
|
groups: unknown;
|
||||||
|
};
|
||||||
|
|
||||||
|
type DashboardInsightsReportsDeps = {
|
||||||
|
assertPermission: (ctx: ToolContext, perm: PermissionKey) => void;
|
||||||
|
createDashboardCaller: (ctx: TRPCContext) => {
|
||||||
|
getDetail: (params: { section?: string }) => Promise<unknown>;
|
||||||
|
getSkillGapSummary: () => Promise<unknown>;
|
||||||
|
getProjectHealthDetail: () => Promise<unknown>;
|
||||||
|
getBudgetForecastDetail: () => Promise<unknown>;
|
||||||
|
};
|
||||||
|
createInsightsCaller: (ctx: TRPCContext) => {
|
||||||
|
getAnomalyDetail: () => Promise<unknown>;
|
||||||
|
getInsightsSummary: () => Promise<unknown>;
|
||||||
|
};
|
||||||
|
createReportCaller: (ctx: TRPCContext) => {
|
||||||
|
getReportData: (params: {
|
||||||
|
entity: ReportEntity;
|
||||||
|
columns: string[];
|
||||||
|
filters: ReportFilter[];
|
||||||
|
periodMonth?: string;
|
||||||
|
groupBy?: string;
|
||||||
|
sortBy?: string;
|
||||||
|
sortDir: "asc" | "desc";
|
||||||
|
limit: number;
|
||||||
|
offset: number;
|
||||||
|
}) => Promise<ReportQueryResult>;
|
||||||
|
};
|
||||||
|
createScopedCallerContext: (ctx: ToolContext) => TRPCContext;
|
||||||
|
};
|
||||||
|
|
||||||
|
const REPORT_ENTITIES: ReportEntity[] = ["resource", "project", "assignment", "resource_month"];
|
||||||
|
|
||||||
|
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.",
|
||||||
|
parameters: {
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
section: {
|
||||||
|
type: "string",
|
||||||
|
description: "Which section: peak_times, top_resources, demand_pipeline, chargeability_overview, or all",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "function",
|
||||||
|
function: {
|
||||||
|
name: "detect_anomalies",
|
||||||
|
description: "Detect anomalies across all active projects: budget burn rate issues, staffing gaps, utilization outliers, and timeline overruns.",
|
||||||
|
parameters: { type: "object", properties: {} },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "function",
|
||||||
|
function: {
|
||||||
|
name: "get_skill_gaps",
|
||||||
|
description: "Analyze skill supply vs demand across all active projects. Returns which skills are in short supply relative to demand requirements.",
|
||||||
|
parameters: { type: "object", properties: {} },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "function",
|
||||||
|
function: {
|
||||||
|
name: "get_project_health",
|
||||||
|
description: "Get health scores for all active projects based on budget utilization, staffing completeness, and timeline status.",
|
||||||
|
parameters: { type: "object", properties: {} },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "function",
|
||||||
|
function: {
|
||||||
|
name: "get_budget_forecast",
|
||||||
|
description: "Get budget utilization and burn rate per active project. Shows total budget, spent, remaining, and whether burn is ahead or behind schedule.",
|
||||||
|
parameters: { type: "object", properties: {} },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "function",
|
||||||
|
function: {
|
||||||
|
name: "get_insights_summary",
|
||||||
|
description: "Get a summary of anomaly counts by category (budget, staffing, timeline, utilization) plus critical count.",
|
||||||
|
parameters: { type: "object", properties: {} },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "function",
|
||||||
|
function: {
|
||||||
|
name: "run_report",
|
||||||
|
description: "Run a dynamic report query on resources, projects, assignments, or resource-month rows with flexible column selection and filtering.",
|
||||||
|
parameters: {
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
entity: {
|
||||||
|
type: "string",
|
||||||
|
enum: ["resource", "project", "assignment", "resource_month"],
|
||||||
|
description: "Entity type to query",
|
||||||
|
},
|
||||||
|
columns: {
|
||||||
|
type: "array",
|
||||||
|
items: { type: "string" },
|
||||||
|
description: "Column keys to include (e.g. 'displayName', 'chapter', 'country.name')",
|
||||||
|
},
|
||||||
|
filters: {
|
||||||
|
type: "array",
|
||||||
|
items: {
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
field: { type: "string", description: "Field to filter on" },
|
||||||
|
op: { type: "string", enum: ["eq", "neq", "gt", "lt", "gte", "lte", "contains", "in"], description: "Filter operator" },
|
||||||
|
value: { type: "string", description: "Filter value (string)" },
|
||||||
|
},
|
||||||
|
required: ["field", "op", "value"],
|
||||||
|
},
|
||||||
|
description: "Filters to apply",
|
||||||
|
},
|
||||||
|
periodMonth: {
|
||||||
|
type: "string",
|
||||||
|
description: "Required for resource_month reports. Format: YYYY-MM",
|
||||||
|
},
|
||||||
|
groupBy: {
|
||||||
|
type: "string",
|
||||||
|
description: "Optional scalar field used to group result rows into labeled sections.",
|
||||||
|
},
|
||||||
|
sortBy: {
|
||||||
|
type: "string",
|
||||||
|
description: "Optional scalar field used to sort rows within the grouped result.",
|
||||||
|
},
|
||||||
|
sortDir: {
|
||||||
|
type: "string",
|
||||||
|
enum: ["asc", "desc"],
|
||||||
|
description: "Sort direction for sortBy. Default: asc",
|
||||||
|
},
|
||||||
|
limit: { type: "integer", description: "Max results. Default: 50" },
|
||||||
|
},
|
||||||
|
required: ["entity", "columns"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
], {
|
||||||
|
get_dashboard_detail: {
|
||||||
|
allowedSystemRoles: [SystemRole.ADMIN, SystemRole.MANAGER, SystemRole.CONTROLLER],
|
||||||
|
},
|
||||||
|
detect_anomalies: {
|
||||||
|
allowedSystemRoles: [SystemRole.ADMIN, SystemRole.MANAGER, SystemRole.CONTROLLER],
|
||||||
|
},
|
||||||
|
get_skill_gaps: {
|
||||||
|
allowedSystemRoles: [SystemRole.ADMIN, SystemRole.MANAGER, SystemRole.CONTROLLER],
|
||||||
|
},
|
||||||
|
get_project_health: {
|
||||||
|
allowedSystemRoles: [SystemRole.ADMIN, SystemRole.MANAGER, SystemRole.CONTROLLER],
|
||||||
|
},
|
||||||
|
get_budget_forecast: {
|
||||||
|
allowedSystemRoles: [SystemRole.ADMIN, SystemRole.MANAGER, SystemRole.CONTROLLER],
|
||||||
|
},
|
||||||
|
get_insights_summary: {
|
||||||
|
allowedSystemRoles: [SystemRole.ADMIN, SystemRole.MANAGER, SystemRole.CONTROLLER],
|
||||||
|
},
|
||||||
|
run_report: {
|
||||||
|
allowedSystemRoles: [SystemRole.ADMIN, SystemRole.MANAGER, SystemRole.CONTROLLER],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export function createDashboardInsightsReportsExecutors(
|
||||||
|
deps: DashboardInsightsReportsDeps,
|
||||||
|
): Record<string, ToolExecutor> {
|
||||||
|
return {
|
||||||
|
async get_dashboard_detail(params: { section?: string }, ctx: ToolContext) {
|
||||||
|
const caller = deps.createDashboardCaller(deps.createScopedCallerContext(ctx));
|
||||||
|
return caller.getDetail({ ...(params.section ? { section: params.section } : {}) });
|
||||||
|
},
|
||||||
|
|
||||||
|
async detect_anomalies(_params: Record<string, never>, ctx: ToolContext) {
|
||||||
|
const caller = deps.createInsightsCaller(deps.createScopedCallerContext(ctx));
|
||||||
|
return caller.getAnomalyDetail();
|
||||||
|
},
|
||||||
|
|
||||||
|
async get_skill_gaps(_params: Record<string, never>, ctx: ToolContext) {
|
||||||
|
const caller = deps.createDashboardCaller(deps.createScopedCallerContext(ctx));
|
||||||
|
return caller.getSkillGapSummary();
|
||||||
|
},
|
||||||
|
|
||||||
|
async get_project_health(_params: Record<string, never>, ctx: ToolContext) {
|
||||||
|
const caller = deps.createDashboardCaller(deps.createScopedCallerContext(ctx));
|
||||||
|
return caller.getProjectHealthDetail();
|
||||||
|
},
|
||||||
|
|
||||||
|
async get_budget_forecast(_params: Record<string, never>, ctx: ToolContext) {
|
||||||
|
deps.assertPermission(ctx, PermissionKey.VIEW_COSTS);
|
||||||
|
const caller = deps.createDashboardCaller(deps.createScopedCallerContext(ctx));
|
||||||
|
return caller.getBudgetForecastDetail();
|
||||||
|
},
|
||||||
|
|
||||||
|
async get_insights_summary(_params: Record<string, never>, ctx: ToolContext) {
|
||||||
|
const caller = deps.createInsightsCaller(deps.createScopedCallerContext(ctx));
|
||||||
|
return caller.getInsightsSummary();
|
||||||
|
},
|
||||||
|
|
||||||
|
async run_report(params: {
|
||||||
|
entity: string;
|
||||||
|
columns: string[];
|
||||||
|
filters?: ReportFilter[];
|
||||||
|
periodMonth?: string;
|
||||||
|
groupBy?: string;
|
||||||
|
sortBy?: string;
|
||||||
|
sortDir?: "asc" | "desc";
|
||||||
|
limit?: number;
|
||||||
|
}, ctx: ToolContext) {
|
||||||
|
const entity = params.entity as ReportEntity;
|
||||||
|
if (!REPORT_ENTITIES.includes(entity)) {
|
||||||
|
return {
|
||||||
|
error:
|
||||||
|
`Unknown entity: ${params.entity}. Use resource, project, assignment, or resource_month.`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const caller = deps.createReportCaller(deps.createScopedCallerContext(ctx));
|
||||||
|
const result = await caller.getReportData({
|
||||||
|
entity,
|
||||||
|
columns: params.columns,
|
||||||
|
filters: params.filters ?? [],
|
||||||
|
...(params.periodMonth !== undefined ? { periodMonth: params.periodMonth } : {}),
|
||||||
|
...(params.groupBy !== undefined ? { groupBy: params.groupBy } : {}),
|
||||||
|
...(params.sortBy !== undefined ? { sortBy: params.sortBy } : {}),
|
||||||
|
sortDir: params.sortDir ?? "asc",
|
||||||
|
limit: Math.min(params.limit ?? 50, 200),
|
||||||
|
offset: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
rows: result.rows,
|
||||||
|
rowCount: result.rows.length,
|
||||||
|
totalCount: result.totalCount,
|
||||||
|
columns: result.columns,
|
||||||
|
groups: result.groups,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user