feat: added simple statistics to backend to track progress on experiment

This commit is contained in:
averel10
2026-04-03 07:55:56 +02:00
parent 7c82befa3d
commit 8e3ee07d9f
8 changed files with 1105 additions and 26 deletions

View File

@@ -5,9 +5,11 @@ import { experiment } from '@/lib/model/experiment';
import { experiment_calibration } from '@/lib/model/experiment_calibration';
import { annotation } from '@/lib/model/annotation';
import { participant } from '@/lib/model/participant';
import { and, eq } from 'drizzle-orm';
import { dataset_entry } from '@/lib/model/dataset_entry';
import { and, eq, count, sql, isNotNull } from 'drizzle-orm';
import { revalidatePath } from 'next/cache';
import { auth, requireAdmin } from '@/lib/auth';
import { isParticipantCalibrationDone } from './calibration-scoring';
import { headers } from 'next/headers';
export async function createExperiment({
@@ -183,3 +185,108 @@ export async function clearExperimentData(id: number) {
throw new Error('Failed to clear experiment data');
}
}
export async function getExperimentStatistics(id: number) {
const result = await requireAdmin();
if (!result.authenticated || !result.admin) {
throw new Error('Unauthorized');
}
try {
// Get the experiment with dataset
const exp = await db
.select()
.from(experiment)
.where(eq(experiment.id, id))
.limit(1);
if (!exp || exp.length === 0) {
throw new Error('Experiment not found');
}
const datasetId = exp[0].datasetId;
// Get all participants
const allParticipants = await db
.select()
.from(participant)
.where(eq(participant.experimentId, id));
// Count onboarding completion (simple check for non-null)
const completedOnboarding = allParticipants.filter(
(p) => p.onboardingAnswers !== null && p.onboardingAnswers !== undefined
).length;
// Check calibration completion using the proper validation
let completedCalibration = 0;
for (const p of allParticipants) {
if (await isParticipantCalibrationDone(id, p)) {
completedCalibration++;
}
}
// Count total dataset entries
const totalEntriesResult = await db
.select({ total: count().as('total') })
.from(dataset_entry)
.where(eq(dataset_entry.datasetId, datasetId));
const totalEntries = totalEntriesResult[0]?.total || 0;
// Get annotation statistics
const annotationStats = await db
.select({
total: count().as('total'),
entriesWithAnnotations: count(sql`DISTINCT ${annotation.datasetEntryId}`).as('entriesWithAnnotations'),
})
.from(annotation)
.where(eq(annotation.experimentId, id));
// Get per-entry annotation counts
const perEntryAnnotations = await db
.select({
datasetEntryId: annotation.datasetEntryId,
annotationCount: count().as('annotationCount'),
})
.from(annotation)
.where(eq(annotation.experimentId, id))
.groupBy(annotation.datasetEntryId);
// Calculate statistics
const totalAnnotations = annotationStats[0]?.total || 0;
const entriesWithAnnotations = annotationStats[0]?.entriesWithAnnotations || 0;
const entriesWithoutAnnotations = Math.max(0, totalEntries - entriesWithAnnotations);
const averageAnnotationsPerEntry = totalEntries > 0 ? (totalAnnotations / totalEntries).toFixed(2) : '0';
// Find max and min annotations per entry
let maxAnnotationsPerEntry = 0;
let minAnnotationsPerEntry = 0;
if (perEntryAnnotations.length > 0) {
const counts = perEntryAnnotations.map(p => p.annotationCount || 0);
maxAnnotationsPerEntry = Math.max(...counts);
minAnnotationsPerEntry = Math.min(...counts);
}
return {
participants: {
total: allParticipants.length,
completedOnboarding,
completedCalibration,
},
entries: {
total: totalEntries,
},
annotations: {
total: totalAnnotations,
entriesWithAnnotations,
entriesWithoutAnnotations,
averagePerEntry: parseFloat(averageAnnotationsPerEntry),
maxPerEntry: maxAnnotationsPerEntry,
minPerEntry: minAnnotationsPerEntry,
},
};
} catch (error) {
console.error('Error fetching experiment statistics:', error);
throw new Error('Failed to fetch experiment statistics');
}
}