import type { TRPCContext } from "../../trpc.js"; import { PermissionKey } from "@capakraken/shared"; import type { ToolContext, ToolDef, ToolExecutor } from "./shared.js"; type AssistantToolErrorResult = { error: string }; type ResolvedResource = { id: string; }; type ResolvedProject = { id: string; }; type ResourceComputationDomain = | "INPUT" | "SAH" | "ALLOCATION" | "RULES" | "CHARGEABILITY" | "BUDGET"; type ProjectComputationDomain = | "INPUT" | "ESTIMATE" | "COMMERCIAL" | "EXPERIENCE" | "EFFORT" | "SPREAD" | "BUDGET"; export type ChargeabilityComputationDeps = { assertPermission: (ctx: ToolContext, perm: PermissionKey) => void; createChargeabilityReportCaller: (ctx: TRPCContext) => { getDetail: (params: { startMonth: string; endMonth: string; orgUnitId?: string; managementLevelGroupId?: string; countryId?: string; includeProposed: boolean; resourceQuery?: string; resourceLimit?: number; }) => Promise; }; createComputationGraphCaller: (ctx: TRPCContext) => { getResourceDataDetail: (params: { resourceId: string; month: string; domain?: string; includeLinks?: boolean; }) => Promise; getProjectDataDetail: (params: { projectId: string; domain?: string; includeLinks?: boolean; }) => Promise; }; createScopedCallerContext: (ctx: ToolContext) => TRPCContext; resolveResourceIdentifier: ( ctx: ToolContext, identifier: string, ) => Promise; resolveProjectIdentifier: ( ctx: ToolContext, identifier: string, ) => Promise; }; export const chargeabilityComputationReadToolDefinitions: ToolDef[] = [ { type: "function", function: { name: "get_chargeability_report", description: "Get the detailed chargeability report readmodel for a month range, including group totals and per-resource month series. Requires controller/manager/admin access, viewCosts, and useAssistantAdvancedTools.", parameters: { type: "object", properties: { startMonth: { type: "string", description: "Start month in YYYY-MM format." }, endMonth: { type: "string", description: "End month in YYYY-MM format." }, orgUnitId: { type: "string", description: "Optional org unit filter." }, managementLevelGroupId: { type: "string", description: "Optional management level group filter." }, countryId: { type: "string", description: "Optional country filter." }, includeProposed: { type: "boolean", description: "Whether proposed bookings should count towards chargeability. Default: false." }, resourceQuery: { type: "string", description: "Optional resource filter by name or eid after loading the report." }, resourceLimit: { type: "integer", description: "Maximum number of resources returned. Default: 25, max 100." }, }, required: ["startMonth", "endMonth"], }, }, }, { type: "function", function: { name: "get_resource_computation_graph", description: "Get the resource computation graph with transparent SAH, holiday, absence, allocation, chargeability, and budget derivation factors. Requires controller/manager/admin access, viewCosts, and useAssistantAdvancedTools.", parameters: { type: "object", properties: { resourceId: { type: "string", description: "Resource ID, eid, or display name." }, month: { type: "string", description: "Month in YYYY-MM format." }, domain: { type: "string", enum: ["INPUT", "SAH", "ALLOCATION", "RULES", "CHARGEABILITY", "BUDGET"], description: "Optional domain filter for graph nodes." }, includeLinks: { type: "boolean", description: "Include graph links for the selected nodes. Default: false." }, }, required: ["resourceId", "month"], }, }, }, { type: "function", function: { name: "get_project_computation_graph", description: "Get the project computation graph with estimate, commercial, effort, experience, spread, and budget derivation factors. Requires controller/manager/admin access, viewCosts, and useAssistantAdvancedTools.", parameters: { type: "object", properties: { projectId: { type: "string", description: "Project ID, short code, or project name." }, domain: { type: "string", enum: ["INPUT", "ESTIMATE", "COMMERCIAL", "EXPERIENCE", "EFFORT", "SPREAD", "BUDGET"], description: "Optional domain filter for graph nodes." }, includeLinks: { type: "boolean", description: "Include graph links for the selected nodes. Default: false." }, }, required: ["projectId"], }, }, }, ]; export function createChargeabilityComputationExecutors( deps: ChargeabilityComputationDeps, ): Record { return { async get_chargeability_report(params: { startMonth: string; endMonth: string; orgUnitId?: string; managementLevelGroupId?: string; countryId?: string; includeProposed?: boolean; resourceQuery?: string; resourceLimit?: number; }, ctx: ToolContext) { deps.assertPermission(ctx, PermissionKey.VIEW_COSTS); deps.assertPermission(ctx, PermissionKey.USE_ASSISTANT_ADVANCED_TOOLS); const caller = deps.createChargeabilityReportCaller(deps.createScopedCallerContext(ctx)); return caller.getDetail({ startMonth: params.startMonth, endMonth: params.endMonth, ...(params.orgUnitId ? { orgUnitId: params.orgUnitId } : {}), ...(params.managementLevelGroupId ? { managementLevelGroupId: params.managementLevelGroupId } : {}), ...(params.countryId ? { countryId: params.countryId } : {}), includeProposed: params.includeProposed ?? false, ...(params.resourceQuery ? { resourceQuery: params.resourceQuery } : {}), ...(params.resourceLimit !== undefined ? { resourceLimit: params.resourceLimit } : {}), }); }, async get_resource_computation_graph(params: { resourceId: string; month: string; domain?: ResourceComputationDomain; includeLinks?: boolean; }, ctx: ToolContext) { deps.assertPermission(ctx, PermissionKey.VIEW_COSTS); deps.assertPermission(ctx, PermissionKey.USE_ASSISTANT_ADVANCED_TOOLS); const resource = await deps.resolveResourceIdentifier(ctx, params.resourceId); if ("error" in resource) { return resource; } const caller = deps.createComputationGraphCaller(deps.createScopedCallerContext(ctx)); return caller.getResourceDataDetail({ resourceId: resource.id, month: params.month, ...(params.domain ? { domain: params.domain } : {}), ...(params.includeLinks !== undefined ? { includeLinks: params.includeLinks } : {}), }); }, async get_project_computation_graph(params: { projectId: string; domain?: ProjectComputationDomain; includeLinks?: boolean; }, ctx: ToolContext) { deps.assertPermission(ctx, PermissionKey.VIEW_COSTS); deps.assertPermission(ctx, PermissionKey.USE_ASSISTANT_ADVANCED_TOOLS); const project = await deps.resolveProjectIdentifier(ctx, params.projectId); if ("error" in project) { return project; } const caller = deps.createComputationGraphCaller(deps.createScopedCallerContext(ctx)); return caller.getProjectDataDetail({ projectId: project.id, ...(params.domain ? { domain: params.domain } : {}), ...(params.includeLinks !== undefined ? { includeLinks: params.includeLinks } : {}), }); }, }; }