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