[interface-change] Phase 4: Extract shared types + fix Docker context

Shared types extracted to shared/types/:
- companion.ts (33+ types, was 100% duplicated admin-lehrer ↔ studio-v2)
- klausur.ts (18+ types, was 95% duplicated across 4 locations)
- ocr-labeling.ts (11 types, was 100% duplicated admin-lehrer ↔ website)

Original type files replaced with re-exports for backward compat.
tsconfig.json paths updated with @shared/* alias in all 3 services.

Docker: Changed build context from ./service to . (root) so shared/
is accessible. Dockerfiles updated to COPY service/ + shared/.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-04-25 15:52:19 +02:00
parent dc60233262
commit a317bd6164
23 changed files with 976 additions and 1983 deletions

View File

@@ -1,257 +1 @@
// TypeScript Interfaces fuer Korrekturplattform (Studio v2)
export interface Klausur {
id: string
title: string
subject: string
year: number
semester: string
modus: 'landes_abitur' | 'vorabitur'
eh_id?: string
created_at: string
student_count?: number
completed_count?: number
status?: 'draft' | 'in_progress' | 'completed'
}
export interface StudentWork {
id: string
klausur_id: string
anonym_id: string
file_path: string
file_type: 'pdf' | 'image'
ocr_text: string
criteria_scores: CriteriaScores
gutachten: string
status: StudentStatus
raw_points: number
grade_points: number
grade_label?: string
created_at: string
examiner_id?: string
second_examiner_id?: string
second_examiner_grade?: number
}
export type StudentStatus =
| 'UPLOADED'
| 'OCR_PROCESSING'
| 'OCR_COMPLETE'
| 'ANALYZING'
| 'FIRST_EXAMINER'
| 'SECOND_EXAMINER'
| 'COMPLETED'
| 'ERROR'
export interface CriteriaScores {
rechtschreibung?: number
grammatik?: number
inhalt?: number
struktur?: number
stil?: number
[key: string]: number | undefined
}
export interface Criterion {
id: string
name: string
weight: number
description?: string
}
export interface GradeInfo {
thresholds: Record<number, number>
labels: Record<number, string>
criteria: Record<string, Criterion>
}
export interface Annotation {
id: string
student_work_id: string
page: number
position: AnnotationPosition
type: AnnotationType
text: string
severity: 'minor' | 'major' | 'critical'
suggestion?: string
created_by: string
created_at: string
role: 'first_examiner' | 'second_examiner'
linked_criterion?: string
}
export interface AnnotationPosition {
x: number // Prozent (0-100)
y: number // Prozent (0-100)
width: number // Prozent (0-100)
height: number // Prozent (0-100)
}
export type AnnotationType =
| 'rechtschreibung'
| 'grammatik'
| 'inhalt'
| 'struktur'
| 'stil'
| 'comment'
| 'highlight'
export interface FairnessAnalysis {
klausur_id: string
student_count: number
average_grade: number
std_deviation: number
spread: number
outliers: OutlierInfo[]
criteria_analysis: Record<string, CriteriaStats>
fairness_score: number
warnings: string[]
}
export interface OutlierInfo {
student_id: string
anonym_id: string
grade_points: number
deviation: number
reason: string
}
export interface CriteriaStats {
min: number
max: number
average: number
std_deviation: number
}
export interface EHSuggestion {
criterion: string
excerpt: string
relevance_score: number
source_chunk_id: string
// Attribution fields (CTRL-SRC-002)
source_document?: string
source_url?: string
license?: string
license_url?: string
publisher?: string
}
// Default Attribution for NiBiS documents (CTRL-SRC-002)
export const NIBIS_ATTRIBUTION = {
publisher: 'Niedersaechsischer Bildungsserver (NiBiS)',
license: 'DL-DE-BY-2.0',
license_url: 'https://www.govdata.de/dl-de/by-2-0',
source_url: 'https://nibis.de',
}
export interface GutachtenSection {
title: string
content: string
evidence_links?: string[]
}
export interface Gutachten {
einleitung: string
hauptteil: string
fazit: string
staerken: string[]
schwaechen: string[]
generated_at?: string
}
// API Response Types
export interface KlausurenResponse {
klausuren: Klausur[]
total: number
}
export interface StudentsResponse {
students: StudentWork[]
total: number
}
export interface AnnotationsResponse {
annotations: Annotation[]
}
// Create/Update Types
export interface CreateKlausurData {
title: string
subject?: string
year?: number
semester?: string
modus?: 'landes_abitur' | 'vorabitur'
}
// Color mapping for annotation types
export const ANNOTATION_COLORS: Record<AnnotationType, string> = {
rechtschreibung: '#dc2626', // Red
grammatik: '#2563eb', // Blue
inhalt: '#16a34a', // Green
struktur: '#9333ea', // Purple
stil: '#ea580c', // Orange
comment: '#6b7280', // Gray
highlight: '#eab308', // Yellow
}
// Status colors
export const STATUS_COLORS: Record<StudentStatus, string> = {
UPLOADED: '#6b7280',
OCR_PROCESSING: '#eab308',
OCR_COMPLETE: '#3b82f6',
ANALYZING: '#8b5cf6',
FIRST_EXAMINER: '#f97316',
SECOND_EXAMINER: '#06b6d4',
COMPLETED: '#22c55e',
ERROR: '#ef4444',
}
export const STATUS_LABELS: Record<StudentStatus, string> = {
UPLOADED: 'Hochgeladen',
OCR_PROCESSING: 'OCR laeuft',
OCR_COMPLETE: 'OCR fertig',
ANALYZING: 'Analyse laeuft',
FIRST_EXAMINER: 'Erstkorrektur',
SECOND_EXAMINER: 'Zweitkorrektur',
COMPLETED: 'Abgeschlossen',
ERROR: 'Fehler',
}
// Default criteria with weights (NI standard)
export const DEFAULT_CRITERIA: Record<string, { name: string; weight: number }> = {
rechtschreibung: { name: 'Rechtschreibung', weight: 15 },
grammatik: { name: 'Grammatik', weight: 15 },
inhalt: { name: 'Inhalt', weight: 40 },
struktur: { name: 'Struktur', weight: 15 },
stil: { name: 'Stil', weight: 15 },
}
// Grade thresholds (15-point system)
export const GRADE_THRESHOLDS: Record<number, number> = {
15: 95, 14: 90, 13: 85, 12: 80, 11: 75,
10: 70, 9: 65, 8: 60, 7: 55, 6: 50,
5: 45, 4: 40, 3: 33, 2: 27, 1: 20, 0: 0
}
// Helper function to calculate grade from percentage
export function calculateGrade(percentage: number): number {
for (const [grade, threshold] of Object.entries(GRADE_THRESHOLDS).sort((a, b) => Number(b[0]) - Number(a[0]))) {
if (percentage >= threshold) {
return Number(grade)
}
}
return 0
}
// Helper function to get grade label
export function getGradeLabel(points: number): string {
const labels: Record<number, string> = {
15: '1+', 14: '1', 13: '1-',
12: '2+', 11: '2', 10: '2-',
9: '3+', 8: '3', 7: '3-',
6: '4+', 5: '4', 4: '4-',
3: '5+', 2: '5', 1: '5-',
0: '6'
}
return labels[points] || String(points)
}
export * from '../../../shared/types/klausur'