import { z } from "zod"; import { createTRPCRouter, protectedProcedure, controllerProcedure } from "../trpc.js"; import { getDashboardChargeabilityOverview, getDashboardDemand, getDashboardOverview, getDashboardPeakTimes, getDashboardTopValueResources, getDashboardBudgetForecast, getDashboardSkillGaps, getDashboardProjectHealth, } from "@capakraken/application"; import { anonymizeResources, getAnonymizationDirectory } from "../lib/anonymization.js"; import { cacheGet, cacheSet } from "../lib/cache.js"; const DEFAULT_TTL = 60; // seconds export const dashboardRouter = createTRPCRouter({ getOverview: protectedProcedure.query(async ({ ctx }) => { const cacheKey = "overview"; const cached = await cacheGet>>(cacheKey); if (cached) return cached; const result = await getDashboardOverview(ctx.db); await cacheSet(cacheKey, result, DEFAULT_TTL); return result; }), getPeakTimes: protectedProcedure .input( z.object({ startDate: z.string().datetime(), endDate: z.string().datetime(), granularity: z.enum(["week", "month"]).default("month"), groupBy: z.enum(["project", "chapter", "resource"]).default("project"), }), ) .query(async ({ ctx, input }) => { const cacheKey = `peakTimes:${input.startDate}:${input.endDate}:${input.granularity}:${input.groupBy}`; const cached = await cacheGet>>(cacheKey); if (cached) return cached; const result = await getDashboardPeakTimes(ctx.db, { startDate: new Date(input.startDate), endDate: new Date(input.endDate), granularity: input.granularity, groupBy: input.groupBy, }); await cacheSet(cacheKey, result, DEFAULT_TTL); return result; }), getTopValueResources: protectedProcedure .input(z.object({ limit: z.number().int().min(1).max(50).default(10) })) .query(async ({ ctx, input }) => { const userRole = (ctx.session.user as { role?: string } | undefined)?.role ?? "USER"; const cacheKey = `topValue:${input.limit}:${userRole}`; const cached = await cacheGet>>(cacheKey); if (cached) return cached; const [resources, directory] = await Promise.all([ getDashboardTopValueResources(ctx.db, { limit: input.limit, userRole, }), getAnonymizationDirectory(ctx.db), ]); const result = anonymizeResources(resources, directory); await cacheSet(cacheKey, result, DEFAULT_TTL); return result; }), getDemand: protectedProcedure .input( z.object({ startDate: z.string().datetime(), endDate: z.string().datetime(), groupBy: z.enum(["project", "person", "chapter"]).default("project"), }), ) .query(async ({ ctx, input }) => { const cacheKey = `demand:${input.startDate}:${input.endDate}:${input.groupBy}`; const cached = await cacheGet>>(cacheKey); if (cached) return cached; const result = await getDashboardDemand(ctx.db, { startDate: new Date(input.startDate), endDate: new Date(input.endDate), groupBy: input.groupBy, }); await cacheSet(cacheKey, result, DEFAULT_TTL); return result; }), getChargeabilityOverview: controllerProcedure .input( z.object({ includeProposed: z.boolean().default(false), topN: z.number().int().min(1).max(50).default(10), watchlistThreshold: z.number().default(15), countryIds: z.array(z.string()).optional(), departed: z.boolean().optional(), }), ) .query(async ({ ctx, input }) => { const cacheKey = `chargeability:${input.includeProposed}:${input.topN}:${input.watchlistThreshold}:${(input.countryIds ?? []).join(",")}:${input.departed ?? ""}`; type ChargeResult = Awaited>; const cached = await cacheGet<{ top: unknown[]; watchlist: unknown[]; [key: string]: unknown; }>(cacheKey); if (cached) return cached; const [overview, directory] = await Promise.all([ getDashboardChargeabilityOverview(ctx.db, { includeProposed: input.includeProposed, topN: input.topN, watchlistThreshold: input.watchlistThreshold, ...(input.countryIds !== undefined ? { countryIds: input.countryIds } : {}), ...(input.departed !== undefined ? { departed: input.departed } : {}), }), getAnonymizationDirectory(ctx.db), ]); const result = { ...overview, top: anonymizeResources(overview.top, directory), watchlist: anonymizeResources(overview.watchlist, directory), }; await cacheSet(cacheKey, result, DEFAULT_TTL); return result; }), getBudgetForecast: protectedProcedure.query(async ({ ctx }) => { const cacheKey = "budgetForecast"; const cached = await cacheGet>>(cacheKey); if (cached) return cached; const result = await getDashboardBudgetForecast(ctx.db); await cacheSet(cacheKey, result, DEFAULT_TTL); return result; }), getSkillGaps: protectedProcedure.query(async ({ ctx }) => { const cacheKey = "skillGaps"; const cached = await cacheGet>>(cacheKey); if (cached) return cached; const result = await getDashboardSkillGaps(ctx.db); await cacheSet(cacheKey, result, DEFAULT_TTL); return result; }), getProjectHealth: protectedProcedure.query(async ({ ctx }) => { const cacheKey = "projectHealth"; const cached = await cacheGet>>(cacheKey); if (cached) return cached; const result = await getDashboardProjectHealth(ctx.db); await cacheSet(cacheKey, result, DEFAULT_TTL); return result; }), });