134 lines
5.2 KiB
TypeScript
Executable File
134 lines
5.2 KiB
TypeScript
Executable File
import { NextResponse } from 'next/server';
|
|
import { db } from '@/lib/db';
|
|
import { getSession } from '@/lib/auth';
|
|
import ZAI from 'z-ai-web-dev-sdk';
|
|
|
|
export async function POST(request: Request) {
|
|
try {
|
|
const session = await getSession();
|
|
if (!session) {
|
|
return NextResponse.json({ error: 'Not authenticated' }, { status: 401 });
|
|
}
|
|
|
|
const role = session.user?.role;
|
|
if (role !== 'TEACHER' && role !== 'SCHOOL_ADMIN' && role !== 'SUPER_ADMIN' && role !== 'VICE_PRINCIPAL') {
|
|
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
|
|
}
|
|
|
|
const body = await request.json();
|
|
const { schoolId, assessmentId, studentId } = body;
|
|
|
|
if (!schoolId || !assessmentId || !studentId) {
|
|
return NextResponse.json({ error: 'schoolId, assessmentId, and studentId are required' }, { status: 400 });
|
|
}
|
|
|
|
// Get assessment and student data
|
|
const assessment = await db.assessment.findUnique({
|
|
where: { id: assessmentId },
|
|
include: {
|
|
assessmentResults: {
|
|
where: { studentId },
|
|
include: {
|
|
student: { select: { id: true, firstName: true, lastName: true } },
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
if (!assessment) {
|
|
return NextResponse.json({ error: 'Assessment not found' }, { status: 404 });
|
|
}
|
|
|
|
// Get annotations for this student's submission
|
|
const annotations = await db.gradingAnnotation.findMany({
|
|
where: { assessmentId, studentId },
|
|
orderBy: { createdAt: 'asc' },
|
|
});
|
|
|
|
const studentResult = assessment.assessmentResults[0];
|
|
const student = studentResult?.student;
|
|
|
|
// Generate AI grading audit
|
|
const systemPrompt = `Du bist ein erfahrener Lehrer und Prüfer an einer deutschen Schule.
|
|
Du überprüfst die Benotung eines Schülers und gibst detailliertes Feedback.
|
|
Die deutsche Notenskala: 1 (sehr gut), 2 (gut), 3 (befriedigend), 4 (ausreichend), 5 (mangelhaft), 6 (ungenügend).
|
|
Analysiere die Leistung und die gegebene Benotung. Wenn die Benotung nicht angemessen ist, schlage eine Korrektur vor mit Begründung.
|
|
Antworte immer auf Deutsch und im JSON-Format.`;
|
|
|
|
const gradingInfo = {
|
|
studentName: student ? `${student.firstName} ${student.lastName}` : 'Unknown',
|
|
assessmentTitle: assessment.title,
|
|
assessmentType: assessment.type,
|
|
maxScore: assessment.maxScore,
|
|
score: studentResult?.score ?? null,
|
|
note: studentResult?.note ?? null,
|
|
annotationsCount: annotations.length,
|
|
annotationTypes: annotations.map((a: { type: string; content: string | null }) => ({ type: a.type, content: a.content })),
|
|
teacherAnnotations: annotations.filter((a: { type: string }) => a.type === 'stamp' || a.type === 'text').map((a: { type: string; content: string | null; color: string }) => ({ type: a.type, content: a.content, color: a.color })),
|
|
};
|
|
|
|
let reviewResult: string;
|
|
let discrepanciesFound = 0;
|
|
|
|
try {
|
|
const ai = await ZAI.create();
|
|
const response = await ai.chat.completions.create({
|
|
messages: [
|
|
{ role: 'system', content: systemPrompt },
|
|
{
|
|
role: 'user',
|
|
content: `Überprüfe die Benotung für diesen Schüler und gib Feedback:\n\n${JSON.stringify(gradingInfo, null, 2)}\n\nGib deine Antwort als JSON mit folgenden Feldern:\n- currentGrade: die aktuelle Benotung\n- suggestedGrade: deine vorgeschlagene Note\n- reason: detaillierte Begründung auf Deutsch\n- discrepancies: Liste der gefundenen Unstimmigkeiten\n- overallAssessment: Gesamtbewertung der Benotung`,
|
|
},
|
|
],
|
|
});
|
|
|
|
reviewResult = response?.choices?.[0]?.message?.content || 'Review could not be generated';
|
|
} catch (aiError) {
|
|
console.error('AI grading audit error:', aiError);
|
|
reviewResult = JSON.stringify({
|
|
currentGrade: studentResult?.score ?? 'N/A',
|
|
suggestedGrade: 'N/A',
|
|
reason: 'AI review could not be generated. Manual review recommended.',
|
|
discrepancies: ['AI analysis unavailable — please review manually'],
|
|
overallAssessment: 'AI audit could not be completed due to a technical error. Please review the grading manually.',
|
|
});
|
|
}
|
|
|
|
// Count discrepancies
|
|
try {
|
|
const parsed = typeof reviewResult === 'string' ? JSON.parse(reviewResult) : reviewResult;
|
|
discrepanciesFound = Array.isArray(parsed.discrepancies) ? parsed.discrepancies.length : 0;
|
|
} catch {
|
|
discrepanciesFound = 0;
|
|
}
|
|
|
|
// Create or update the grading review
|
|
const review = await db.teacherGradingReview.create({
|
|
data: {
|
|
schoolId,
|
|
assessmentId,
|
|
teacherId: session.userId,
|
|
aiProvider: 'pollination',
|
|
status: 'completed',
|
|
reviewResult,
|
|
discrepanciesFound,
|
|
},
|
|
include: {
|
|
assessment: { select: { id: true, title: true } },
|
|
teacher: { select: { id: true, firstName: true, lastName: true } },
|
|
comments: {
|
|
include: {
|
|
user: { select: { id: true, firstName: true, lastName: true } },
|
|
},
|
|
orderBy: { createdAt: 'desc' },
|
|
},
|
|
},
|
|
});
|
|
|
|
return NextResponse.json(review, { status: 201 });
|
|
} catch (error) {
|
|
console.error('AI Grading Audit POST error:', error);
|
|
return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
|
|
}
|
|
}
|