import { db } from '@/lib/db' /** * Zusammenfassung der Ladewerte für den Adminbereich. * * Ausgewertet wird der **Median und das 75. Perzentil**, nicht der Mittelwert: * ein einzelner Ausreisser auf einem alten Laptop soll die Zahl nicht * bestimmen. Zielwerte aus docs/09-performance-und-ux.md. */ const TAGE = 7 export type VitalSummary = { metric: string count: number median: number p75: number } export async function summarizeVitals(): Promise<{ metriken: VitalSummary[] routen: { route: string; metric: string; p75: number; count: number }[] }> { const seit = new Date(Date.now() - TAGE * 86_400_000) const metriken = await db.$queryRaw< { metric: string; count: bigint; median: number; p75: number }[] >` SELECT "metric", count(*) AS count, percentile_cont(0.5) WITHIN GROUP (ORDER BY "value") AS median, percentile_cont(0.75) WITHIN GROUP (ORDER BY "value") AS p75 FROM "WebVital" WHERE "at" >= ${seit} GROUP BY "metric" ORDER BY "metric" ` const routen = await db.$queryRaw< { route: string; metric: string; p75: number; count: bigint }[] >` SELECT "route", "metric", percentile_cont(0.75) WITHIN GROUP (ORDER BY "value") AS p75, count(*) AS count FROM "WebVital" WHERE "at" >= ${seit} AND "metric" = 'LCP' GROUP BY "route", "metric" HAVING count(*) >= 3 ORDER BY p75 DESC LIMIT 8 ` return { metriken: metriken.map((zeile) => ({ metric: zeile.metric, count: Number(zeile.count), median: zeile.median, p75: zeile.p75, })), routen: routen.map((zeile) => ({ route: zeile.route, metric: zeile.metric, p75: zeile.p75, count: Number(zeile.count), })), } } /** Millisekunden lesbar machen; CLS ist dimensionslos. */ export function formatVital(metric: string, wert: number): string { if (metric === 'CLS') return wert.toFixed(3) if (wert >= 1000) return `${(wert / 1000).toFixed(2)} s` return `${Math.round(wert)} ms` }