import { and, eq, sql } from "drizzle-orm"; import type { Db } from "@paperclip/db"; import { agents, approvals, companies, issues } from "@paperclip/db"; import { notFound } from "../errors.js"; export function dashboardService(db: Db) { return { summary: async (companyId: string) => { const company = await db .select() .from(companies) .where(eq(companies.id, companyId)) .then((rows) => rows[0] ?? null); if (!company) throw notFound("Company not found"); const agentRows = await db .select({ status: agents.status, count: sql`count(*)` }) .from(agents) .where(eq(agents.companyId, companyId)) .groupBy(agents.status); const taskRows = await db .select({ status: issues.status, count: sql`count(*)` }) .from(issues) .where(eq(issues.companyId, companyId)) .groupBy(issues.status); const pendingApprovals = await db .select({ count: sql`count(*)` }) .from(approvals) .where(and(eq(approvals.companyId, companyId), eq(approvals.status, "pending"))) .then((rows) => Number(rows[0]?.count ?? 0)); const staleCutoff = new Date(Date.now() - 60 * 60 * 1000); const staleTasks = await db .select({ count: sql`count(*)` }) .from(issues) .where( and( eq(issues.companyId, companyId), eq(issues.status, "in_progress"), sql`${issues.startedAt} < ${staleCutoff.toISOString()}`, ), ) .then((rows) => Number(rows[0]?.count ?? 0)); const agentCounts: Record = { active: 0, running: 0, paused: 0, error: 0, }; for (const row of agentRows) { agentCounts[row.status] = Number(row.count); } const taskCounts: Record = { open: 0, inProgress: 0, blocked: 0, done: 0, }; for (const row of taskRows) { const count = Number(row.count); if (row.status === "in_progress") taskCounts.inProgress += count; if (row.status === "blocked") taskCounts.blocked += count; if (row.status === "done") taskCounts.done += count; if (row.status !== "done" && row.status !== "cancelled") taskCounts.open += count; } const utilization = company.budgetMonthlyCents > 0 ? (company.spentMonthlyCents / company.budgetMonthlyCents) * 100 : 0; return { companyId, agents: { active: agentCounts.active, running: agentCounts.running, paused: agentCounts.paused, error: agentCounts.error, }, tasks: taskCounts, costs: { monthSpendCents: company.spentMonthlyCents, monthBudgetCents: company.budgetMonthlyCents, monthUtilizationPercent: Number(utilization.toFixed(2)), }, pendingApprovals, staleTasks, }; }, }; }