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-pwa/backend/state_engine/models.py
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

318 lines
10 KiB
Python

"""
State Engine Models - Datenstrukturen für das Phasen-Management.
Definiert:
- SchoolYearPhase: Die 9 Phasen des Schuljahres
- TeacherContext: Aggregierter Kontext für Antizipation
- Event, Milestone, Stats: Unterstützende Modelle
"""
from dataclasses import dataclass, field
from datetime import datetime
from enum import Enum
from typing import List, Optional, Dict, Any
import uuid
class SchoolYearPhase(str, Enum):
"""Die 9 Phasen eines Schuljahres."""
# Phase 1: Schuljahresbeginn (Aug/Sep)
ONBOARDING = "onboarding"
# Neue Lehrer, Schulsuche, Grundkonfiguration
# Phase 2: Schuljahresstart (Sep/Okt)
SCHOOL_YEAR_START = "school_year_start"
# Klassen anlegen, Stundenplan, erste Einheiten
# Phase 3: Unterrichtsaufbau (Okt/Nov)
TEACHING_SETUP = "teaching_setup"
# Lerneinheiten, Materialien, Elternkommunikation
# Phase 4: Leistungsphase 1 (Nov/Dez)
PERFORMANCE_1 = "performance_1"
# Klausuren, Korrektur, erste Noten
# Phase 5: Halbjahresabschluss (Jan/Feb)
SEMESTER_END = "semester_end"
# Halbjahreszeugnisse, Konferenzen, Elterngespräche
# Phase 6: 2. Halbjahr Unterricht (Feb/Apr)
TEACHING_2 = "teaching_2"
# Wiederholung von Phase 3
# Phase 7: Leistungsphase 2 (Apr/Jun)
PERFORMANCE_2 = "performance_2"
# Klausuren, Korrektur, finale Noten
# Phase 8: Jahresabschluss (Jun/Jul)
YEAR_END = "year_end"
# Abschlusszeugnisse, Versetzung, Archivierung
# Phase 9: Archiviert
ARCHIVED = "archived"
# Schuljahr abgeschlossen
@dataclass
class PhaseInfo:
"""Metadaten zu einer Phase."""
phase: SchoolYearPhase
display_name: str
description: str
typical_months: List[int] # 1-12
expected_duration_weeks: int
required_actions: List[str]
optional_actions: List[str]
# Phasen-Definitionen mit Metadaten
PHASE_INFO: Dict[SchoolYearPhase, PhaseInfo] = {
SchoolYearPhase.ONBOARDING: PhaseInfo(
phase=SchoolYearPhase.ONBOARDING,
display_name="Onboarding",
description="Willkommen bei BreakPilot! Richte dein Schuljahr ein.",
typical_months=[8, 9],
expected_duration_weeks=2,
required_actions=["school_select", "consent_accept", "profile_complete"],
optional_actions=["import_previous_year"],
),
SchoolYearPhase.SCHOOL_YEAR_START: PhaseInfo(
phase=SchoolYearPhase.SCHOOL_YEAR_START,
display_name="Schuljahresstart",
description="Lege deine Klassen und den Stundenplan an.",
typical_months=[9, 10],
expected_duration_weeks=3,
required_actions=["create_classes", "add_students", "create_timetable"],
optional_actions=["import_students_csv", "invite_parents"],
),
SchoolYearPhase.TEACHING_SETUP: PhaseInfo(
phase=SchoolYearPhase.TEACHING_SETUP,
display_name="Unterrichtsaufbau",
description="Erstelle Lerneinheiten und Materialien.",
typical_months=[10, 11],
expected_duration_weeks=4,
required_actions=["create_learning_units"],
optional_actions=["generate_worksheets", "prepare_parent_meeting"],
),
SchoolYearPhase.PERFORMANCE_1: PhaseInfo(
phase=SchoolYearPhase.PERFORMANCE_1,
display_name="Leistungsphase 1",
description="Erste Klausuren und Bewertungen.",
typical_months=[11, 12],
expected_duration_weeks=6,
required_actions=["schedule_exams", "enter_grades"],
optional_actions=["use_correction_module", "generate_feedback"],
),
SchoolYearPhase.SEMESTER_END: PhaseInfo(
phase=SchoolYearPhase.SEMESTER_END,
display_name="Halbjahresabschluss",
description="Halbjahreszeugnisse und Konferenzen.",
typical_months=[1, 2],
expected_duration_weeks=3,
required_actions=["complete_grades", "generate_certificates"],
optional_actions=["parent_conferences", "archive_semester"],
),
SchoolYearPhase.TEACHING_2: PhaseInfo(
phase=SchoolYearPhase.TEACHING_2,
display_name="2. Halbjahr",
description="Weiterführender Unterricht im 2. Halbjahr.",
typical_months=[2, 3, 4],
expected_duration_weeks=8,
required_actions=["update_learning_units"],
optional_actions=["generate_worksheets"],
),
SchoolYearPhase.PERFORMANCE_2: PhaseInfo(
phase=SchoolYearPhase.PERFORMANCE_2,
display_name="Leistungsphase 2",
description="Finale Klausuren und Bewertungen.",
typical_months=[4, 5, 6],
expected_duration_weeks=8,
required_actions=["schedule_exams", "enter_final_grades"],
optional_actions=["use_correction_module"],
),
SchoolYearPhase.YEAR_END: PhaseInfo(
phase=SchoolYearPhase.YEAR_END,
display_name="Jahresabschluss",
description="Abschlusszeugnisse und Versetzung.",
typical_months=[6, 7],
expected_duration_weeks=3,
required_actions=["complete_all_grades", "generate_final_certificates"],
optional_actions=["archive_year", "export_data"],
),
SchoolYearPhase.ARCHIVED: PhaseInfo(
phase=SchoolYearPhase.ARCHIVED,
display_name="Archiviert",
description="Das Schuljahr ist abgeschlossen.",
typical_months=[7, 8],
expected_duration_weeks=0,
required_actions=[],
optional_actions=["view_archive"],
),
}
def get_phase_info(phase: SchoolYearPhase) -> PhaseInfo:
"""Gibt Metadaten für eine Phase zurück."""
return PHASE_INFO.get(phase, PHASE_INFO[SchoolYearPhase.ONBOARDING])
@dataclass
class ClassSummary:
"""Zusammenfassung einer Klasse."""
class_id: str
name: str
grade_level: int
student_count: int
subject: str
@dataclass
class Event:
"""Ein anstehendes Ereignis."""
type: str # "exam", "parent_meeting", "deadline"
title: str
date: datetime
in_days: int
class_id: Optional[str] = None
priority: str = "medium" # "high", "medium", "low"
@dataclass
class Milestone:
"""Ein erreichter Meilenstein."""
milestone: str
completed_at: datetime
@dataclass
class TeacherStats:
"""Statistiken eines Lehrers."""
learning_units_created: int = 0
exams_scheduled: int = 0
exams_graded: int = 0
grades_entered: int = 0
parent_messages_count: int = 0
avg_response_time_hours: float = 0.0
unanswered_messages: int = 0
@dataclass
class TeacherContext:
"""
Aggregierter Kontext für einen Lehrer.
Enthält alle relevanten Informationen für die Antizipations-Engine:
- Identifikation
- Schulkontext
- Zeitlicher Kontext
- Klassen und Schüler
- Termine und Events
- Fortschritt
- Statistiken
"""
# Identifikation
teacher_id: str
school_id: str
school_year_id: str
# Schulkontext
federal_state: str = "niedersachsen" # Bundesland
school_type: str = "gymnasium" # Schulform
# Zeitlicher Kontext
school_year_start: datetime = field(default_factory=datetime.now)
current_phase: SchoolYearPhase = SchoolYearPhase.ONBOARDING
phase_entered_at: datetime = field(default_factory=datetime.now)
weeks_since_start: int = 0
days_in_phase: int = 0
# Klassen und Schüler
classes: List[ClassSummary] = field(default_factory=list)
total_students: int = 0
# Termine und Events
upcoming_events: List[Event] = field(default_factory=list)
overdue_actions: List[Dict[str, Any]] = field(default_factory=list)
# Fortschritt
completed_milestones: List[str] = field(default_factory=list)
pending_milestones: List[str] = field(default_factory=list)
# Statistiken
stats: TeacherStats = field(default_factory=TeacherStats)
def has_completed_milestone(self, milestone: str) -> bool:
"""Prüft ob ein Meilenstein erreicht wurde."""
return milestone in self.completed_milestones
def has_learning_units(self) -> bool:
"""Prüft ob Lerneinheiten erstellt wurden."""
return self.stats.learning_units_created > 0
def is_in_month(self, month: int) -> bool:
"""Prüft ob aktueller Monat übereinstimmt."""
return datetime.now().month == month
def get_next_deadline(self) -> Optional[Event]:
"""Gibt die nächste Deadline zurück."""
for e in self.upcoming_events:
if e.type == "deadline":
return e
return None
def get_next_exam(self) -> Optional[Event]:
"""Gibt die nächste Klausur zurück."""
for e in self.upcoming_events:
if e.type == "exam" and e.in_days > 0:
return e
return None
def to_dict(self) -> Dict[str, Any]:
"""Konvertiert zu Dictionary."""
return {
"teacher_id": self.teacher_id,
"school_id": self.school_id,
"school_year_id": self.school_year_id,
"federal_state": self.federal_state,
"school_type": self.school_type,
"school_year_start": self.school_year_start.isoformat(),
"current_phase": self.current_phase.value,
"phase_entered_at": self.phase_entered_at.isoformat(),
"weeks_since_start": self.weeks_since_start,
"days_in_phase": self.days_in_phase,
"classes": [
{
"class_id": c.class_id,
"name": c.name,
"grade_level": c.grade_level,
"student_count": c.student_count,
"subject": c.subject,
}
for c in self.classes
],
"total_students": self.total_students,
"upcoming_events": [
{
"type": e.type,
"title": e.title,
"date": e.date.isoformat(),
"in_days": e.in_days,
"class_id": e.class_id,
"priority": e.priority,
}
for e in self.upcoming_events
],
"completed_milestones": self.completed_milestones,
"pending_milestones": self.pending_milestones,
"stats": {
"learning_units_created": self.stats.learning_units_created,
"exams_scheduled": self.stats.exams_scheduled,
"exams_graded": self.stats.exams_graded,
"grades_entered": self.stats.grades_entered,
"parent_messages_count": self.stats.parent_messages_count,
"avg_response_time_hours": self.stats.avg_response_time_hours,
"unanswered_messages": self.stats.unanswered_messages,
},
}