This repository has been archived on 2026-02-15. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
Benjamin Admin 21a844cb8a 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>
2026-02-09 09:51:32 +01:00

408 lines
13 KiB
Python

"""
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,
}