""" Analytics Models - Datenstrukturen fuer Classroom Analytics. Enthaelt PhaseStatistics, SessionSummary, TeacherAnalytics, LessonReflection. """ from dataclasses import dataclass, field from datetime import datetime from typing import Optional, List, Dict, Any @dataclass class PhaseStatistics: """Statistik fuer eine einzelne Phase.""" phase: str display_name: str # Dauer-Metriken planned_duration_seconds: int actual_duration_seconds: int difference_seconds: int # positiv = laenger als geplant # Overtime had_overtime: bool overtime_seconds: int = 0 # Erweiterungen was_extended: bool = False extension_minutes: int = 0 # Pausen pause_count: int = 0 total_pause_seconds: int = 0 def to_dict(self) -> Dict[str, Any]: return { "phase": self.phase, "display_name": self.display_name, "planned_duration_seconds": self.planned_duration_seconds, "actual_duration_seconds": self.actual_duration_seconds, "difference_seconds": self.difference_seconds, "difference_formatted": self._format_difference(), "had_overtime": self.had_overtime, "overtime_seconds": self.overtime_seconds, "overtime_formatted": self._format_seconds(self.overtime_seconds), "was_extended": self.was_extended, "extension_minutes": self.extension_minutes, "pause_count": self.pause_count, "total_pause_seconds": self.total_pause_seconds, } def _format_difference(self) -> str: prefix = "+" if self.difference_seconds >= 0 else "" return f"{prefix}{self._format_seconds(abs(self.difference_seconds))}" def _format_seconds(self, seconds: int) -> str: mins = seconds // 60 secs = seconds % 60 return f"{mins:02d}:{secs:02d}" @dataclass class SessionSummary: """Zusammenfassung einer Unterrichtsstunde.""" session_id: str teacher_id: str class_id: str subject: str topic: Optional[str] date: datetime total_duration_seconds: int planned_duration_seconds: int phases_completed: int total_phases: int = 5 phase_statistics: List[PhaseStatistics] = field(default_factory=list) total_overtime_seconds: int = 0 phases_with_overtime: int = 0 total_pause_count: int = 0 total_pause_seconds: int = 0 reflection_notes: str = "" reflection_rating: Optional[int] = None key_learnings: List[str] = field(default_factory=list) def to_dict(self) -> Dict[str, Any]: return { "session_id": self.session_id, "teacher_id": self.teacher_id, "class_id": self.class_id, "subject": self.subject, "topic": self.topic, "date": self.date.isoformat() if self.date else None, "date_formatted": self._format_date(), "total_duration_seconds": self.total_duration_seconds, "total_duration_formatted": self._format_seconds(self.total_duration_seconds), "planned_duration_seconds": self.planned_duration_seconds, "planned_duration_formatted": self._format_seconds(self.planned_duration_seconds), "phases_completed": self.phases_completed, "total_phases": self.total_phases, "completion_percentage": round(self.phases_completed / self.total_phases * 100), "phase_statistics": [p.to_dict() for p in self.phase_statistics], "total_overtime_seconds": self.total_overtime_seconds, "total_overtime_formatted": self._format_seconds(self.total_overtime_seconds), "phases_with_overtime": self.phases_with_overtime, "total_pause_count": self.total_pause_count, "total_pause_seconds": self.total_pause_seconds, "reflection_notes": self.reflection_notes, "reflection_rating": self.reflection_rating, "key_learnings": self.key_learnings, } def _format_seconds(self, seconds: int) -> str: mins = seconds // 60 secs = seconds % 60 return f"{mins:02d}:{secs:02d}" def _format_date(self) -> str: if not self.date: return "" return self.date.strftime("%d.%m.%Y %H:%M") @dataclass class TeacherAnalytics: """Aggregierte Statistiken fuer einen Lehrer.""" teacher_id: str period_start: datetime period_end: datetime total_sessions: int = 0 completed_sessions: int = 0 total_teaching_minutes: int = 0 avg_phase_durations: Dict[str, float] = field(default_factory=dict) sessions_with_overtime: int = 0 avg_overtime_seconds: float = 0 most_overtime_phase: Optional[str] = None avg_pause_count: float = 0 avg_pause_duration_seconds: float = 0 subjects_taught: Dict[str, int] = field(default_factory=dict) classes_taught: Dict[str, int] = field(default_factory=dict) def to_dict(self) -> Dict[str, Any]: return { "teacher_id": self.teacher_id, "period_start": self.period_start.isoformat() if self.period_start else None, "period_end": self.period_end.isoformat() if self.period_end else None, "total_sessions": self.total_sessions, "completed_sessions": self.completed_sessions, "total_teaching_minutes": self.total_teaching_minutes, "total_teaching_hours": round(self.total_teaching_minutes / 60, 1), "avg_phase_durations": self.avg_phase_durations, "sessions_with_overtime": self.sessions_with_overtime, "overtime_percentage": round(self.sessions_with_overtime / max(self.total_sessions, 1) * 100), "avg_overtime_seconds": round(self.avg_overtime_seconds), "avg_overtime_formatted": self._format_seconds(int(self.avg_overtime_seconds)), "most_overtime_phase": self.most_overtime_phase, "avg_pause_count": round(self.avg_pause_count, 1), "avg_pause_duration_seconds": round(self.avg_pause_duration_seconds), "subjects_taught": self.subjects_taught, "classes_taught": self.classes_taught, } def _format_seconds(self, seconds: int) -> str: mins = seconds // 60 secs = seconds % 60 return f"{mins:02d}:{secs:02d}" @dataclass class LessonReflection: """Post-Lesson Reflection (Feature).""" reflection_id: str session_id: str teacher_id: str notes: str = "" overall_rating: Optional[int] = None what_worked: List[str] = field(default_factory=list) improvements: List[str] = field(default_factory=list) notes_for_next_lesson: str = "" created_at: Optional[datetime] = None updated_at: Optional[datetime] = None def to_dict(self) -> Dict[str, Any]: return { "reflection_id": self.reflection_id, "session_id": self.session_id, "teacher_id": self.teacher_id, "notes": self.notes, "overall_rating": self.overall_rating, "what_worked": self.what_worked, "improvements": self.improvements, "notes_for_next_lesson": self.notes_for_next_lesson, "created_at": self.created_at.isoformat() if self.created_at else None, "updated_at": self.updated_at.isoformat() if self.updated_at else None, }