Fix: Revert to inline shared types (Turbopack can't resolve path aliases)
Some checks failed
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-school (push) Successful in 40s
CI / test-go-edu-search (push) Successful in 30s
CI / test-python-klausur (push) Failing after 2m56s
CI / test-python-agent-core (push) Successful in 19s
CI / test-nodejs-website (push) Successful in 25s
Some checks failed
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-school (push) Successful in 40s
CI / test-go-edu-search (push) Successful in 30s
CI / test-python-klausur (push) Failing after 2m56s
CI / test-python-agent-core (push) Successful in 19s
CI / test-nodejs-website (push) Successful in 25s
Turbopack doesn't support tsconfig path aliases pointing outside the project root. Reverted to copying shared types directly into each service. The canonical source remains shared/types/*.ts, synced via scripts/sync-shared-types.sh. Changes: - Reverted docker-compose.yml contexts to ./service - Reverted Dockerfiles to simple COPY . . - Removed @shared/* from tsconfigs - Removed symlinks + .gitignore hacks - Added scripts/sync-shared-types.sh for keeping copies in sync Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2
admin-lehrer/.gitignore
vendored
2
admin-lehrer/.gitignore
vendored
@@ -1,2 +0,0 @@
|
|||||||
# Symlink to shared/ (Docker COPY handles this in container)
|
|
||||||
shared
|
|
||||||
@@ -4,14 +4,13 @@ FROM node:20-alpine AS builder
|
|||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
# Copy package files
|
# Copy package files
|
||||||
COPY admin-lehrer/package.json admin-lehrer/package-lock.json* ./
|
COPY package.json package-lock.json* ./
|
||||||
|
|
||||||
# Install dependencies
|
# Install dependencies
|
||||||
RUN npm install
|
RUN npm install
|
||||||
|
|
||||||
# Copy source code + shared types (inside project for Turbopack)
|
# Copy source code
|
||||||
COPY admin-lehrer/ .
|
COPY . .
|
||||||
COPY shared/ ./shared/
|
|
||||||
|
|
||||||
# Build arguments for environment variables
|
# Build arguments for environment variables
|
||||||
ARG NEXT_PUBLIC_API_URL
|
ARG NEXT_PUBLIC_API_URL
|
||||||
|
|||||||
@@ -1 +1,127 @@
|
|||||||
export * from '@shared/types/ocr-labeling'
|
/**
|
||||||
|
* Shared TypeScript types for OCR Labeling UI.
|
||||||
|
*
|
||||||
|
* Single source of truth used by:
|
||||||
|
* - admin-lehrer (ai/ocr-labeling)
|
||||||
|
* - website (admin/ocr-labeling)
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Available OCR Models
|
||||||
|
*
|
||||||
|
* - llama3.2-vision:11b: Vision LLM, beste Qualitaet bei Handschrift (Standard)
|
||||||
|
* - trocr: Microsoft TrOCR, schnell bei gedrucktem Text
|
||||||
|
* - paddleocr: PaddleOCR + LLM, 4x schneller durch Hybrid-Ansatz
|
||||||
|
* - donut: Document Understanding Transformer, strukturierte Dokumente
|
||||||
|
*/
|
||||||
|
export type OCRModel = 'llama3.2-vision:11b' | 'trocr' | 'paddleocr' | 'donut'
|
||||||
|
|
||||||
|
export const OCR_MODEL_INFO: Record<OCRModel, { label: string; description: string; speed: string }> = {
|
||||||
|
'llama3.2-vision:11b': {
|
||||||
|
label: 'Vision LLM',
|
||||||
|
description: 'Beste Qualitaet bei Handschrift',
|
||||||
|
speed: 'langsam',
|
||||||
|
},
|
||||||
|
trocr: {
|
||||||
|
label: 'Microsoft TrOCR',
|
||||||
|
description: 'Schnell bei gedrucktem Text',
|
||||||
|
speed: 'schnell',
|
||||||
|
},
|
||||||
|
paddleocr: {
|
||||||
|
label: 'PaddleOCR + LLM',
|
||||||
|
description: 'Hybrid-Ansatz: OCR + Strukturierung',
|
||||||
|
speed: 'sehr schnell',
|
||||||
|
},
|
||||||
|
donut: {
|
||||||
|
label: 'Donut',
|
||||||
|
description: 'Document Understanding fuer Tabellen/Formulare',
|
||||||
|
speed: 'mittel',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface OCRSession {
|
||||||
|
id: string
|
||||||
|
name: string
|
||||||
|
source_type: 'klausur' | 'handwriting_sample' | 'scan'
|
||||||
|
description?: string
|
||||||
|
ocr_model?: OCRModel
|
||||||
|
total_items: number
|
||||||
|
labeled_items: number
|
||||||
|
confirmed_items: number
|
||||||
|
corrected_items: number
|
||||||
|
skipped_items: number
|
||||||
|
created_at: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface OCRItem {
|
||||||
|
id: string
|
||||||
|
session_id: string
|
||||||
|
session_name: string
|
||||||
|
image_path: string
|
||||||
|
image_url?: string
|
||||||
|
ocr_text?: string
|
||||||
|
ocr_confidence?: number
|
||||||
|
ground_truth?: string
|
||||||
|
status: 'pending' | 'confirmed' | 'corrected' | 'skipped'
|
||||||
|
metadata?: Record<string, unknown>
|
||||||
|
created_at: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface OCRStats {
|
||||||
|
total_sessions?: number
|
||||||
|
session_id?: string
|
||||||
|
name?: string
|
||||||
|
total_items: number
|
||||||
|
labeled_items: number
|
||||||
|
confirmed_items: number
|
||||||
|
corrected_items: number
|
||||||
|
skipped_items?: number
|
||||||
|
pending_items: number
|
||||||
|
exportable_items?: number
|
||||||
|
accuracy_rate: number
|
||||||
|
avg_label_time_seconds?: number
|
||||||
|
progress_percent?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TrainingSample {
|
||||||
|
id: string
|
||||||
|
image_path: string
|
||||||
|
ground_truth: string
|
||||||
|
export_format: 'generic' | 'trocr' | 'llama_vision'
|
||||||
|
training_batch: string
|
||||||
|
exported_at?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CreateSessionRequest {
|
||||||
|
name: string
|
||||||
|
source_type: 'klausur' | 'handwriting_sample' | 'scan'
|
||||||
|
description?: string
|
||||||
|
ocr_model?: OCRModel
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ConfirmRequest {
|
||||||
|
item_id: string
|
||||||
|
label_time_seconds?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CorrectRequest {
|
||||||
|
item_id: string
|
||||||
|
ground_truth: string
|
||||||
|
label_time_seconds?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ExportRequest {
|
||||||
|
export_format: 'generic' | 'trocr' | 'llama_vision'
|
||||||
|
session_id?: string
|
||||||
|
batch_id?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UploadResult {
|
||||||
|
id: string
|
||||||
|
filename: string
|
||||||
|
image_path: string
|
||||||
|
image_hash: string
|
||||||
|
ocr_text?: string
|
||||||
|
ocr_confidence?: number
|
||||||
|
status: string
|
||||||
|
}
|
||||||
|
|||||||
@@ -12,13 +12,13 @@ export type {
|
|||||||
ActiveTab,
|
ActiveTab,
|
||||||
GradeTotals,
|
GradeTotals,
|
||||||
CriteriaScores,
|
CriteriaScores,
|
||||||
} from '@shared/types/klausur'
|
} from '../../../../types'
|
||||||
|
|
||||||
export {
|
export {
|
||||||
WORKFLOW_STATUS_LABELS,
|
WORKFLOW_STATUS_LABELS,
|
||||||
ROLE_LABELS,
|
ROLE_LABELS,
|
||||||
GRADE_LABELS,
|
GRADE_LABELS,
|
||||||
} from '@shared/types/klausur'
|
} from '../../../../types'
|
||||||
|
|
||||||
/** Same-origin proxy to avoid CORS issues */
|
/** Same-origin proxy to avoid CORS issues */
|
||||||
export const API_BASE = '/klausur-api'
|
export const API_BASE = '/klausur-api'
|
||||||
|
|||||||
@@ -8,4 +8,4 @@ export type {
|
|||||||
VorabiturEHForm,
|
VorabiturEHForm,
|
||||||
EHTemplate,
|
EHTemplate,
|
||||||
DirektuploadForm,
|
DirektuploadForm,
|
||||||
} from '@shared/types/klausur'
|
} from '../../types'
|
||||||
|
|||||||
@@ -1 +1,432 @@
|
|||||||
export * from '@shared/types/klausur'
|
/**
|
||||||
|
* Shared Klausur-Korrektur types and constants.
|
||||||
|
*
|
||||||
|
* This is the single source of truth used by:
|
||||||
|
* - admin-lehrer (education/klausur-korrektur)
|
||||||
|
* - studio-v2 (korrektur)
|
||||||
|
* - website/admin (klausur-korrektur)
|
||||||
|
* - website/lehrer (klausur-korrektur)
|
||||||
|
*/
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Core domain interfaces
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
export interface Klausur {
|
||||||
|
id: string
|
||||||
|
title: string
|
||||||
|
subject: string
|
||||||
|
year: number
|
||||||
|
semester: string
|
||||||
|
modus: KlausurModus
|
||||||
|
eh_id?: string
|
||||||
|
created_at: string
|
||||||
|
student_count?: number
|
||||||
|
completed_count?: number
|
||||||
|
status?: 'draft' | 'in_progress' | 'completed'
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Union of all modus values used across services */
|
||||||
|
export type KlausurModus = 'abitur' | 'vorabitur' | 'landes_abitur'
|
||||||
|
|
||||||
|
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>
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Annotations
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
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'
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Fairness analysis
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// EH suggestions
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
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',
|
||||||
|
} as const
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Gutachten
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
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?: KlausurModus
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Constants — annotation colors
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Constants — status colors & labels
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
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',
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Constants — criteria & grades
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/** Default criteria with weights (Niedersachsen 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 functions
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/** Calculate grade points from a percentage (0-100). */
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Human-readable label for a 15-point grade value. */
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Examiner workflow types (workspace)
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
export interface ExaminerInfo {
|
||||||
|
id: string
|
||||||
|
assigned_at: string
|
||||||
|
notes?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ExaminerResult {
|
||||||
|
grade_points: number
|
||||||
|
criteria_scores?: CriteriaScores
|
||||||
|
notes?: string
|
||||||
|
submitted_at: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ExaminerWorkflow {
|
||||||
|
student_id: string
|
||||||
|
workflow_status: string
|
||||||
|
visibility_mode: string
|
||||||
|
user_role: 'ek' | 'zk' | 'dk' | 'viewer'
|
||||||
|
first_examiner?: ExaminerInfo
|
||||||
|
second_examiner?: ExaminerInfo
|
||||||
|
third_examiner?: ExaminerInfo
|
||||||
|
first_result?: ExaminerResult
|
||||||
|
first_result_visible?: boolean
|
||||||
|
second_result?: ExaminerResult
|
||||||
|
third_result?: ExaminerResult
|
||||||
|
grade_difference?: number
|
||||||
|
final_grade?: number
|
||||||
|
consensus_reached?: boolean
|
||||||
|
consensus_type?: string
|
||||||
|
einigung?: {
|
||||||
|
final_grade: number
|
||||||
|
notes: string
|
||||||
|
type: string
|
||||||
|
submitted_by: string
|
||||||
|
submitted_at: string
|
||||||
|
ek_grade: number
|
||||||
|
zk_grade: number
|
||||||
|
}
|
||||||
|
drittkorrektur_reason?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ActiveTab = 'kriterien' | 'gutachten' | 'annotationen' | 'eh-vorschlaege'
|
||||||
|
|
||||||
|
export interface GradeTotals {
|
||||||
|
raw: number
|
||||||
|
weighted: number
|
||||||
|
gradePoints: number
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Constants — workflow status & roles
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
export const GRADE_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',
|
||||||
|
}
|
||||||
|
|
||||||
|
export const WORKFLOW_STATUS_LABELS: Record<string, { label: string; color: string }> = {
|
||||||
|
not_started: { label: 'Nicht gestartet', color: 'bg-slate-100 text-slate-700' },
|
||||||
|
ek_in_progress: { label: 'EK in Arbeit', color: 'bg-blue-100 text-blue-700' },
|
||||||
|
ek_completed: { label: 'EK abgeschlossen', color: 'bg-blue-200 text-blue-800' },
|
||||||
|
zk_assigned: { label: 'ZK zugewiesen', color: 'bg-amber-100 text-amber-700' },
|
||||||
|
zk_in_progress: { label: 'ZK in Arbeit', color: 'bg-amber-200 text-amber-800' },
|
||||||
|
zk_completed: { label: 'ZK abgeschlossen', color: 'bg-amber-300 text-amber-900' },
|
||||||
|
einigung_required: { label: 'Einigung erforderlich', color: 'bg-orange-100 text-orange-700' },
|
||||||
|
einigung_completed: { label: 'Einigung abgeschlossen', color: 'bg-green-100 text-green-700' },
|
||||||
|
drittkorrektur_required: { label: 'DK erforderlich', color: 'bg-red-100 text-red-700' },
|
||||||
|
drittkorrektur_assigned: { label: 'DK zugewiesen', color: 'bg-red-200 text-red-800' },
|
||||||
|
drittkorrektur_in_progress: { label: 'DK in Arbeit', color: 'bg-red-300 text-red-900' },
|
||||||
|
completed: { label: 'Abgeschlossen', color: 'bg-green-200 text-green-800' },
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ROLE_LABELS: Record<string, { label: string; color: string }> = {
|
||||||
|
ek: { label: 'Erstkorrektor', color: 'bg-blue-500' },
|
||||||
|
zk: { label: 'Zweitkorrektor', color: 'bg-amber-500' },
|
||||||
|
dk: { label: 'Drittkorrektor', color: 'bg-purple-500' },
|
||||||
|
viewer: { label: 'Betrachter', color: 'bg-slate-500' },
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Form types (create / upload)
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
export interface CreateKlausurForm {
|
||||||
|
title: string
|
||||||
|
subject: string
|
||||||
|
year: number
|
||||||
|
semester: string
|
||||||
|
modus: 'abitur' | 'vorabitur'
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface VorabiturEHForm {
|
||||||
|
aufgabentyp: string
|
||||||
|
titel: string
|
||||||
|
text_titel: string
|
||||||
|
text_autor: string
|
||||||
|
aufgabenstellung: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface EHTemplate {
|
||||||
|
aufgabentyp: string
|
||||||
|
name: string
|
||||||
|
description: string
|
||||||
|
category: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DirektuploadForm {
|
||||||
|
files: File[]
|
||||||
|
ehFile: File | null
|
||||||
|
ehText: string
|
||||||
|
aufgabentyp: string
|
||||||
|
klausurTitle: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TabId = 'willkommen' | 'klausuren' | 'erstellen' | 'direktupload' | 'statistiken'
|
||||||
|
|||||||
@@ -1 +1,329 @@
|
|||||||
export * from '@shared/types/companion'
|
/**
|
||||||
|
* TypeScript Types for Companion Module
|
||||||
|
* Migration from Flask companion.py/companion_js.py
|
||||||
|
*/
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Phase System
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
export type PhaseId = 'einstieg' | 'erarbeitung' | 'sicherung' | 'transfer' | 'reflexion'
|
||||||
|
|
||||||
|
export interface Phase {
|
||||||
|
id: PhaseId
|
||||||
|
shortName: string // E, A, S, T, R
|
||||||
|
displayName: string
|
||||||
|
duration: number // minutes
|
||||||
|
status: 'planned' | 'active' | 'completed'
|
||||||
|
actualTime?: number // seconds (actual time spent)
|
||||||
|
color: string // hex color
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PhaseContext {
|
||||||
|
currentPhase: PhaseId
|
||||||
|
phaseDisplayName: string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Dashboard / Companion Mode
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
export interface CompanionStats {
|
||||||
|
classesCount: number
|
||||||
|
studentsCount: number
|
||||||
|
learningUnitsCreated: number
|
||||||
|
gradesEntered: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Progress {
|
||||||
|
percentage: number
|
||||||
|
completed: number
|
||||||
|
total: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export type SuggestionPriority = 'urgent' | 'high' | 'medium' | 'low'
|
||||||
|
|
||||||
|
export interface Suggestion {
|
||||||
|
id: string
|
||||||
|
title: string
|
||||||
|
description: string
|
||||||
|
priority: SuggestionPriority
|
||||||
|
icon: string // lucide icon name
|
||||||
|
actionTarget: string // navigation path
|
||||||
|
estimatedTime: number // minutes
|
||||||
|
}
|
||||||
|
|
||||||
|
export type EventType = 'exam' | 'parent_meeting' | 'deadline' | 'other'
|
||||||
|
|
||||||
|
export interface UpcomingEvent {
|
||||||
|
id: string
|
||||||
|
title: string
|
||||||
|
date: string // ISO date string
|
||||||
|
type: EventType
|
||||||
|
inDays: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CompanionData {
|
||||||
|
context: PhaseContext
|
||||||
|
stats: CompanionStats
|
||||||
|
phases: Phase[]
|
||||||
|
progress: Progress
|
||||||
|
suggestions: Suggestion[]
|
||||||
|
upcomingEvents: UpcomingEvent[]
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Lesson Mode
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
export type LessonStatus =
|
||||||
|
| 'not_started'
|
||||||
|
| 'in_progress'
|
||||||
|
| 'paused'
|
||||||
|
| 'completed'
|
||||||
|
| 'overtime'
|
||||||
|
|
||||||
|
export interface LessonPhase {
|
||||||
|
phase: PhaseId
|
||||||
|
duration: number // planned duration in minutes
|
||||||
|
status: 'planned' | 'active' | 'completed' | 'skipped'
|
||||||
|
actualTime: number // actual time spent in seconds
|
||||||
|
startedAt?: string // ISO timestamp
|
||||||
|
completedAt?: string // ISO timestamp
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Homework {
|
||||||
|
id: string
|
||||||
|
title: string
|
||||||
|
description?: string
|
||||||
|
dueDate: string // ISO date
|
||||||
|
attachments?: string[]
|
||||||
|
completed?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Material {
|
||||||
|
id: string
|
||||||
|
title: string
|
||||||
|
type: 'document' | 'video' | 'presentation' | 'link' | 'other'
|
||||||
|
url?: string
|
||||||
|
fileName?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LessonReflection {
|
||||||
|
rating: number // 1-5 stars
|
||||||
|
notes: string
|
||||||
|
nextSteps: string
|
||||||
|
savedAt?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LessonSession {
|
||||||
|
sessionId: string
|
||||||
|
classId: string
|
||||||
|
className: string
|
||||||
|
subject: string
|
||||||
|
topic?: string
|
||||||
|
startTime: string // ISO timestamp
|
||||||
|
endTime?: string // ISO timestamp
|
||||||
|
phases: LessonPhase[]
|
||||||
|
totalPlannedDuration: number // minutes
|
||||||
|
currentPhaseIndex: number
|
||||||
|
elapsedTime: number // seconds
|
||||||
|
isPaused: boolean
|
||||||
|
pausedAt?: string
|
||||||
|
pauseDuration: number // total pause time in seconds
|
||||||
|
overtimeMinutes: number
|
||||||
|
status: LessonStatus
|
||||||
|
homeworkList: Homework[]
|
||||||
|
materials: Material[]
|
||||||
|
reflection?: LessonReflection
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Lesson Templates
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
export interface PhaseDurations {
|
||||||
|
einstieg: number
|
||||||
|
erarbeitung: number
|
||||||
|
sicherung: number
|
||||||
|
transfer: number
|
||||||
|
reflexion: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LessonTemplate {
|
||||||
|
templateId: string
|
||||||
|
name: string
|
||||||
|
description?: string
|
||||||
|
subject?: string
|
||||||
|
durations: PhaseDurations
|
||||||
|
isSystemTemplate: boolean
|
||||||
|
createdBy?: string
|
||||||
|
createdAt?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Settings
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
export interface TeacherSettings {
|
||||||
|
defaultPhaseDurations: PhaseDurations
|
||||||
|
preferredLessonLength: number // minutes (default 45)
|
||||||
|
autoAdvancePhases: boolean
|
||||||
|
soundNotifications: boolean
|
||||||
|
showKeyboardShortcuts: boolean
|
||||||
|
highContrastMode: boolean
|
||||||
|
onboardingCompleted: boolean
|
||||||
|
selectedTemplateId?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Timer State
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
export type TimerColorStatus = 'plenty' | 'warning' | 'critical' | 'overtime'
|
||||||
|
|
||||||
|
export interface TimerState {
|
||||||
|
isRunning: boolean
|
||||||
|
isPaused: boolean
|
||||||
|
elapsedSeconds: number
|
||||||
|
remainingSeconds: number
|
||||||
|
totalSeconds: number
|
||||||
|
progress: number // 0-1
|
||||||
|
colorStatus: TimerColorStatus
|
||||||
|
currentPhase: LessonPhase | null
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Forms
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
export interface LessonStartFormData {
|
||||||
|
classId: string
|
||||||
|
subject: string
|
||||||
|
topic?: string
|
||||||
|
templateId?: string
|
||||||
|
customDurations?: PhaseDurations
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Class {
|
||||||
|
id: string
|
||||||
|
name: string
|
||||||
|
grade: string
|
||||||
|
studentCount: number
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Feedback
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
export type FeedbackType = 'bug' | 'feature' | 'feedback'
|
||||||
|
|
||||||
|
export interface FeedbackSubmission {
|
||||||
|
type: FeedbackType
|
||||||
|
title: string
|
||||||
|
description: string
|
||||||
|
screenshot?: string // base64
|
||||||
|
sessionId?: string
|
||||||
|
metadata?: Record<string, unknown>
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Onboarding
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
export interface OnboardingStep {
|
||||||
|
step: number
|
||||||
|
title: string
|
||||||
|
description: string
|
||||||
|
completed: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface OnboardingState {
|
||||||
|
currentStep: number
|
||||||
|
totalSteps: number
|
||||||
|
steps: OnboardingStep[]
|
||||||
|
selectedState?: string // Bundesland
|
||||||
|
selectedSchoolType?: string
|
||||||
|
completed: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// WebSocket Messages
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
export type WSMessageType =
|
||||||
|
| 'phase_update'
|
||||||
|
| 'timer_tick'
|
||||||
|
| 'overtime_warning'
|
||||||
|
| 'pause_toggle'
|
||||||
|
| 'session_end'
|
||||||
|
| 'sync_request'
|
||||||
|
|
||||||
|
export interface WSMessage {
|
||||||
|
type: WSMessageType
|
||||||
|
payload: {
|
||||||
|
sessionId: string
|
||||||
|
phase?: number
|
||||||
|
elapsed?: number
|
||||||
|
isPaused?: boolean
|
||||||
|
overtimeMinutes?: number
|
||||||
|
[key: string]: unknown
|
||||||
|
}
|
||||||
|
timestamp: string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// API Responses
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
export interface APIResponse<T> {
|
||||||
|
success: boolean
|
||||||
|
data?: T
|
||||||
|
error?: string
|
||||||
|
message?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DashboardResponse extends APIResponse<CompanionData> {}
|
||||||
|
|
||||||
|
export interface LessonResponse extends APIResponse<LessonSession> {}
|
||||||
|
|
||||||
|
export interface TemplatesResponse extends APIResponse<{ templates: LessonTemplate[] }> {}
|
||||||
|
|
||||||
|
export interface SettingsResponse extends APIResponse<TeacherSettings> {}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Component Props
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
export type CompanionMode = 'companion' | 'lesson' | 'classic'
|
||||||
|
|
||||||
|
export interface ModeToggleProps {
|
||||||
|
currentMode: CompanionMode
|
||||||
|
onModeChange: (mode: CompanionMode) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PhaseTimelineProps {
|
||||||
|
phases: Phase[]
|
||||||
|
currentPhaseIndex: number
|
||||||
|
onPhaseClick?: (index: number) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface VisualPieTimerProps {
|
||||||
|
progress: number // 0-1
|
||||||
|
remainingSeconds: number
|
||||||
|
totalSeconds: number
|
||||||
|
colorStatus: TimerColorStatus
|
||||||
|
isPaused: boolean
|
||||||
|
currentPhaseName: string
|
||||||
|
phaseColor: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface QuickActionsBarProps {
|
||||||
|
onExtend: (minutes: number) => void
|
||||||
|
onPause: () => void
|
||||||
|
onResume: () => void
|
||||||
|
onSkip: () => void
|
||||||
|
isPaused: boolean
|
||||||
|
isLastPhase: boolean
|
||||||
|
disabled?: boolean
|
||||||
|
}
|
||||||
|
|||||||
@@ -24,9 +24,6 @@
|
|||||||
"paths": {
|
"paths": {
|
||||||
"@/*": [
|
"@/*": [
|
||||||
"./*"
|
"./*"
|
||||||
],
|
|
||||||
"@shared/*": [
|
|
||||||
"../shared/*"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"target": "ES2017"
|
"target": "ES2017"
|
||||||
|
|||||||
@@ -60,8 +60,8 @@ services:
|
|||||||
# =========================================================
|
# =========================================================
|
||||||
admin-lehrer:
|
admin-lehrer:
|
||||||
build:
|
build:
|
||||||
context: .
|
context: ./admin-lehrer
|
||||||
dockerfile: admin-lehrer/Dockerfile
|
dockerfile: Dockerfile
|
||||||
args:
|
args:
|
||||||
NEXT_PUBLIC_API_URL: ${NEXT_PUBLIC_API_URL:-https://macmini:8001}
|
NEXT_PUBLIC_API_URL: ${NEXT_PUBLIC_API_URL:-https://macmini:8001}
|
||||||
NEXT_PUBLIC_OLD_ADMIN_URL: ${NEXT_PUBLIC_OLD_ADMIN_URL:-http://macmini:3000/admin}
|
NEXT_PUBLIC_OLD_ADMIN_URL: ${NEXT_PUBLIC_OLD_ADMIN_URL:-http://macmini:3000/admin}
|
||||||
@@ -95,8 +95,8 @@ services:
|
|||||||
|
|
||||||
studio-v2:
|
studio-v2:
|
||||||
build:
|
build:
|
||||||
context: .
|
context: ./studio-v2
|
||||||
dockerfile: studio-v2/Dockerfile
|
dockerfile: Dockerfile
|
||||||
args:
|
args:
|
||||||
NEXT_PUBLIC_VOICE_SERVICE_URL: ${NEXT_PUBLIC_VOICE_SERVICE_URL:-wss://macmini:8091}
|
NEXT_PUBLIC_VOICE_SERVICE_URL: ${NEXT_PUBLIC_VOICE_SERVICE_URL:-wss://macmini:8091}
|
||||||
NEXT_PUBLIC_KLAUSUR_SERVICE_URL: ${NEXT_PUBLIC_KLAUSUR_SERVICE_URL:-https://macmini:8086}
|
NEXT_PUBLIC_KLAUSUR_SERVICE_URL: ${NEXT_PUBLIC_KLAUSUR_SERVICE_URL:-https://macmini:8086}
|
||||||
@@ -116,8 +116,8 @@ services:
|
|||||||
|
|
||||||
website:
|
website:
|
||||||
build:
|
build:
|
||||||
context: .
|
context: ./website
|
||||||
dockerfile: website/Dockerfile
|
dockerfile: Dockerfile
|
||||||
args:
|
args:
|
||||||
NEXT_PUBLIC_BILLING_API_URL: ${NEXT_PUBLIC_BILLING_API_URL:-https://macmini:8083}
|
NEXT_PUBLIC_BILLING_API_URL: ${NEXT_PUBLIC_BILLING_API_URL:-https://macmini:8083}
|
||||||
NEXT_PUBLIC_APP_URL: ${NEXT_PUBLIC_APP_URL:-https://macmini}
|
NEXT_PUBLIC_APP_URL: ${NEXT_PUBLIC_APP_URL:-https://macmini}
|
||||||
|
|||||||
34
scripts/sync-shared-types.sh
Executable file
34
scripts/sync-shared-types.sh
Executable file
@@ -0,0 +1,34 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
#
|
||||||
|
# Sync shared types from shared/types/ into each frontend service.
|
||||||
|
#
|
||||||
|
# The canonical source of truth is shared/types/*.ts.
|
||||||
|
# This script copies them into each service's local directory
|
||||||
|
# because Turbopack doesn't support tsconfig path aliases
|
||||||
|
# pointing outside the project root.
|
||||||
|
#
|
||||||
|
# Run this after modifying any file in shared/types/.
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
|
||||||
|
SHARED="$ROOT/shared/types"
|
||||||
|
|
||||||
|
echo "Syncing shared types from $SHARED..."
|
||||||
|
|
||||||
|
# Companion types
|
||||||
|
cp "$SHARED/companion.ts" "$ROOT/admin-lehrer/lib/companion/types.ts"
|
||||||
|
cp "$SHARED/companion.ts" "$ROOT/studio-v2/lib/companion/types.ts"
|
||||||
|
|
||||||
|
# Klausur-Korrektur types
|
||||||
|
cp "$SHARED/klausur.ts" "$ROOT/admin-lehrer/app/(admin)/education/klausur-korrektur/types.ts"
|
||||||
|
cp "$SHARED/klausur.ts" "$ROOT/studio-v2/app/korrektur/types.ts"
|
||||||
|
cp "$SHARED/klausur.ts" "$ROOT/website/app/admin/klausur-korrektur/types.ts"
|
||||||
|
cp "$SHARED/klausur.ts" "$ROOT/website/app/lehrer/klausur-korrektur/types.ts"
|
||||||
|
cp "$SHARED/klausur.ts" "$ROOT/website/components/klausur-korrektur/klausur-types.ts"
|
||||||
|
|
||||||
|
# OCR Labeling types
|
||||||
|
cp "$SHARED/ocr-labeling.ts" "$ROOT/admin-lehrer/app/(admin)/ai/ocr-labeling/types.ts"
|
||||||
|
cp "$SHARED/ocr-labeling.ts" "$ROOT/website/app/admin/ocr-labeling/types.ts"
|
||||||
|
|
||||||
|
echo "Done. Synced to 9 locations."
|
||||||
2
studio-v2/.gitignore
vendored
2
studio-v2/.gitignore
vendored
@@ -1,2 +0,0 @@
|
|||||||
# Symlink to shared/ (Docker COPY handles this in container)
|
|
||||||
shared
|
|
||||||
@@ -4,14 +4,13 @@ FROM node:20-alpine AS builder
|
|||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
# Copy package files
|
# Copy package files
|
||||||
COPY studio-v2/package.json studio-v2/package-lock.json* ./
|
COPY package.json package-lock.json* ./
|
||||||
|
|
||||||
# Install dependencies
|
# Install dependencies
|
||||||
RUN npm install
|
RUN npm install
|
||||||
|
|
||||||
# Copy source files + shared types (inside project for Turbopack)
|
# Copy source files
|
||||||
COPY studio-v2/ .
|
COPY . .
|
||||||
COPY shared/ ./shared/
|
|
||||||
|
|
||||||
# Build arguments for environment variables (needed at build time for Next.js)
|
# Build arguments for environment variables (needed at build time for Next.js)
|
||||||
ARG NEXT_PUBLIC_VOICE_SERVICE_URL
|
ARG NEXT_PUBLIC_VOICE_SERVICE_URL
|
||||||
|
|||||||
@@ -1 +1,432 @@
|
|||||||
export * from '@shared/types/klausur'
|
/**
|
||||||
|
* Shared Klausur-Korrektur types and constants.
|
||||||
|
*
|
||||||
|
* This is the single source of truth used by:
|
||||||
|
* - admin-lehrer (education/klausur-korrektur)
|
||||||
|
* - studio-v2 (korrektur)
|
||||||
|
* - website/admin (klausur-korrektur)
|
||||||
|
* - website/lehrer (klausur-korrektur)
|
||||||
|
*/
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Core domain interfaces
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
export interface Klausur {
|
||||||
|
id: string
|
||||||
|
title: string
|
||||||
|
subject: string
|
||||||
|
year: number
|
||||||
|
semester: string
|
||||||
|
modus: KlausurModus
|
||||||
|
eh_id?: string
|
||||||
|
created_at: string
|
||||||
|
student_count?: number
|
||||||
|
completed_count?: number
|
||||||
|
status?: 'draft' | 'in_progress' | 'completed'
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Union of all modus values used across services */
|
||||||
|
export type KlausurModus = 'abitur' | 'vorabitur' | 'landes_abitur'
|
||||||
|
|
||||||
|
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>
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Annotations
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
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'
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Fairness analysis
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// EH suggestions
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
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',
|
||||||
|
} as const
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Gutachten
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
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?: KlausurModus
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Constants — annotation colors
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Constants — status colors & labels
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
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',
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Constants — criteria & grades
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/** Default criteria with weights (Niedersachsen 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 functions
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/** Calculate grade points from a percentage (0-100). */
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Human-readable label for a 15-point grade value. */
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Examiner workflow types (workspace)
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
export interface ExaminerInfo {
|
||||||
|
id: string
|
||||||
|
assigned_at: string
|
||||||
|
notes?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ExaminerResult {
|
||||||
|
grade_points: number
|
||||||
|
criteria_scores?: CriteriaScores
|
||||||
|
notes?: string
|
||||||
|
submitted_at: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ExaminerWorkflow {
|
||||||
|
student_id: string
|
||||||
|
workflow_status: string
|
||||||
|
visibility_mode: string
|
||||||
|
user_role: 'ek' | 'zk' | 'dk' | 'viewer'
|
||||||
|
first_examiner?: ExaminerInfo
|
||||||
|
second_examiner?: ExaminerInfo
|
||||||
|
third_examiner?: ExaminerInfo
|
||||||
|
first_result?: ExaminerResult
|
||||||
|
first_result_visible?: boolean
|
||||||
|
second_result?: ExaminerResult
|
||||||
|
third_result?: ExaminerResult
|
||||||
|
grade_difference?: number
|
||||||
|
final_grade?: number
|
||||||
|
consensus_reached?: boolean
|
||||||
|
consensus_type?: string
|
||||||
|
einigung?: {
|
||||||
|
final_grade: number
|
||||||
|
notes: string
|
||||||
|
type: string
|
||||||
|
submitted_by: string
|
||||||
|
submitted_at: string
|
||||||
|
ek_grade: number
|
||||||
|
zk_grade: number
|
||||||
|
}
|
||||||
|
drittkorrektur_reason?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ActiveTab = 'kriterien' | 'gutachten' | 'annotationen' | 'eh-vorschlaege'
|
||||||
|
|
||||||
|
export interface GradeTotals {
|
||||||
|
raw: number
|
||||||
|
weighted: number
|
||||||
|
gradePoints: number
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Constants — workflow status & roles
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
export const GRADE_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',
|
||||||
|
}
|
||||||
|
|
||||||
|
export const WORKFLOW_STATUS_LABELS: Record<string, { label: string; color: string }> = {
|
||||||
|
not_started: { label: 'Nicht gestartet', color: 'bg-slate-100 text-slate-700' },
|
||||||
|
ek_in_progress: { label: 'EK in Arbeit', color: 'bg-blue-100 text-blue-700' },
|
||||||
|
ek_completed: { label: 'EK abgeschlossen', color: 'bg-blue-200 text-blue-800' },
|
||||||
|
zk_assigned: { label: 'ZK zugewiesen', color: 'bg-amber-100 text-amber-700' },
|
||||||
|
zk_in_progress: { label: 'ZK in Arbeit', color: 'bg-amber-200 text-amber-800' },
|
||||||
|
zk_completed: { label: 'ZK abgeschlossen', color: 'bg-amber-300 text-amber-900' },
|
||||||
|
einigung_required: { label: 'Einigung erforderlich', color: 'bg-orange-100 text-orange-700' },
|
||||||
|
einigung_completed: { label: 'Einigung abgeschlossen', color: 'bg-green-100 text-green-700' },
|
||||||
|
drittkorrektur_required: { label: 'DK erforderlich', color: 'bg-red-100 text-red-700' },
|
||||||
|
drittkorrektur_assigned: { label: 'DK zugewiesen', color: 'bg-red-200 text-red-800' },
|
||||||
|
drittkorrektur_in_progress: { label: 'DK in Arbeit', color: 'bg-red-300 text-red-900' },
|
||||||
|
completed: { label: 'Abgeschlossen', color: 'bg-green-200 text-green-800' },
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ROLE_LABELS: Record<string, { label: string; color: string }> = {
|
||||||
|
ek: { label: 'Erstkorrektor', color: 'bg-blue-500' },
|
||||||
|
zk: { label: 'Zweitkorrektor', color: 'bg-amber-500' },
|
||||||
|
dk: { label: 'Drittkorrektor', color: 'bg-purple-500' },
|
||||||
|
viewer: { label: 'Betrachter', color: 'bg-slate-500' },
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Form types (create / upload)
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
export interface CreateKlausurForm {
|
||||||
|
title: string
|
||||||
|
subject: string
|
||||||
|
year: number
|
||||||
|
semester: string
|
||||||
|
modus: 'abitur' | 'vorabitur'
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface VorabiturEHForm {
|
||||||
|
aufgabentyp: string
|
||||||
|
titel: string
|
||||||
|
text_titel: string
|
||||||
|
text_autor: string
|
||||||
|
aufgabenstellung: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface EHTemplate {
|
||||||
|
aufgabentyp: string
|
||||||
|
name: string
|
||||||
|
description: string
|
||||||
|
category: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DirektuploadForm {
|
||||||
|
files: File[]
|
||||||
|
ehFile: File | null
|
||||||
|
ehText: string
|
||||||
|
aufgabentyp: string
|
||||||
|
klausurTitle: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TabId = 'willkommen' | 'klausuren' | 'erstellen' | 'direktupload' | 'statistiken'
|
||||||
|
|||||||
@@ -1 +1,329 @@
|
|||||||
export * from '@shared/types/companion'
|
/**
|
||||||
|
* TypeScript Types for Companion Module
|
||||||
|
* Migration from Flask companion.py/companion_js.py
|
||||||
|
*/
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Phase System
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
export type PhaseId = 'einstieg' | 'erarbeitung' | 'sicherung' | 'transfer' | 'reflexion'
|
||||||
|
|
||||||
|
export interface Phase {
|
||||||
|
id: PhaseId
|
||||||
|
shortName: string // E, A, S, T, R
|
||||||
|
displayName: string
|
||||||
|
duration: number // minutes
|
||||||
|
status: 'planned' | 'active' | 'completed'
|
||||||
|
actualTime?: number // seconds (actual time spent)
|
||||||
|
color: string // hex color
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PhaseContext {
|
||||||
|
currentPhase: PhaseId
|
||||||
|
phaseDisplayName: string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Dashboard / Companion Mode
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
export interface CompanionStats {
|
||||||
|
classesCount: number
|
||||||
|
studentsCount: number
|
||||||
|
learningUnitsCreated: number
|
||||||
|
gradesEntered: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Progress {
|
||||||
|
percentage: number
|
||||||
|
completed: number
|
||||||
|
total: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export type SuggestionPriority = 'urgent' | 'high' | 'medium' | 'low'
|
||||||
|
|
||||||
|
export interface Suggestion {
|
||||||
|
id: string
|
||||||
|
title: string
|
||||||
|
description: string
|
||||||
|
priority: SuggestionPriority
|
||||||
|
icon: string // lucide icon name
|
||||||
|
actionTarget: string // navigation path
|
||||||
|
estimatedTime: number // minutes
|
||||||
|
}
|
||||||
|
|
||||||
|
export type EventType = 'exam' | 'parent_meeting' | 'deadline' | 'other'
|
||||||
|
|
||||||
|
export interface UpcomingEvent {
|
||||||
|
id: string
|
||||||
|
title: string
|
||||||
|
date: string // ISO date string
|
||||||
|
type: EventType
|
||||||
|
inDays: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CompanionData {
|
||||||
|
context: PhaseContext
|
||||||
|
stats: CompanionStats
|
||||||
|
phases: Phase[]
|
||||||
|
progress: Progress
|
||||||
|
suggestions: Suggestion[]
|
||||||
|
upcomingEvents: UpcomingEvent[]
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Lesson Mode
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
export type LessonStatus =
|
||||||
|
| 'not_started'
|
||||||
|
| 'in_progress'
|
||||||
|
| 'paused'
|
||||||
|
| 'completed'
|
||||||
|
| 'overtime'
|
||||||
|
|
||||||
|
export interface LessonPhase {
|
||||||
|
phase: PhaseId
|
||||||
|
duration: number // planned duration in minutes
|
||||||
|
status: 'planned' | 'active' | 'completed' | 'skipped'
|
||||||
|
actualTime: number // actual time spent in seconds
|
||||||
|
startedAt?: string // ISO timestamp
|
||||||
|
completedAt?: string // ISO timestamp
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Homework {
|
||||||
|
id: string
|
||||||
|
title: string
|
||||||
|
description?: string
|
||||||
|
dueDate: string // ISO date
|
||||||
|
attachments?: string[]
|
||||||
|
completed?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Material {
|
||||||
|
id: string
|
||||||
|
title: string
|
||||||
|
type: 'document' | 'video' | 'presentation' | 'link' | 'other'
|
||||||
|
url?: string
|
||||||
|
fileName?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LessonReflection {
|
||||||
|
rating: number // 1-5 stars
|
||||||
|
notes: string
|
||||||
|
nextSteps: string
|
||||||
|
savedAt?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LessonSession {
|
||||||
|
sessionId: string
|
||||||
|
classId: string
|
||||||
|
className: string
|
||||||
|
subject: string
|
||||||
|
topic?: string
|
||||||
|
startTime: string // ISO timestamp
|
||||||
|
endTime?: string // ISO timestamp
|
||||||
|
phases: LessonPhase[]
|
||||||
|
totalPlannedDuration: number // minutes
|
||||||
|
currentPhaseIndex: number
|
||||||
|
elapsedTime: number // seconds
|
||||||
|
isPaused: boolean
|
||||||
|
pausedAt?: string
|
||||||
|
pauseDuration: number // total pause time in seconds
|
||||||
|
overtimeMinutes: number
|
||||||
|
status: LessonStatus
|
||||||
|
homeworkList: Homework[]
|
||||||
|
materials: Material[]
|
||||||
|
reflection?: LessonReflection
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Lesson Templates
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
export interface PhaseDurations {
|
||||||
|
einstieg: number
|
||||||
|
erarbeitung: number
|
||||||
|
sicherung: number
|
||||||
|
transfer: number
|
||||||
|
reflexion: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LessonTemplate {
|
||||||
|
templateId: string
|
||||||
|
name: string
|
||||||
|
description?: string
|
||||||
|
subject?: string
|
||||||
|
durations: PhaseDurations
|
||||||
|
isSystemTemplate: boolean
|
||||||
|
createdBy?: string
|
||||||
|
createdAt?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Settings
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
export interface TeacherSettings {
|
||||||
|
defaultPhaseDurations: PhaseDurations
|
||||||
|
preferredLessonLength: number // minutes (default 45)
|
||||||
|
autoAdvancePhases: boolean
|
||||||
|
soundNotifications: boolean
|
||||||
|
showKeyboardShortcuts: boolean
|
||||||
|
highContrastMode: boolean
|
||||||
|
onboardingCompleted: boolean
|
||||||
|
selectedTemplateId?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Timer State
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
export type TimerColorStatus = 'plenty' | 'warning' | 'critical' | 'overtime'
|
||||||
|
|
||||||
|
export interface TimerState {
|
||||||
|
isRunning: boolean
|
||||||
|
isPaused: boolean
|
||||||
|
elapsedSeconds: number
|
||||||
|
remainingSeconds: number
|
||||||
|
totalSeconds: number
|
||||||
|
progress: number // 0-1
|
||||||
|
colorStatus: TimerColorStatus
|
||||||
|
currentPhase: LessonPhase | null
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Forms
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
export interface LessonStartFormData {
|
||||||
|
classId: string
|
||||||
|
subject: string
|
||||||
|
topic?: string
|
||||||
|
templateId?: string
|
||||||
|
customDurations?: PhaseDurations
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Class {
|
||||||
|
id: string
|
||||||
|
name: string
|
||||||
|
grade: string
|
||||||
|
studentCount: number
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Feedback
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
export type FeedbackType = 'bug' | 'feature' | 'feedback'
|
||||||
|
|
||||||
|
export interface FeedbackSubmission {
|
||||||
|
type: FeedbackType
|
||||||
|
title: string
|
||||||
|
description: string
|
||||||
|
screenshot?: string // base64
|
||||||
|
sessionId?: string
|
||||||
|
metadata?: Record<string, unknown>
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Onboarding
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
export interface OnboardingStep {
|
||||||
|
step: number
|
||||||
|
title: string
|
||||||
|
description: string
|
||||||
|
completed: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface OnboardingState {
|
||||||
|
currentStep: number
|
||||||
|
totalSteps: number
|
||||||
|
steps: OnboardingStep[]
|
||||||
|
selectedState?: string // Bundesland
|
||||||
|
selectedSchoolType?: string
|
||||||
|
completed: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// WebSocket Messages
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
export type WSMessageType =
|
||||||
|
| 'phase_update'
|
||||||
|
| 'timer_tick'
|
||||||
|
| 'overtime_warning'
|
||||||
|
| 'pause_toggle'
|
||||||
|
| 'session_end'
|
||||||
|
| 'sync_request'
|
||||||
|
|
||||||
|
export interface WSMessage {
|
||||||
|
type: WSMessageType
|
||||||
|
payload: {
|
||||||
|
sessionId: string
|
||||||
|
phase?: number
|
||||||
|
elapsed?: number
|
||||||
|
isPaused?: boolean
|
||||||
|
overtimeMinutes?: number
|
||||||
|
[key: string]: unknown
|
||||||
|
}
|
||||||
|
timestamp: string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// API Responses
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
export interface APIResponse<T> {
|
||||||
|
success: boolean
|
||||||
|
data?: T
|
||||||
|
error?: string
|
||||||
|
message?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DashboardResponse extends APIResponse<CompanionData> {}
|
||||||
|
|
||||||
|
export interface LessonResponse extends APIResponse<LessonSession> {}
|
||||||
|
|
||||||
|
export interface TemplatesResponse extends APIResponse<{ templates: LessonTemplate[] }> {}
|
||||||
|
|
||||||
|
export interface SettingsResponse extends APIResponse<TeacherSettings> {}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Component Props
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
export type CompanionMode = 'companion' | 'lesson' | 'classic'
|
||||||
|
|
||||||
|
export interface ModeToggleProps {
|
||||||
|
currentMode: CompanionMode
|
||||||
|
onModeChange: (mode: CompanionMode) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PhaseTimelineProps {
|
||||||
|
phases: Phase[]
|
||||||
|
currentPhaseIndex: number
|
||||||
|
onPhaseClick?: (index: number) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface VisualPieTimerProps {
|
||||||
|
progress: number // 0-1
|
||||||
|
remainingSeconds: number
|
||||||
|
totalSeconds: number
|
||||||
|
colorStatus: TimerColorStatus
|
||||||
|
isPaused: boolean
|
||||||
|
currentPhaseName: string
|
||||||
|
phaseColor: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface QuickActionsBarProps {
|
||||||
|
onExtend: (minutes: number) => void
|
||||||
|
onPause: () => void
|
||||||
|
onResume: () => void
|
||||||
|
onSkip: () => void
|
||||||
|
isPaused: boolean
|
||||||
|
isLastPhase: boolean
|
||||||
|
disabled?: boolean
|
||||||
|
}
|
||||||
|
|||||||
@@ -24,9 +24,6 @@
|
|||||||
"paths": {
|
"paths": {
|
||||||
"@/*": [
|
"@/*": [
|
||||||
"./*"
|
"./*"
|
||||||
],
|
|
||||||
"@shared/*": [
|
|
||||||
"../shared/*"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"target": "ES2017"
|
"target": "ES2017"
|
||||||
|
|||||||
2
website/.gitignore
vendored
2
website/.gitignore
vendored
@@ -1,2 +0,0 @@
|
|||||||
# Symlink to shared/ (Docker COPY handles this in container)
|
|
||||||
shared
|
|
||||||
@@ -4,14 +4,13 @@ FROM node:20-alpine AS builder
|
|||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
# Copy package files
|
# Copy package files
|
||||||
COPY website/package.json website/package-lock.json* ./
|
COPY package.json package-lock.json* ./
|
||||||
|
|
||||||
# Install dependencies
|
# Install dependencies
|
||||||
RUN npm install
|
RUN npm install
|
||||||
|
|
||||||
# Copy source code + shared types (inside project for Turbopack)
|
# Copy source code
|
||||||
COPY website/ .
|
COPY . .
|
||||||
COPY shared/ ./shared/
|
|
||||||
|
|
||||||
# Build arguments for environment variables
|
# Build arguments for environment variables
|
||||||
ARG NEXT_PUBLIC_BILLING_API_URL
|
ARG NEXT_PUBLIC_BILLING_API_URL
|
||||||
|
|||||||
@@ -1 +1,432 @@
|
|||||||
export * from '@shared/types/klausur'
|
/**
|
||||||
|
* Shared Klausur-Korrektur types and constants.
|
||||||
|
*
|
||||||
|
* This is the single source of truth used by:
|
||||||
|
* - admin-lehrer (education/klausur-korrektur)
|
||||||
|
* - studio-v2 (korrektur)
|
||||||
|
* - website/admin (klausur-korrektur)
|
||||||
|
* - website/lehrer (klausur-korrektur)
|
||||||
|
*/
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Core domain interfaces
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
export interface Klausur {
|
||||||
|
id: string
|
||||||
|
title: string
|
||||||
|
subject: string
|
||||||
|
year: number
|
||||||
|
semester: string
|
||||||
|
modus: KlausurModus
|
||||||
|
eh_id?: string
|
||||||
|
created_at: string
|
||||||
|
student_count?: number
|
||||||
|
completed_count?: number
|
||||||
|
status?: 'draft' | 'in_progress' | 'completed'
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Union of all modus values used across services */
|
||||||
|
export type KlausurModus = 'abitur' | 'vorabitur' | 'landes_abitur'
|
||||||
|
|
||||||
|
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>
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Annotations
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
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'
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Fairness analysis
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// EH suggestions
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
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',
|
||||||
|
} as const
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Gutachten
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
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?: KlausurModus
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Constants — annotation colors
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Constants — status colors & labels
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
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',
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Constants — criteria & grades
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/** Default criteria with weights (Niedersachsen 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 functions
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/** Calculate grade points from a percentage (0-100). */
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Human-readable label for a 15-point grade value. */
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Examiner workflow types (workspace)
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
export interface ExaminerInfo {
|
||||||
|
id: string
|
||||||
|
assigned_at: string
|
||||||
|
notes?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ExaminerResult {
|
||||||
|
grade_points: number
|
||||||
|
criteria_scores?: CriteriaScores
|
||||||
|
notes?: string
|
||||||
|
submitted_at: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ExaminerWorkflow {
|
||||||
|
student_id: string
|
||||||
|
workflow_status: string
|
||||||
|
visibility_mode: string
|
||||||
|
user_role: 'ek' | 'zk' | 'dk' | 'viewer'
|
||||||
|
first_examiner?: ExaminerInfo
|
||||||
|
second_examiner?: ExaminerInfo
|
||||||
|
third_examiner?: ExaminerInfo
|
||||||
|
first_result?: ExaminerResult
|
||||||
|
first_result_visible?: boolean
|
||||||
|
second_result?: ExaminerResult
|
||||||
|
third_result?: ExaminerResult
|
||||||
|
grade_difference?: number
|
||||||
|
final_grade?: number
|
||||||
|
consensus_reached?: boolean
|
||||||
|
consensus_type?: string
|
||||||
|
einigung?: {
|
||||||
|
final_grade: number
|
||||||
|
notes: string
|
||||||
|
type: string
|
||||||
|
submitted_by: string
|
||||||
|
submitted_at: string
|
||||||
|
ek_grade: number
|
||||||
|
zk_grade: number
|
||||||
|
}
|
||||||
|
drittkorrektur_reason?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ActiveTab = 'kriterien' | 'gutachten' | 'annotationen' | 'eh-vorschlaege'
|
||||||
|
|
||||||
|
export interface GradeTotals {
|
||||||
|
raw: number
|
||||||
|
weighted: number
|
||||||
|
gradePoints: number
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Constants — workflow status & roles
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
export const GRADE_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',
|
||||||
|
}
|
||||||
|
|
||||||
|
export const WORKFLOW_STATUS_LABELS: Record<string, { label: string; color: string }> = {
|
||||||
|
not_started: { label: 'Nicht gestartet', color: 'bg-slate-100 text-slate-700' },
|
||||||
|
ek_in_progress: { label: 'EK in Arbeit', color: 'bg-blue-100 text-blue-700' },
|
||||||
|
ek_completed: { label: 'EK abgeschlossen', color: 'bg-blue-200 text-blue-800' },
|
||||||
|
zk_assigned: { label: 'ZK zugewiesen', color: 'bg-amber-100 text-amber-700' },
|
||||||
|
zk_in_progress: { label: 'ZK in Arbeit', color: 'bg-amber-200 text-amber-800' },
|
||||||
|
zk_completed: { label: 'ZK abgeschlossen', color: 'bg-amber-300 text-amber-900' },
|
||||||
|
einigung_required: { label: 'Einigung erforderlich', color: 'bg-orange-100 text-orange-700' },
|
||||||
|
einigung_completed: { label: 'Einigung abgeschlossen', color: 'bg-green-100 text-green-700' },
|
||||||
|
drittkorrektur_required: { label: 'DK erforderlich', color: 'bg-red-100 text-red-700' },
|
||||||
|
drittkorrektur_assigned: { label: 'DK zugewiesen', color: 'bg-red-200 text-red-800' },
|
||||||
|
drittkorrektur_in_progress: { label: 'DK in Arbeit', color: 'bg-red-300 text-red-900' },
|
||||||
|
completed: { label: 'Abgeschlossen', color: 'bg-green-200 text-green-800' },
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ROLE_LABELS: Record<string, { label: string; color: string }> = {
|
||||||
|
ek: { label: 'Erstkorrektor', color: 'bg-blue-500' },
|
||||||
|
zk: { label: 'Zweitkorrektor', color: 'bg-amber-500' },
|
||||||
|
dk: { label: 'Drittkorrektor', color: 'bg-purple-500' },
|
||||||
|
viewer: { label: 'Betrachter', color: 'bg-slate-500' },
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Form types (create / upload)
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
export interface CreateKlausurForm {
|
||||||
|
title: string
|
||||||
|
subject: string
|
||||||
|
year: number
|
||||||
|
semester: string
|
||||||
|
modus: 'abitur' | 'vorabitur'
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface VorabiturEHForm {
|
||||||
|
aufgabentyp: string
|
||||||
|
titel: string
|
||||||
|
text_titel: string
|
||||||
|
text_autor: string
|
||||||
|
aufgabenstellung: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface EHTemplate {
|
||||||
|
aufgabentyp: string
|
||||||
|
name: string
|
||||||
|
description: string
|
||||||
|
category: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DirektuploadForm {
|
||||||
|
files: File[]
|
||||||
|
ehFile: File | null
|
||||||
|
ehText: string
|
||||||
|
aufgabentyp: string
|
||||||
|
klausurTitle: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TabId = 'willkommen' | 'klausuren' | 'erstellen' | 'direktupload' | 'statistiken'
|
||||||
|
|||||||
@@ -1 +1,127 @@
|
|||||||
export * from '@shared/types/ocr-labeling'
|
/**
|
||||||
|
* Shared TypeScript types for OCR Labeling UI.
|
||||||
|
*
|
||||||
|
* Single source of truth used by:
|
||||||
|
* - admin-lehrer (ai/ocr-labeling)
|
||||||
|
* - website (admin/ocr-labeling)
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Available OCR Models
|
||||||
|
*
|
||||||
|
* - llama3.2-vision:11b: Vision LLM, beste Qualitaet bei Handschrift (Standard)
|
||||||
|
* - trocr: Microsoft TrOCR, schnell bei gedrucktem Text
|
||||||
|
* - paddleocr: PaddleOCR + LLM, 4x schneller durch Hybrid-Ansatz
|
||||||
|
* - donut: Document Understanding Transformer, strukturierte Dokumente
|
||||||
|
*/
|
||||||
|
export type OCRModel = 'llama3.2-vision:11b' | 'trocr' | 'paddleocr' | 'donut'
|
||||||
|
|
||||||
|
export const OCR_MODEL_INFO: Record<OCRModel, { label: string; description: string; speed: string }> = {
|
||||||
|
'llama3.2-vision:11b': {
|
||||||
|
label: 'Vision LLM',
|
||||||
|
description: 'Beste Qualitaet bei Handschrift',
|
||||||
|
speed: 'langsam',
|
||||||
|
},
|
||||||
|
trocr: {
|
||||||
|
label: 'Microsoft TrOCR',
|
||||||
|
description: 'Schnell bei gedrucktem Text',
|
||||||
|
speed: 'schnell',
|
||||||
|
},
|
||||||
|
paddleocr: {
|
||||||
|
label: 'PaddleOCR + LLM',
|
||||||
|
description: 'Hybrid-Ansatz: OCR + Strukturierung',
|
||||||
|
speed: 'sehr schnell',
|
||||||
|
},
|
||||||
|
donut: {
|
||||||
|
label: 'Donut',
|
||||||
|
description: 'Document Understanding fuer Tabellen/Formulare',
|
||||||
|
speed: 'mittel',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface OCRSession {
|
||||||
|
id: string
|
||||||
|
name: string
|
||||||
|
source_type: 'klausur' | 'handwriting_sample' | 'scan'
|
||||||
|
description?: string
|
||||||
|
ocr_model?: OCRModel
|
||||||
|
total_items: number
|
||||||
|
labeled_items: number
|
||||||
|
confirmed_items: number
|
||||||
|
corrected_items: number
|
||||||
|
skipped_items: number
|
||||||
|
created_at: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface OCRItem {
|
||||||
|
id: string
|
||||||
|
session_id: string
|
||||||
|
session_name: string
|
||||||
|
image_path: string
|
||||||
|
image_url?: string
|
||||||
|
ocr_text?: string
|
||||||
|
ocr_confidence?: number
|
||||||
|
ground_truth?: string
|
||||||
|
status: 'pending' | 'confirmed' | 'corrected' | 'skipped'
|
||||||
|
metadata?: Record<string, unknown>
|
||||||
|
created_at: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface OCRStats {
|
||||||
|
total_sessions?: number
|
||||||
|
session_id?: string
|
||||||
|
name?: string
|
||||||
|
total_items: number
|
||||||
|
labeled_items: number
|
||||||
|
confirmed_items: number
|
||||||
|
corrected_items: number
|
||||||
|
skipped_items?: number
|
||||||
|
pending_items: number
|
||||||
|
exportable_items?: number
|
||||||
|
accuracy_rate: number
|
||||||
|
avg_label_time_seconds?: number
|
||||||
|
progress_percent?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TrainingSample {
|
||||||
|
id: string
|
||||||
|
image_path: string
|
||||||
|
ground_truth: string
|
||||||
|
export_format: 'generic' | 'trocr' | 'llama_vision'
|
||||||
|
training_batch: string
|
||||||
|
exported_at?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CreateSessionRequest {
|
||||||
|
name: string
|
||||||
|
source_type: 'klausur' | 'handwriting_sample' | 'scan'
|
||||||
|
description?: string
|
||||||
|
ocr_model?: OCRModel
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ConfirmRequest {
|
||||||
|
item_id: string
|
||||||
|
label_time_seconds?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CorrectRequest {
|
||||||
|
item_id: string
|
||||||
|
ground_truth: string
|
||||||
|
label_time_seconds?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ExportRequest {
|
||||||
|
export_format: 'generic' | 'trocr' | 'llama_vision'
|
||||||
|
session_id?: string
|
||||||
|
batch_id?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UploadResult {
|
||||||
|
id: string
|
||||||
|
filename: string
|
||||||
|
image_path: string
|
||||||
|
image_hash: string
|
||||||
|
ocr_text?: string
|
||||||
|
ocr_confidence?: number
|
||||||
|
status: string
|
||||||
|
}
|
||||||
|
|||||||
@@ -1 +1,432 @@
|
|||||||
export * from '@shared/types/klausur'
|
/**
|
||||||
|
* Shared Klausur-Korrektur types and constants.
|
||||||
|
*
|
||||||
|
* This is the single source of truth used by:
|
||||||
|
* - admin-lehrer (education/klausur-korrektur)
|
||||||
|
* - studio-v2 (korrektur)
|
||||||
|
* - website/admin (klausur-korrektur)
|
||||||
|
* - website/lehrer (klausur-korrektur)
|
||||||
|
*/
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Core domain interfaces
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
export interface Klausur {
|
||||||
|
id: string
|
||||||
|
title: string
|
||||||
|
subject: string
|
||||||
|
year: number
|
||||||
|
semester: string
|
||||||
|
modus: KlausurModus
|
||||||
|
eh_id?: string
|
||||||
|
created_at: string
|
||||||
|
student_count?: number
|
||||||
|
completed_count?: number
|
||||||
|
status?: 'draft' | 'in_progress' | 'completed'
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Union of all modus values used across services */
|
||||||
|
export type KlausurModus = 'abitur' | 'vorabitur' | 'landes_abitur'
|
||||||
|
|
||||||
|
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>
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Annotations
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
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'
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Fairness analysis
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// EH suggestions
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
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',
|
||||||
|
} as const
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Gutachten
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
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?: KlausurModus
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Constants — annotation colors
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Constants — status colors & labels
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
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',
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Constants — criteria & grades
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/** Default criteria with weights (Niedersachsen 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 functions
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/** Calculate grade points from a percentage (0-100). */
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Human-readable label for a 15-point grade value. */
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Examiner workflow types (workspace)
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
export interface ExaminerInfo {
|
||||||
|
id: string
|
||||||
|
assigned_at: string
|
||||||
|
notes?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ExaminerResult {
|
||||||
|
grade_points: number
|
||||||
|
criteria_scores?: CriteriaScores
|
||||||
|
notes?: string
|
||||||
|
submitted_at: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ExaminerWorkflow {
|
||||||
|
student_id: string
|
||||||
|
workflow_status: string
|
||||||
|
visibility_mode: string
|
||||||
|
user_role: 'ek' | 'zk' | 'dk' | 'viewer'
|
||||||
|
first_examiner?: ExaminerInfo
|
||||||
|
second_examiner?: ExaminerInfo
|
||||||
|
third_examiner?: ExaminerInfo
|
||||||
|
first_result?: ExaminerResult
|
||||||
|
first_result_visible?: boolean
|
||||||
|
second_result?: ExaminerResult
|
||||||
|
third_result?: ExaminerResult
|
||||||
|
grade_difference?: number
|
||||||
|
final_grade?: number
|
||||||
|
consensus_reached?: boolean
|
||||||
|
consensus_type?: string
|
||||||
|
einigung?: {
|
||||||
|
final_grade: number
|
||||||
|
notes: string
|
||||||
|
type: string
|
||||||
|
submitted_by: string
|
||||||
|
submitted_at: string
|
||||||
|
ek_grade: number
|
||||||
|
zk_grade: number
|
||||||
|
}
|
||||||
|
drittkorrektur_reason?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ActiveTab = 'kriterien' | 'gutachten' | 'annotationen' | 'eh-vorschlaege'
|
||||||
|
|
||||||
|
export interface GradeTotals {
|
||||||
|
raw: number
|
||||||
|
weighted: number
|
||||||
|
gradePoints: number
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Constants — workflow status & roles
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
export const GRADE_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',
|
||||||
|
}
|
||||||
|
|
||||||
|
export const WORKFLOW_STATUS_LABELS: Record<string, { label: string; color: string }> = {
|
||||||
|
not_started: { label: 'Nicht gestartet', color: 'bg-slate-100 text-slate-700' },
|
||||||
|
ek_in_progress: { label: 'EK in Arbeit', color: 'bg-blue-100 text-blue-700' },
|
||||||
|
ek_completed: { label: 'EK abgeschlossen', color: 'bg-blue-200 text-blue-800' },
|
||||||
|
zk_assigned: { label: 'ZK zugewiesen', color: 'bg-amber-100 text-amber-700' },
|
||||||
|
zk_in_progress: { label: 'ZK in Arbeit', color: 'bg-amber-200 text-amber-800' },
|
||||||
|
zk_completed: { label: 'ZK abgeschlossen', color: 'bg-amber-300 text-amber-900' },
|
||||||
|
einigung_required: { label: 'Einigung erforderlich', color: 'bg-orange-100 text-orange-700' },
|
||||||
|
einigung_completed: { label: 'Einigung abgeschlossen', color: 'bg-green-100 text-green-700' },
|
||||||
|
drittkorrektur_required: { label: 'DK erforderlich', color: 'bg-red-100 text-red-700' },
|
||||||
|
drittkorrektur_assigned: { label: 'DK zugewiesen', color: 'bg-red-200 text-red-800' },
|
||||||
|
drittkorrektur_in_progress: { label: 'DK in Arbeit', color: 'bg-red-300 text-red-900' },
|
||||||
|
completed: { label: 'Abgeschlossen', color: 'bg-green-200 text-green-800' },
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ROLE_LABELS: Record<string, { label: string; color: string }> = {
|
||||||
|
ek: { label: 'Erstkorrektor', color: 'bg-blue-500' },
|
||||||
|
zk: { label: 'Zweitkorrektor', color: 'bg-amber-500' },
|
||||||
|
dk: { label: 'Drittkorrektor', color: 'bg-purple-500' },
|
||||||
|
viewer: { label: 'Betrachter', color: 'bg-slate-500' },
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Form types (create / upload)
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
export interface CreateKlausurForm {
|
||||||
|
title: string
|
||||||
|
subject: string
|
||||||
|
year: number
|
||||||
|
semester: string
|
||||||
|
modus: 'abitur' | 'vorabitur'
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface VorabiturEHForm {
|
||||||
|
aufgabentyp: string
|
||||||
|
titel: string
|
||||||
|
text_titel: string
|
||||||
|
text_autor: string
|
||||||
|
aufgabenstellung: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface EHTemplate {
|
||||||
|
aufgabentyp: string
|
||||||
|
name: string
|
||||||
|
description: string
|
||||||
|
category: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DirektuploadForm {
|
||||||
|
files: File[]
|
||||||
|
ehFile: File | null
|
||||||
|
ehText: string
|
||||||
|
aufgabentyp: string
|
||||||
|
klausurTitle: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TabId = 'willkommen' | 'klausuren' | 'erstellen' | 'direktupload' | 'statistiken'
|
||||||
|
|||||||
432
website/components/klausur-korrektur/klausur-types.ts
Normal file
432
website/components/klausur-korrektur/klausur-types.ts
Normal file
@@ -0,0 +1,432 @@
|
|||||||
|
/**
|
||||||
|
* Shared Klausur-Korrektur types and constants.
|
||||||
|
*
|
||||||
|
* This is the single source of truth used by:
|
||||||
|
* - admin-lehrer (education/klausur-korrektur)
|
||||||
|
* - studio-v2 (korrektur)
|
||||||
|
* - website/admin (klausur-korrektur)
|
||||||
|
* - website/lehrer (klausur-korrektur)
|
||||||
|
*/
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Core domain interfaces
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
export interface Klausur {
|
||||||
|
id: string
|
||||||
|
title: string
|
||||||
|
subject: string
|
||||||
|
year: number
|
||||||
|
semester: string
|
||||||
|
modus: KlausurModus
|
||||||
|
eh_id?: string
|
||||||
|
created_at: string
|
||||||
|
student_count?: number
|
||||||
|
completed_count?: number
|
||||||
|
status?: 'draft' | 'in_progress' | 'completed'
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Union of all modus values used across services */
|
||||||
|
export type KlausurModus = 'abitur' | 'vorabitur' | 'landes_abitur'
|
||||||
|
|
||||||
|
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>
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Annotations
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
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'
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Fairness analysis
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// EH suggestions
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
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',
|
||||||
|
} as const
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Gutachten
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
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?: KlausurModus
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Constants — annotation colors
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Constants — status colors & labels
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
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',
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Constants — criteria & grades
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/** Default criteria with weights (Niedersachsen 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 functions
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/** Calculate grade points from a percentage (0-100). */
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Human-readable label for a 15-point grade value. */
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Examiner workflow types (workspace)
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
export interface ExaminerInfo {
|
||||||
|
id: string
|
||||||
|
assigned_at: string
|
||||||
|
notes?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ExaminerResult {
|
||||||
|
grade_points: number
|
||||||
|
criteria_scores?: CriteriaScores
|
||||||
|
notes?: string
|
||||||
|
submitted_at: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ExaminerWorkflow {
|
||||||
|
student_id: string
|
||||||
|
workflow_status: string
|
||||||
|
visibility_mode: string
|
||||||
|
user_role: 'ek' | 'zk' | 'dk' | 'viewer'
|
||||||
|
first_examiner?: ExaminerInfo
|
||||||
|
second_examiner?: ExaminerInfo
|
||||||
|
third_examiner?: ExaminerInfo
|
||||||
|
first_result?: ExaminerResult
|
||||||
|
first_result_visible?: boolean
|
||||||
|
second_result?: ExaminerResult
|
||||||
|
third_result?: ExaminerResult
|
||||||
|
grade_difference?: number
|
||||||
|
final_grade?: number
|
||||||
|
consensus_reached?: boolean
|
||||||
|
consensus_type?: string
|
||||||
|
einigung?: {
|
||||||
|
final_grade: number
|
||||||
|
notes: string
|
||||||
|
type: string
|
||||||
|
submitted_by: string
|
||||||
|
submitted_at: string
|
||||||
|
ek_grade: number
|
||||||
|
zk_grade: number
|
||||||
|
}
|
||||||
|
drittkorrektur_reason?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ActiveTab = 'kriterien' | 'gutachten' | 'annotationen' | 'eh-vorschlaege'
|
||||||
|
|
||||||
|
export interface GradeTotals {
|
||||||
|
raw: number
|
||||||
|
weighted: number
|
||||||
|
gradePoints: number
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Constants — workflow status & roles
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
export const GRADE_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',
|
||||||
|
}
|
||||||
|
|
||||||
|
export const WORKFLOW_STATUS_LABELS: Record<string, { label: string; color: string }> = {
|
||||||
|
not_started: { label: 'Nicht gestartet', color: 'bg-slate-100 text-slate-700' },
|
||||||
|
ek_in_progress: { label: 'EK in Arbeit', color: 'bg-blue-100 text-blue-700' },
|
||||||
|
ek_completed: { label: 'EK abgeschlossen', color: 'bg-blue-200 text-blue-800' },
|
||||||
|
zk_assigned: { label: 'ZK zugewiesen', color: 'bg-amber-100 text-amber-700' },
|
||||||
|
zk_in_progress: { label: 'ZK in Arbeit', color: 'bg-amber-200 text-amber-800' },
|
||||||
|
zk_completed: { label: 'ZK abgeschlossen', color: 'bg-amber-300 text-amber-900' },
|
||||||
|
einigung_required: { label: 'Einigung erforderlich', color: 'bg-orange-100 text-orange-700' },
|
||||||
|
einigung_completed: { label: 'Einigung abgeschlossen', color: 'bg-green-100 text-green-700' },
|
||||||
|
drittkorrektur_required: { label: 'DK erforderlich', color: 'bg-red-100 text-red-700' },
|
||||||
|
drittkorrektur_assigned: { label: 'DK zugewiesen', color: 'bg-red-200 text-red-800' },
|
||||||
|
drittkorrektur_in_progress: { label: 'DK in Arbeit', color: 'bg-red-300 text-red-900' },
|
||||||
|
completed: { label: 'Abgeschlossen', color: 'bg-green-200 text-green-800' },
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ROLE_LABELS: Record<string, { label: string; color: string }> = {
|
||||||
|
ek: { label: 'Erstkorrektor', color: 'bg-blue-500' },
|
||||||
|
zk: { label: 'Zweitkorrektor', color: 'bg-amber-500' },
|
||||||
|
dk: { label: 'Drittkorrektor', color: 'bg-purple-500' },
|
||||||
|
viewer: { label: 'Betrachter', color: 'bg-slate-500' },
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Form types (create / upload)
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
export interface CreateKlausurForm {
|
||||||
|
title: string
|
||||||
|
subject: string
|
||||||
|
year: number
|
||||||
|
semester: string
|
||||||
|
modus: 'abitur' | 'vorabitur'
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface VorabiturEHForm {
|
||||||
|
aufgabentyp: string
|
||||||
|
titel: string
|
||||||
|
text_titel: string
|
||||||
|
text_autor: string
|
||||||
|
aufgabenstellung: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface EHTemplate {
|
||||||
|
aufgabentyp: string
|
||||||
|
name: string
|
||||||
|
description: string
|
||||||
|
category: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DirektuploadForm {
|
||||||
|
files: File[]
|
||||||
|
ehFile: File | null
|
||||||
|
ehText: string
|
||||||
|
aufgabentyp: string
|
||||||
|
klausurTitle: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TabId = 'willkommen' | 'klausuren' | 'erstellen' | 'direktupload' | 'statistiken'
|
||||||
@@ -12,6 +12,6 @@ export type {
|
|||||||
VorabiturEHForm,
|
VorabiturEHForm,
|
||||||
EHTemplate,
|
EHTemplate,
|
||||||
DirektuploadForm,
|
DirektuploadForm,
|
||||||
} from '@shared/types/klausur'
|
} from './klausur-types'
|
||||||
|
|
||||||
export const API_BASE = process.env.NEXT_PUBLIC_KLAUSUR_SERVICE_URL || 'http://localhost:8086'
|
export const API_BASE = process.env.NEXT_PUBLIC_KLAUSUR_SERVICE_URL || 'http://localhost:8086'
|
||||||
|
|||||||
@@ -12,12 +12,12 @@ export type {
|
|||||||
ExaminerWorkflow,
|
ExaminerWorkflow,
|
||||||
ActiveTab,
|
ActiveTab,
|
||||||
CriteriaScores,
|
CriteriaScores,
|
||||||
} from '@shared/types/klausur'
|
} from './klausur-types'
|
||||||
|
|
||||||
export {
|
export {
|
||||||
WORKFLOW_STATUS_LABELS,
|
WORKFLOW_STATUS_LABELS,
|
||||||
ROLE_LABELS,
|
ROLE_LABELS,
|
||||||
GRADE_LABELS,
|
GRADE_LABELS,
|
||||||
} from '@shared/types/klausur'
|
} from './klausur-types'
|
||||||
|
|
||||||
export const API_BASE = process.env.NEXT_PUBLIC_KLAUSUR_SERVICE_URL || 'http://localhost:8086'
|
export const API_BASE = process.env.NEXT_PUBLIC_KLAUSUR_SERVICE_URL || 'http://localhost:8086'
|
||||||
|
|||||||
@@ -24,9 +24,6 @@
|
|||||||
"paths": {
|
"paths": {
|
||||||
"@/*": [
|
"@/*": [
|
||||||
"./*"
|
"./*"
|
||||||
],
|
|
||||||
"@shared/*": [
|
|
||||||
"../shared/*"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"target": "ES2017"
|
"target": "ES2017"
|
||||||
|
|||||||
Reference in New Issue
Block a user