/** * Korrektur Core API - Base functions and CRUD operations * * Split from api.ts. This module contains the base fetch wrapper * and all core Klausur/Student/Annotation/Fairness operations. */ import type { Klausur, StudentWork, CriteriaScores, Annotation, AnnotationPosition, AnnotationType, FairnessAnalysis, EHSuggestion, GradeInfo, CreateKlausurData, } from '@/app/korrektur/types' export const getApiBase = (): string => { if (typeof window === 'undefined') return 'http://localhost:8086' const { hostname } = window.location return hostname === 'localhost' ? 'http://localhost:8086' : '/klausur-api' } export async function apiFetch(endpoint: string, options: RequestInit = {}): Promise { const url = `${getApiBase()}${endpoint}` const response = await fetch(url, { ...options, headers: { 'Content-Type': 'application/json', ...options.headers } }) if (!response.ok) { const errorData = await response.json().catch(() => ({ detail: 'Unknown error' })) throw new Error(errorData.detail || `HTTP ${response.status}`) } return response.json() } // Klausuren export async function getKlausuren(): Promise { const data = await apiFetch<{ klausuren: Klausur[] }>('/api/v1/klausuren') return data.klausuren || [] } export async function getKlausur(id: string): Promise { return apiFetch(`/api/v1/klausuren/${id}`) } export async function createKlausur(data: CreateKlausurData): Promise { return apiFetch('/api/v1/klausuren', { method: 'POST', body: JSON.stringify(data) }) } export async function deleteKlausur(id: string): Promise { await apiFetch(`/api/v1/klausuren/${id}`, { method: 'DELETE' }) } // Students export async function getStudents(klausurId: string): Promise { const data = await apiFetch<{ students: StudentWork[] }>(`/api/v1/klausuren/${klausurId}/students`) return data.students || [] } export async function getStudent(studentId: string): Promise { return apiFetch(`/api/v1/students/${studentId}`) } export async function uploadStudentWork(klausurId: string, file: File, anonymId: string): Promise { const formData = new FormData() formData.append('file', file) formData.append('anonym_id', anonymId) const response = await fetch(`${getApiBase()}/api/v1/klausuren/${klausurId}/students`, { method: 'POST', body: formData }) if (!response.ok) { const errorData = await response.json().catch(() => ({ detail: 'Upload failed' })) throw new Error(errorData.detail || `HTTP ${response.status}`) } return response.json() } export async function deleteStudent(studentId: string): Promise { await apiFetch(`/api/v1/students/${studentId}`, { method: 'DELETE' }) } // Criteria & Gutachten export async function updateCriteria(studentId: string, criteria: CriteriaScores): Promise { return apiFetch(`/api/v1/students/${studentId}/criteria`, { method: 'PUT', body: JSON.stringify({ criteria_scores: criteria }) }) } export async function updateGutachten(studentId: string, gutachten: string): Promise { return apiFetch(`/api/v1/students/${studentId}/gutachten`, { method: 'PUT', body: JSON.stringify({ gutachten }) }) } export async function generateGutachten(studentId: string): Promise<{ gutachten: string }> { return apiFetch<{ gutachten: string }>(`/api/v1/students/${studentId}/gutachten/generate`, { method: 'POST' }) } // Annotations export async function getAnnotations(studentId: string): Promise { const data = await apiFetch<{ annotations: Annotation[] }>(`/api/v1/students/${studentId}/annotations`) return data.annotations || [] } export async function createAnnotation(studentId: string, annotation: { page: number; position: AnnotationPosition; type: AnnotationType; text: string; severity?: 'minor' | 'major' | 'critical'; suggestion?: string; linked_criterion?: string }): Promise { return apiFetch(`/api/v1/students/${studentId}/annotations`, { method: 'POST', body: JSON.stringify(annotation) }) } export async function updateAnnotation(annotationId: string, updates: Partial<{ text: string; severity: 'minor' | 'major' | 'critical'; suggestion: string }>): Promise { return apiFetch(`/api/v1/annotations/${annotationId}`, { method: 'PUT', body: JSON.stringify(updates) }) } export async function deleteAnnotation(annotationId: string): Promise { await apiFetch(`/api/v1/annotations/${annotationId}`, { method: 'DELETE' }) } // EH/RAG export async function getEHSuggestions(studentId: string, criterion?: string): Promise { const data = await apiFetch<{ suggestions: EHSuggestion[] }>(`/api/v1/students/${studentId}/eh-suggestions`, { method: 'POST', body: JSON.stringify({ criterion }) }) return data.suggestions || [] } export async function queryRAG(query: string, topK: number = 5): Promise<{ results: Array<{ text: string; score: number; metadata: any }> }> { return apiFetch('/api/v1/eh/rag-query', { method: 'POST', body: JSON.stringify({ query, top_k: topK }) }) } export async function uploadEH(file: File): Promise<{ id: string; name: string }> { const formData = new FormData() formData.append('file', file) const response = await fetch(`${getApiBase()}/api/v1/eh/upload`, { method: 'POST', body: formData }) if (!response.ok) { const errorData = await response.json().catch(() => ({ detail: 'EH Upload failed' })) throw new Error(errorData.detail || `HTTP ${response.status}`) } return response.json() } // Fairness & Export export async function getFairnessAnalysis(klausurId: string): Promise { return apiFetch(`/api/v1/klausuren/${klausurId}/fairness`) } export async function getGradeInfo(): Promise { return apiFetch('/api/v1/grade-info') } export function getGutachtenExportUrl(studentId: string): string { return `${getApiBase()}/api/v1/students/${studentId}/export/gutachten` } export function getAnnotationsExportUrl(studentId: string): string { return `${getApiBase()}/api/v1/students/${studentId}/export/annotations` } export function getOverviewExportUrl(klausurId: string): string { return `${getApiBase()}/api/v1/klausuren/${klausurId}/export/overview` } export function getAllGutachtenExportUrl(klausurId: string): string { return `${getApiBase()}/api/v1/klausuren/${klausurId}/export/all-gutachten` } export function getStudentFileUrl(studentId: string): string { return `${getApiBase()}/api/v1/students/${studentId}/file` }