Upload (Audio nur per Magic Bytes, versiegelt, 2 MiB), Text-Weg, Liste, Abruf, Wiederholen, Verwerfen, alle verwerfen, Konfiguration und Zähler unter /voice-captures. Rechte voice-captures:use plus inspections:create (eigener Guard, API-Schlüssel ausgeschlossen), Zähler nur mit system:manage; jede Abfrage auf den Besitzer begrenzt. Grenzen je Benutzer unter Advisory-Lock (10 offen, 30 je Tag, 10 je 60 s), Wiederholen ebenfalls begrenzt und gedrosselt, Audio für QUEUED nur unter 24 h. Verwerfen löscht Inhalt und Claim sofort. Keine Inhalte in Log oder Audit. Wächter gegen Sprachinhalte in Suche, Export und Berichten erweitert. Refs #27 Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
67 lines
2.2 KiB
TypeScript
67 lines
2.2 KiB
TypeScript
import { Injectable } from '@nestjs/common';
|
|
import { Prisma } from '../../../generated/prisma';
|
|
import { PrismaService } from '../../prisma/prisma.service';
|
|
|
|
export interface VoiceCaptureStats {
|
|
byStatus: Record<string, number>;
|
|
byError: Record<string, number>;
|
|
tokens: { input: number; output: number };
|
|
byModel: Record<string, number>;
|
|
}
|
|
|
|
interface CountRow {
|
|
key: string;
|
|
count: number;
|
|
}
|
|
|
|
interface TokenRow {
|
|
input: bigint | number;
|
|
output: bigint | number;
|
|
}
|
|
|
|
function toRecord(rows: readonly CountRow[]): Record<string, number> {
|
|
return Object.fromEntries(rows.map((row) => [row.key, Number(row.count)]));
|
|
}
|
|
|
|
/**
|
|
* Counters of the last seven days for the system area: per status, per error
|
|
* code, per model and the token sums. Aggregates only - no user, no capture
|
|
* id, no content. The window runs on the database clock.
|
|
*/
|
|
@Injectable()
|
|
export class VoiceCapturesStatsService {
|
|
constructor(private readonly prisma: PrismaService) {}
|
|
|
|
async stats(): Promise<VoiceCaptureStats> {
|
|
const since = Prisma.sql`"createdAt" > (now() AT TIME ZONE 'UTC') - interval '7 days'`;
|
|
const [byStatus, byError, byModel, tokens] = await Promise.all([
|
|
this.prisma.$queryRaw<CountRow[]>(Prisma.sql`
|
|
SELECT "status"::text AS "key", count(*)::int AS "count"
|
|
FROM "VoiceCapture" WHERE ${since} GROUP BY "status"`),
|
|
this.prisma.$queryRaw<CountRow[]>(Prisma.sql`
|
|
SELECT "lastErrorCode" AS "key", count(*)::int AS "count"
|
|
FROM "VoiceCapture"
|
|
WHERE ${since} AND "lastErrorCode" IS NOT NULL
|
|
GROUP BY "lastErrorCode"`),
|
|
this.prisma.$queryRaw<CountRow[]>(Prisma.sql`
|
|
SELECT "model" AS "key", count(*)::int AS "count"
|
|
FROM "VoiceCapture"
|
|
WHERE ${since} AND "model" IS NOT NULL
|
|
GROUP BY "model"`),
|
|
this.prisma.$queryRaw<TokenRow[]>(Prisma.sql`
|
|
SELECT COALESCE(sum("inputTokens"), 0) AS "input",
|
|
COALESCE(sum("outputTokens"), 0) AS "output"
|
|
FROM "VoiceCapture" WHERE ${since}`),
|
|
]);
|
|
return {
|
|
byStatus: toRecord(byStatus),
|
|
byError: toRecord(byError),
|
|
tokens: {
|
|
input: Number(tokens[0]?.input ?? 0),
|
|
output: Number(tokens[0]?.output ?? 0),
|
|
},
|
|
byModel: toRecord(byModel),
|
|
};
|
|
}
|
|
}
|