fix: Restore all files lost during destructive rebase

A previous `git pull --rebase origin main` dropped 177 local commits,
losing 3400+ files across admin-v2, backend, studio-v2, website,
klausur-service, and many other services. The partial restore attempt
(660295e2) only recovered some files.

This commit restores all missing files from pre-rebase ref 98933f5e
while preserving post-rebase additions (night-scheduler, night-mode UI,
NightModeWidget dashboard integration).

Restored features include:
- AI Module Sidebar (FAB), OCR Labeling, OCR Compare
- GPU Dashboard, RAG Pipeline, Magic Help
- Klausur-Korrektur (8 files), Abitur-Archiv (5+ files)
- Companion, Zeugnisse-Crawler, Screen Flow
- Full backend, studio-v2, website, klausur-service
- All compliance SDKs, agent-core, voice-service
- CI/CD configs, documentation, scripts

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-02-09 09:51:32 +01:00
parent f7487ee240
commit bfdaf63ba9
2009 changed files with 749983 additions and 1731 deletions

View File

@@ -0,0 +1,407 @@
"""
Datenmodelle fuer die Classroom State Machine.
"""
from dataclasses import dataclass, field
from datetime import datetime
from typing import Optional, List, Dict, Any
from enum import Enum
class LessonPhase(str, Enum):
"""Unterrichtsphasen als Enum."""
NOT_STARTED = "not_started"
EINSTIEG = "einstieg"
ERARBEITUNG = "erarbeitung"
SICHERUNG = "sicherung"
TRANSFER = "transfer"
REFLEXION = "reflexion"
ENDED = "ended"
@dataclass
class PhaseConfig:
"""Konfiguration einer einzelnen Phase."""
phase: LessonPhase
display_name: str
duration_minutes: int
activities: List[str]
icon: str
description: str = ""
# Phasen-Definitionen mit Default-Werten
LESSON_PHASES: Dict[str, Dict[str, Any]] = {
"einstieg": {
"display_name": "Einstieg",
"default_duration_minutes": 8,
"next_phase": "erarbeitung",
"activities": ["warmup", "motivation", "problem_introduction"],
"icon": "play_circle",
"description": "Motivation und Problemstellung"
},
"erarbeitung": {
"display_name": "Erarbeitung",
"default_duration_minutes": 20,
"next_phase": "sicherung",
"activities": ["instruction", "individual_work", "partner_work", "group_work"],
"icon": "edit",
"description": "Hauptarbeitsphase mit Input und Uebungen"
},
"sicherung": {
"display_name": "Sicherung",
"default_duration_minutes": 10,
"next_phase": "transfer",
"activities": ["summary", "visualization", "documentation"],
"icon": "save",
"description": "Ergebnisse festhalten und zusammenfassen"
},
"transfer": {
"display_name": "Transfer",
"default_duration_minutes": 7,
"next_phase": "reflexion",
"activities": ["application", "real_world_connection", "differentiation"],
"icon": "compare_arrows",
"description": "Anwendung auf neue Kontexte"
},
"reflexion": {
"display_name": "Reflexion",
"default_duration_minutes": 5,
"next_phase": None,
"activities": ["feedback", "homework", "preview"],
"icon": "lightbulb",
"description": "Rueckblick, Hausaufgaben und Ausblick"
}
}
def get_default_durations() -> Dict[str, int]:
"""Gibt die Standard-Phasendauern zurueck."""
return {
phase_id: config["default_duration_minutes"]
for phase_id, config in LESSON_PHASES.items()
}
@dataclass
class LessonSession:
"""Repräsentiert eine laufende Unterrichtsstunde."""
session_id: str
teacher_id: str
class_id: str
subject: str
topic: Optional[str] = None
# State
current_phase: LessonPhase = LessonPhase.NOT_STARTED
phase_started_at: Optional[datetime] = None
lesson_started_at: Optional[datetime] = None
lesson_ended_at: Optional[datetime] = None
# Pause state (Feature f26/f27)
is_paused: bool = False
pause_started_at: Optional[datetime] = None
total_paused_seconds: int = 0 # Cumulative pause time for current phase
# Phase durations (customizable per session)
phase_durations: Dict[str, int] = field(default_factory=get_default_durations)
# History
phase_history: List[Dict[str, Any]] = field(default_factory=list)
# Metadata
notes: str = ""
homework: str = ""
def to_dict(self) -> Dict[str, Any]:
"""Konvertiert die Session in ein Dictionary."""
return {
"session_id": self.session_id,
"teacher_id": self.teacher_id,
"class_id": self.class_id,
"subject": self.subject,
"topic": self.topic,
"current_phase": self.current_phase.value,
"phase_started_at": self.phase_started_at.isoformat() if self.phase_started_at else None,
"lesson_started_at": self.lesson_started_at.isoformat() if self.lesson_started_at else None,
"lesson_ended_at": self.lesson_ended_at.isoformat() if self.lesson_ended_at else None,
"is_paused": self.is_paused,
"pause_started_at": self.pause_started_at.isoformat() if self.pause_started_at else None,
"total_paused_seconds": self.total_paused_seconds,
"phase_durations": self.phase_durations,
"phase_history": self.phase_history,
"notes": self.notes,
"homework": self.homework,
}
def get_phase_display_name(self) -> str:
"""Gibt den Anzeigenamen der aktuellen Phase zurueck."""
if self.current_phase == LessonPhase.NOT_STARTED:
return "Nicht gestartet"
elif self.current_phase == LessonPhase.ENDED:
return "Beendet"
else:
return LESSON_PHASES.get(self.current_phase.value, {}).get("display_name", "Unbekannt")
def get_total_duration_minutes(self) -> int:
"""Gibt die Gesamtdauer aller Phasen in Minuten zurueck."""
return sum(self.phase_durations.values())
@dataclass
class LessonTemplate:
"""
Vorlage fuer Unterrichtsstunden (Feature f37).
Ermoeglicht Lehrern, haeufig genutzte Stundenkonfigurationen zu speichern.
"""
template_id: str
teacher_id: str # Ersteller der Vorlage
name: str
description: str = ""
subject: str = ""
grade_level: str = "" # z.B. "7", "10", "Oberstufe"
# Phasenkonfiguration
phase_durations: Dict[str, int] = field(default_factory=get_default_durations)
# Optionale Vorbelegungen
default_topic: str = ""
default_notes: str = ""
# Metadaten
is_public: bool = False # Oeffentlich fuer alle Lehrer?
usage_count: int = 0
created_at: Optional[datetime] = None
updated_at: Optional[datetime] = None
def to_dict(self) -> Dict[str, Any]:
"""Konvertiert das Template in ein Dictionary."""
return {
"template_id": self.template_id,
"teacher_id": self.teacher_id,
"name": self.name,
"description": self.description,
"subject": self.subject,
"grade_level": self.grade_level,
"phase_durations": self.phase_durations,
"default_topic": self.default_topic,
"default_notes": self.default_notes,
"is_public": self.is_public,
"usage_count": self.usage_count,
"created_at": self.created_at.isoformat() if self.created_at else None,
"updated_at": self.updated_at.isoformat() if self.updated_at else None,
"total_duration_minutes": sum(self.phase_durations.values()),
}
# Vordefinierte System-Templates
SYSTEM_TEMPLATES: List[Dict[str, Any]] = [
{
"template_id": "system_standard_45",
"name": "Standard 45 Min",
"description": "Klassische Unterrichtsstunde mit 45 Minuten",
"phase_durations": {
"einstieg": 5,
"erarbeitung": 20,
"sicherung": 10,
"transfer": 5,
"reflexion": 5,
},
"is_public": True,
},
{
"template_id": "system_standard_90",
"name": "Doppelstunde 90 Min",
"description": "Ausfuehrliche Doppelstunde mit mehr Zeit fuer Erarbeitung",
"phase_durations": {
"einstieg": 10,
"erarbeitung": 40,
"sicherung": 20,
"transfer": 10,
"reflexion": 10,
},
"is_public": True,
},
{
"template_id": "system_workshop",
"name": "Workshop-Stil",
"description": "Praxisorientiert mit langer Erarbeitungsphase",
"phase_durations": {
"einstieg": 5,
"erarbeitung": 30,
"sicherung": 5,
"transfer": 5,
"reflexion": 5,
},
"is_public": True,
},
{
"template_id": "system_discussion",
"name": "Diskussion & Reflexion",
"description": "Fuer Themen mit viel Diskussionsbedarf",
"phase_durations": {
"einstieg": 8,
"erarbeitung": 15,
"sicherung": 7,
"transfer": 10,
"reflexion": 10,
},
"is_public": True,
},
{
"template_id": "system_test_prep",
"name": "Pruefungsvorbereitung",
"description": "Kompakte Wiederholung vor Tests",
"phase_durations": {
"einstieg": 3,
"erarbeitung": 25,
"sicherung": 12,
"transfer": 3,
"reflexion": 2,
},
"is_public": True,
},
]
@dataclass
class PhaseSuggestion:
"""Ein Vorschlag fuer eine Aktivitaet in einer Phase (Feature f18)."""
id: str
title: str
description: str
activity_type: str # warmup, instruction, exercise, etc.
estimated_minutes: int
icon: str
content_url: Optional[str] = None # Link to learning unit
subjects: Optional[List[str]] = None # Faecher fuer die der Vorschlag passt (None = alle)
grade_levels: Optional[List[str]] = None # Klassenstufen (None = alle)
def to_dict(self) -> Dict[str, Any]:
"""Konvertiert den Vorschlag in ein Dictionary."""
return {
"id": self.id,
"title": self.title,
"description": self.description,
"activity_type": self.activity_type,
"estimated_minutes": self.estimated_minutes,
"icon": self.icon,
"content_url": self.content_url,
"subjects": self.subjects,
"grade_levels": self.grade_levels,
}
# ==================== Homework Tracker (Feature f20) ====================
class HomeworkStatus(Enum):
"""Status einer Hausaufgabe."""
ASSIGNED = "assigned" # Aufgegeben
IN_PROGRESS = "in_progress" # In Bearbeitung
COMPLETED = "completed" # Erledigt
OVERDUE = "overdue" # Ueberfaellig
@dataclass
class Homework:
"""
Eine Hausaufgabe (Feature f20).
Ermoeglicht das Tracking von Hausaufgaben ueber Sessions hinweg.
"""
homework_id: str
teacher_id: str
class_id: str
subject: str
title: str
description: str = ""
session_id: Optional[str] = None # Verknuepfte Session
due_date: Optional[datetime] = None
status: HomeworkStatus = HomeworkStatus.ASSIGNED
created_at: Optional[datetime] = None
updated_at: Optional[datetime] = None
def to_dict(self) -> Dict[str, Any]:
"""Konvertiert die Hausaufgabe in ein Dictionary."""
return {
"homework_id": self.homework_id,
"teacher_id": self.teacher_id,
"class_id": self.class_id,
"subject": self.subject,
"title": self.title,
"description": self.description,
"session_id": self.session_id,
"due_date": self.due_date.isoformat() if self.due_date else None,
"status": self.status.value,
"created_at": self.created_at.isoformat() if self.created_at else None,
"updated_at": self.updated_at.isoformat() if self.updated_at else None,
"is_overdue": self.is_overdue,
}
@property
def is_overdue(self) -> bool:
"""Prueft ob die Hausaufgabe ueberfaellig ist."""
if not self.due_date:
return False
if self.status == HomeworkStatus.COMPLETED:
return False
return datetime.now() > self.due_date
# ==================== Phase Materials (Feature f19) ====================
class MaterialType(Enum):
"""Typ des Materials."""
DOCUMENT = "document" # PDF, Word, etc.
LINK = "link" # URL
VIDEO = "video" # Video-Link
IMAGE = "image" # Bild
WORKSHEET = "worksheet" # Arbeitsblatt
PRESENTATION = "presentation" # Praesentation
OTHER = "other"
@dataclass
class PhaseMaterial:
"""
Material fuer eine Unterrichtsphase (Feature f19).
Ermoeglicht das Anhaengen von Dokumenten, Links und Medien
an bestimmte Phasen einer Unterrichtsstunde.
"""
material_id: str
teacher_id: str
title: str
material_type: MaterialType = MaterialType.DOCUMENT
url: Optional[str] = None # URL oder Dateipfad
description: str = ""
phase: Optional[str] = None # Phasen-ID (einstieg, erarbeitung, etc.)
subject: str = ""
grade_level: str = ""
tags: List[str] = field(default_factory=list)
is_public: bool = False # Fuer Sharing mit anderen Lehrern
usage_count: int = 0
session_id: Optional[str] = None # Verknuepfung mit einer Session
created_at: Optional[datetime] = None
updated_at: Optional[datetime] = None
def to_dict(self) -> Dict[str, Any]:
"""Konvertiert das Material in ein Dictionary."""
return {
"material_id": self.material_id,
"teacher_id": self.teacher_id,
"title": self.title,
"material_type": self.material_type.value,
"url": self.url,
"description": self.description,
"phase": self.phase,
"subject": self.subject,
"grade_level": self.grade_level,
"tags": self.tags,
"is_public": self.is_public,
"usage_count": self.usage_count,
"session_id": self.session_id,
"created_at": self.created_at.isoformat() if self.created_at else None,
"updated_at": self.updated_at.isoformat() if self.updated_at else None,
}