[split-required] Split remaining 500-680 LOC files (final batch)

website (17 pages + 3 components):
- multiplayer/wizard, middleware/wizard+test-wizard, communication
- builds/wizard, staff-search, voice, sbom/wizard
- foerderantrag, mail/tasks, tools/communication, sbom
- compliance/evidence, uni-crawler, brandbook (already done)
- CollectionsTab, IngestionTab, RiskHeatmap

backend-lehrer (5 files):
- letters_api (641 → 2), certificates_api (636 → 2)
- alerts_agent/db/models (636 → 3)
- llm_gateway/communication_service (614 → 2)
- game/database already done in prior batch

klausur-service (2 files):
- hybrid_vocab_extractor (664 → 2)
- klausur-service/frontend: api.ts (620 → 3), EHUploadWizard (591 → 2)

voice-service (3 files):
- bqas/rag_judge (618 → 3), runner (529 → 2)
- enhanced_task_orchestrator (519 → 2)

studio-v2 (6 files):
- korrektur/[klausurId] (578 → 4), fairness (569 → 2)
- AlertsWizard (552 → 2), OnboardingWizard (513 → 2)
- korrektur/api.ts (506 → 3), geo-lernwelt (501 → 2)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-04-25 08:56:45 +02:00
parent b4613e26f3
commit 451365a312
115 changed files with 10694 additions and 13839 deletions

View File

@@ -0,0 +1,137 @@
/**
* Korrektur Archiv API - NiBiS Zentralabitur Documents and Stats
*
* Split from api.ts to stay under 500 LOC.
*/
import { getApiBase, apiFetch, getFairnessAnalysis, getKlausuren } from './api-core'
// ============================================================================
// ARCHIV API (NiBiS Zentralabitur Documents)
// ============================================================================
export interface ArchivDokument {
id: string
title: string
subject: string
niveau: string
year: number
task_number?: number
doc_type: string
variant?: string
bundesland: string
minio_path?: string
preview_url?: string
}
export interface ArchivSearchResponse {
total: number
documents: ArchivDokument[]
filters: {
subjects: string[]
years: number[]
niveaus: string[]
doc_types: string[]
}
}
export interface ArchivFilters {
subject?: string
year?: number
bundesland?: string
niveau?: string
doc_type?: string
search?: string
limit?: number
offset?: number
}
export async function getArchivDocuments(filters: ArchivFilters = {}): Promise<ArchivSearchResponse> {
const params = new URLSearchParams()
if (filters.subject && filters.subject !== 'Alle') params.append('subject', filters.subject)
if (filters.year) params.append('year', filters.year.toString())
if (filters.bundesland && filters.bundesland !== 'Alle') params.append('bundesland', filters.bundesland)
if (filters.niveau && filters.niveau !== 'Alle') params.append('niveau', filters.niveau)
if (filters.doc_type && filters.doc_type !== 'Alle') params.append('doc_type', filters.doc_type)
if (filters.search) params.append('search', filters.search)
if (filters.limit) params.append('limit', filters.limit.toString())
if (filters.offset) params.append('offset', filters.offset.toString())
const queryString = params.toString()
return apiFetch<ArchivSearchResponse>(`/api/v1/archiv${queryString ? `?${queryString}` : ''}`)
}
export async function getArchivDocument(docId: string): Promise<ArchivDokument & { text_preview?: string }> {
return apiFetch<ArchivDokument & { text_preview?: string }>(`/api/v1/archiv/${docId}`)
}
export async function getArchivDocumentUrl(docId: string, expires: number = 3600): Promise<{ url: string; expires_in: number; filename: string }> {
return apiFetch<{ url: string; expires_in: number; filename: string }>(`/api/v1/archiv/${docId}/url?expires=${expires}`)
}
export async function searchArchivSemantic(
query: string,
options: { year?: number; subject?: string; niveau?: string; limit?: number } = {}
): Promise<Array<{ id: string; score: number; text: string; year: number; subject: string; niveau: string; task_number?: number; doc_type: string }>> {
const params = new URLSearchParams({ query })
if (options.year) params.append('year', options.year.toString())
if (options.subject) params.append('subject', options.subject)
if (options.niveau) params.append('niveau', options.niveau)
if (options.limit) params.append('limit', options.limit.toString())
return apiFetch(`/api/v1/archiv/search/semantic?${params.toString()}`)
}
export async function getArchivSuggestions(query: string): Promise<Array<{ label: string; type: string }>> {
return apiFetch<Array<{ label: string; type: string }>>(`/api/v1/archiv/suggest?query=${encodeURIComponent(query)}`)
}
export async function getArchivStats(): Promise<{
total_documents: number; total_chunks: number;
by_year: Record<string, number>; by_subject: Record<string, number>; by_niveau: Record<string, number>;
}> {
return apiFetch('/api/v1/archiv/stats')
}
// ============================================================================
// STATS API (for Dashboard)
// ============================================================================
export interface KorrekturStats {
totalKlausuren: number
totalStudents: number
openCorrections: number
completedThisWeek: number
averageGrade: number
timeSavedHours: number
}
export async function getKorrekturStats(): Promise<KorrekturStats> {
try {
const klausuren = await getKlausuren()
let totalStudents = 0, openCorrections = 0, completedCount = 0, gradeSum = 0, gradedCount = 0
for (const klausur of klausuren) {
totalStudents += klausur.student_count || 0
completedCount += klausur.completed_count || 0
openCorrections += (klausur.student_count || 0) - (klausur.completed_count || 0)
}
for (const klausur of klausuren) {
if (klausur.status === 'completed' || klausur.status === 'in_progress') {
try {
const fairness = await getFairnessAnalysis(klausur.id)
if (fairness.average_grade > 0) { gradeSum += fairness.average_grade; gradedCount++ }
} catch { /* Skip */ }
}
}
const averageGrade = gradedCount > 0 ? gradeSum / gradedCount : 0
return {
totalKlausuren: klausuren.length, totalStudents, openCorrections,
completedThisWeek: completedCount,
averageGrade: Math.round(averageGrade * 10) / 10,
timeSavedHours: Math.round(completedCount * 0.5),
}
} catch {
return { totalKlausuren: 0, totalStudents: 0, openCorrections: 0, completedThisWeek: 0, averageGrade: 0, timeSavedHours: 0 }
}
}