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
BreakPilot Dev 19855efacc
Some checks failed
Tests / Go Tests (push) Has been cancelled
Tests / Python Tests (push) Has been cancelled
Tests / Integration Tests (push) Has been cancelled
Tests / Go Lint (push) Has been cancelled
Tests / Python Lint (push) Has been cancelled
Tests / Security Scan (push) Has been cancelled
Tests / All Checks Passed (push) Has been cancelled
Security Scanning / Secret Scanning (push) Has been cancelled
Security Scanning / Dependency Vulnerability Scan (push) Has been cancelled
Security Scanning / Go Security Scan (push) Has been cancelled
Security Scanning / Python Security Scan (push) Has been cancelled
Security Scanning / Node.js Security Scan (push) Has been cancelled
Security Scanning / Docker Image Security (push) Has been cancelled
Security Scanning / Security Summary (push) Has been cancelled
CI/CD Pipeline / Go Tests (push) Has been cancelled
CI/CD Pipeline / Python Tests (push) Has been cancelled
CI/CD Pipeline / Website Tests (push) Has been cancelled
CI/CD Pipeline / Linting (push) Has been cancelled
CI/CD Pipeline / Security Scan (push) Has been cancelled
CI/CD Pipeline / Docker Build & Push (push) Has been cancelled
CI/CD Pipeline / Integration Tests (push) Has been cancelled
CI/CD Pipeline / Deploy to Staging (push) Has been cancelled
CI/CD Pipeline / Deploy to Production (push) Has been cancelled
CI/CD Pipeline / CI Summary (push) Has been cancelled
ci/woodpecker/manual/build-ci-image Pipeline was successful
ci/woodpecker/manual/main Pipeline failed
feat: BreakPilot PWA - Full codebase (clean push without large binaries)
All services: admin-v2, studio-v2, website, ai-compliance-sdk,
consent-service, klausur-service, voice-service, and infrastructure.
Large PDFs and compiled binaries excluded via .gitignore.
2026-02-11 13:25:58 +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,
}