feat: added some additional data on export
Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
@@ -4,8 +4,9 @@ import db from '@/lib/db';
|
||||
import { participant } from '@/lib/model/participant';
|
||||
import { annotation } from '@/lib/model/annotation';
|
||||
import { experiment } from '@/lib/model/experiment';
|
||||
import { experiment_calibration, ExperimentCalibration } from '@/lib/model/experiment_calibration';
|
||||
import { dataset } from '@/lib/model/dataset';
|
||||
import { dataset_entry } from '@/lib/model/dataset_entry';
|
||||
import { dataset_entry, DatasetEntry } from '@/lib/model/dataset_entry';
|
||||
import { user } from '@/lib/model/auth-schema';
|
||||
import { and, eq, count } from 'drizzle-orm';
|
||||
import { requireAdmin } from '@/lib/auth';
|
||||
@@ -24,19 +25,23 @@ export interface ParticipantListItem {
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
export interface EnrichedCalibrationAnswer {
|
||||
dialectLabel: string;
|
||||
confidence: number;
|
||||
calibrationEntry: ExperimentCalibration | null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
export interface ParticipantDetail extends ParticipantListItem {
|
||||
onboardingAnswers: any;
|
||||
calibrationAnswers: any;
|
||||
calibrationAnswers: EnrichedCalibrationAnswer[];
|
||||
annotations: Array<{
|
||||
id: number;
|
||||
datasetEntryId: number;
|
||||
externalId: string;
|
||||
fileName: string;
|
||||
datasetId: number;
|
||||
rating: number;
|
||||
dialectLabel: string;
|
||||
createdAt: Date;
|
||||
confidence: number;
|
||||
createdAt: Date;
|
||||
datasetEntry: DatasetEntry | null;
|
||||
}>;
|
||||
}
|
||||
|
||||
@@ -166,7 +171,7 @@ export async function getParticipantDetail(
|
||||
const participantAnnotations = await db
|
||||
.select({
|
||||
id: annotation.id,
|
||||
datasetEntryId: annotation.datasetEntryId,
|
||||
datasetEntry: dataset_entry,
|
||||
externalId: dataset_entry.externalId,
|
||||
fileName: dataset_entry.fileName,
|
||||
datasetId: dataset_entry.datasetId,
|
||||
@@ -184,6 +189,42 @@ export async function getParticipantDetail(
|
||||
)
|
||||
);
|
||||
|
||||
// Enrich calibration answers with calibration entry details
|
||||
const enrichedCalibrationAnswers: EnrichedCalibrationAnswer[] = [];
|
||||
if (p.calibrationAnswers && typeof p.calibrationAnswers === 'object') {
|
||||
const answers = p.calibrationAnswers as Record<number, any>;
|
||||
|
||||
// Get all calibration entries for this experiment
|
||||
const calibrationEntries = await db
|
||||
.select()
|
||||
.from(experiment_calibration)
|
||||
.where(eq(experiment_calibration.experimentId, experimentId));
|
||||
|
||||
// Create a map of calibration entries by ID for quick lookup
|
||||
const entriesMap = new Map(calibrationEntries.map(e => [e.id, e]));
|
||||
|
||||
// Enrich each answer with calibration entry data
|
||||
for (const [itemId, answer] of Object.entries(answers)) {
|
||||
const calibrationId = parseInt(itemId);
|
||||
const entry = entriesMap.get(calibrationId);
|
||||
|
||||
if (entry) {
|
||||
enrichedCalibrationAnswers.push({
|
||||
dialectLabel: answer.dialectLabel,
|
||||
confidence: answer.confidence,
|
||||
calibrationEntry: entry,
|
||||
});
|
||||
} else {
|
||||
// Fallback if entry not found
|
||||
enrichedCalibrationAnswers.push({
|
||||
dialectLabel: answer.dialectLabel,
|
||||
confidence: answer.confidence,
|
||||
calibrationEntry: null,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Use the proper calibration validation function
|
||||
const completedCalibration = await isParticipantCalibrationDone(experimentId, p);
|
||||
|
||||
@@ -195,16 +236,14 @@ export async function getParticipantDetail(
|
||||
completedOnboarding: p.onboardingAnswers !== null && p.onboardingAnswers !== undefined,
|
||||
completedCalibration,
|
||||
onboardingAnswers: p.onboardingAnswers || {},
|
||||
calibrationAnswers: p.calibrationAnswers || {},
|
||||
calibrationAnswers: enrichedCalibrationAnswers,
|
||||
annotationCount: participantAnnotations.length,
|
||||
annotations: participantAnnotations.map(a => ({
|
||||
id: a.id,
|
||||
datasetEntryId: a.datasetEntryId,
|
||||
externalId: a.externalId || 'Unknown', fileName: a.fileName || 'Unknown',
|
||||
datasetId: a.datasetId || 0, rating: a.rating,
|
||||
dialectLabel: a.dialectLabel,
|
||||
rating: a.rating,
|
||||
confidence: a.confidence,
|
||||
createdAt: a.createdAt,
|
||||
datasetEntry: a.datasetEntry
|
||||
})),
|
||||
createdAt: p.createdAt,
|
||||
updatedAt: p.updatedAt,
|
||||
@@ -311,18 +350,19 @@ export async function exportParticipantDataAsJson(
|
||||
},
|
||||
calibration: {
|
||||
completed: participantDetail.completedCalibration,
|
||||
answers: participantDetail.calibrationAnswers,
|
||||
entries: participantDetail.calibrationAnswers.map((answer) => ({
|
||||
dialectLabel: answer.dialectLabel,
|
||||
confidence: answer.confidence,
|
||||
calibrationEntry: answer.calibrationEntry
|
||||
})),
|
||||
scores: calibrationScores,
|
||||
},
|
||||
annotations: participantDetail.annotations.map((ann) => ({
|
||||
id: ann.id,
|
||||
externalId: ann.externalId,
|
||||
fileName: ann.fileName,
|
||||
datasetId: ann.datasetId,
|
||||
rating: ann.rating,
|
||||
confidence: ann.confidence,
|
||||
dialectLabel: ann.dialectLabel,
|
||||
createdAt: ann.createdAt,
|
||||
datasetEntry: ann.datasetEntry
|
||||
})),
|
||||
};
|
||||
|
||||
|
||||
@@ -127,10 +127,10 @@ export default function ParticipantDetailView({ participant, experimentId }: Par
|
||||
: 'border-gray-200 bg-gray-50 hover:border-gray-300'
|
||||
}`}
|
||||
>
|
||||
<div className="font-medium text-gray-900">{annotation.externalId}</div>
|
||||
<div className="font-medium text-gray-900">{annotation.datasetEntry?.externalId || 'Unknown'}</div>
|
||||
<div className="text-sm text-gray-600 mt-1">
|
||||
Rating: <span className="font-semibold">{annotation.rating}</span> | Dialect:{' '}
|
||||
<span className="font-semibold">{annotation.dialectLabel}</span>
|
||||
<span className="font-semibold">{annotation.datasetEntry?.dialect || 'Unknown'}</span>
|
||||
</div>
|
||||
<div className="text-xs text-gray-500 mt-1">
|
||||
{new Date(annotation.createdAt).toLocaleString()}
|
||||
@@ -147,7 +147,7 @@ export default function ParticipantDetailView({ participant, experimentId }: Par
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<div className="text-sm text-gray-600 mb-1">External ID</div>
|
||||
<div className="font-medium text-gray-900 break-all">{selectedAnnotation.externalId}</div>
|
||||
<div className="font-medium text-gray-900 break-all">{selectedAnnotation.datasetEntry?.externalId || 'Unknown'}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-sm text-gray-600 mb-1">Rating</div>
|
||||
@@ -160,7 +160,7 @@ export default function ParticipantDetailView({ participant, experimentId }: Par
|
||||
<div>
|
||||
<div className="text-sm text-gray-600 mb-1">Dialect</div>
|
||||
<div className="inline-block px-3 py-1 bg-blue-100 text-blue-800 rounded-full text-sm font-medium">
|
||||
{selectedAnnotation.dialectLabel}
|
||||
{selectedAnnotation.datasetEntry?.dialect || 'Unknown'}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
@@ -170,8 +170,8 @@ export default function ParticipantDetailView({ participant, experimentId }: Par
|
||||
<div>
|
||||
<div className="text-sm text-gray-600 mb-1">Audio</div>
|
||||
{(() => {
|
||||
const fileExtension = selectedAnnotation.fileName.substring(selectedAnnotation.fileName.lastIndexOf('.'));
|
||||
const src = `/public/datasets/${selectedAnnotation.datasetId}/${selectedAnnotation.externalId}${fileExtension}`;
|
||||
const fileExtension = selectedAnnotation.datasetEntry?.fileName.substring(selectedAnnotation.datasetEntry?.fileName.lastIndexOf('.'));
|
||||
const src = `/public/datasets/${selectedAnnotation.datasetEntry?.datasetId}/${selectedAnnotation.datasetEntry?.externalId}${fileExtension}`;
|
||||
return <WaveformPlayer src={src} showWaveform={false} playMode='stop' />;
|
||||
})()}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user