diff --git a/lib/insights/verify-stats.ts b/lib/insights/verify-stats.ts new file mode 100644 index 0000000..5f3772e --- /dev/null +++ b/lib/insights/verify-stats.ts @@ -0,0 +1,92 @@ +import { prisma } from '@/lib/prisma' + +export type VerifyResultKey = 'ALIGNED' | 'PARTIAL' | 'EMPTY' | 'DIVERGENT' + +export interface TopJob { + jobId: string + taskId: string + taskTitle: string + productId: string + productName: string + finishedAt: Date +} + +export interface VerifyResultStats { + counts: { result: VerifyResultKey; count: number }[] + topEmpty: TopJob[] + topDivergent: TopJob[] +} + +const RESULT_ORDER: VerifyResultKey[] = ['ALIGNED', 'PARTIAL', 'EMPTY', 'DIVERGENT'] + +export async function getVerifyResultStats( + userId: string, + daysBack = 30, +): Promise { + const cutoff = new Date() + cutoff.setDate(cutoff.getDate() - daysBack) + + const baseWhere = { + user_id: userId, + status: 'DONE' as const, + verify_result: { not: null as null }, + finished_at: { gt: cutoff }, + } + + const [grouped, rawEmpty, rawDivergent] = await Promise.all([ + prisma.claudeJob.groupBy({ + by: ['verify_result'], + where: baseWhere, + _count: { _all: true }, + }), + prisma.claudeJob.findMany({ + where: { ...baseWhere, verify_result: 'EMPTY' }, + orderBy: { finished_at: 'desc' }, + take: 5, + select: { + id: true, + finished_at: true, + task: { select: { id: true, title: true } }, + product: { select: { id: true, name: true } }, + }, + }), + prisma.claudeJob.findMany({ + where: { ...baseWhere, verify_result: 'DIVERGENT' }, + orderBy: { finished_at: 'desc' }, + take: 5, + select: { + id: true, + finished_at: true, + task: { select: { id: true, title: true } }, + product: { select: { id: true, name: true } }, + }, + }), + ]) + + const countMap = new Map( + grouped + .filter(g => g.verify_result !== null) + .map(g => [g.verify_result as VerifyResultKey, g._count._all]), + ) + + const counts = RESULT_ORDER + .filter(r => countMap.has(r)) + .map(r => ({ result: r, count: countMap.get(r)! })) + + function toTopJob(j: { id: string; finished_at: Date | null; task: { id: string; title: string }; product: { id: string; name: string } }): TopJob { + return { + jobId: j.id, + taskId: j.task.id, + taskTitle: j.task.title, + productId: j.product.id, + productName: j.product.name, + finishedAt: j.finished_at!, + } + } + + return { + counts, + topEmpty: rawEmpty.map(toTopJob), + topDivergent: rawDivergent.map(toTopJob), + } +}