[split-required] Split final 43 files (500-668 LOC) to complete refactoring

klausur-service (11 files):
- cv_gutter_repair, ocr_pipeline_regression, upload_api
- ocr_pipeline_sessions, smart_spell, nru_worksheet_generator
- ocr_pipeline_overlays, mail/aggregator, zeugnis_api
- cv_syllable_detect, self_rag

backend-lehrer (17 files):
- classroom_engine/suggestions, generators/quiz_generator
- worksheets_api, llm_gateway/comparison, state_engine_api
- classroom/models (→ 4 submodules), services/file_processor
- alerts_agent/api/wizard+digests+routes, content_generators/pdf
- classroom/routes/sessions, llm_gateway/inference
- classroom_engine/analytics, auth/keycloak_auth
- alerts_agent/processing/rule_engine, ai_processor/print_versions

agent-core (5 files):
- brain/memory_store, brain/knowledge_graph, brain/context_manager
- orchestrator/supervisor, sessions/session_manager

admin-lehrer (5 components):
- GridOverlay, StepGridReview, DevOpsPipelineSidebar
- DataFlowDiagram, sbom/wizard/page

website (2 files):
- DependencyMap, lehrer/abitur-archiv

Other: nibis_ingestion, grid_detection_service, export-doclayout-onnx

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-04-25 09:41:42 +02:00
parent 451365a312
commit bd4b956e3c
113 changed files with 13790 additions and 14148 deletions

View File

@@ -0,0 +1,205 @@
"""
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,
}