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>
430 lines
13 KiB
Python
430 lines
13 KiB
Python
"""
|
|
SQLAlchemy Database Models fuer Classroom Engine (Feature f13).
|
|
|
|
Persistiert Unterrichtsstunden und deren Verlauf in PostgreSQL.
|
|
"""
|
|
from datetime import datetime
|
|
from sqlalchemy import (
|
|
Column, String, Integer, Float, DateTime, JSON,
|
|
Boolean, Text, Enum as SQLEnum, ForeignKey
|
|
)
|
|
from sqlalchemy.orm import relationship
|
|
import enum
|
|
import uuid
|
|
|
|
from .database import Base
|
|
|
|
# Import Phase 8 Context Models
|
|
from .context_models import (
|
|
MacroPhaseEnum,
|
|
EventTypeEnum,
|
|
EventStatusEnum,
|
|
RoutineTypeEnum,
|
|
RecurrencePatternEnum,
|
|
TeacherContextDB,
|
|
SchoolyearEventDB,
|
|
RecurringRoutineDB,
|
|
FEDERAL_STATES,
|
|
SCHOOL_TYPES,
|
|
)
|
|
|
|
# Exports
|
|
__all__ = [
|
|
"LessonPhaseEnum",
|
|
"LessonSessionDB",
|
|
"PhaseHistoryDB",
|
|
"LessonTemplateDB",
|
|
"TeacherSettingsDB",
|
|
"HomeworkStatusEnum",
|
|
"HomeworkDB",
|
|
"MaterialTypeEnum",
|
|
"PhaseMaterialDB",
|
|
"LessonReflectionDB",
|
|
"FeedbackTypeEnum",
|
|
"FeedbackStatusEnum",
|
|
"FeedbackPriorityEnum",
|
|
"TeacherFeedbackDB",
|
|
# Phase 8: Schuljahres-Kontext
|
|
"MacroPhaseEnum",
|
|
"EventTypeEnum",
|
|
"EventStatusEnum",
|
|
"RoutineTypeEnum",
|
|
"RecurrencePatternEnum",
|
|
"TeacherContextDB",
|
|
"SchoolyearEventDB",
|
|
"RecurringRoutineDB",
|
|
"FEDERAL_STATES",
|
|
"SCHOOL_TYPES",
|
|
]
|
|
|
|
|
|
class LessonPhaseEnum(str, enum.Enum):
|
|
"""Unterrichtsphasen als DB-Enum."""
|
|
NOT_STARTED = "not_started"
|
|
EINSTIEG = "einstieg"
|
|
ERARBEITUNG = "erarbeitung"
|
|
SICHERUNG = "sicherung"
|
|
TRANSFER = "transfer"
|
|
REFLEXION = "reflexion"
|
|
ENDED = "ended"
|
|
|
|
|
|
class LessonSessionDB(Base):
|
|
"""
|
|
Persistierte Unterrichtsstunde.
|
|
|
|
Speichert alle Session-Daten inklusive Timer-Status und History.
|
|
"""
|
|
__tablename__ = 'lesson_sessions'
|
|
|
|
# Primary Key
|
|
id = Column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
|
|
|
|
# Beziehungen
|
|
teacher_id = Column(String(100), nullable=False, index=True)
|
|
class_id = Column(String(50), nullable=False, index=True)
|
|
|
|
# Session Metadaten
|
|
subject = Column(String(100), nullable=False)
|
|
topic = Column(String(500))
|
|
|
|
# Status
|
|
current_phase = Column(
|
|
SQLEnum(LessonPhaseEnum),
|
|
default=LessonPhaseEnum.NOT_STARTED,
|
|
nullable=False
|
|
)
|
|
is_paused = Column(Boolean, default=False)
|
|
|
|
# Timestamps
|
|
created_at = Column(DateTime, default=datetime.utcnow)
|
|
lesson_started_at = Column(DateTime)
|
|
lesson_ended_at = Column(DateTime)
|
|
phase_started_at = Column(DateTime)
|
|
pause_started_at = Column(DateTime)
|
|
|
|
# Timer-Daten
|
|
total_paused_seconds = Column(Integer, default=0)
|
|
phase_durations = Column(JSON, default=dict) # {"einstieg": 8, ...}
|
|
|
|
# History & Notizen
|
|
phase_history = Column(JSON, default=list) # [{phase, started_at, ended_at, duration}]
|
|
notes = Column(Text, default="")
|
|
homework = Column(Text, default="")
|
|
|
|
# Relationship zu PhaseHistory (optional fuer detaillierte Abfragen)
|
|
history_entries = relationship("PhaseHistoryDB", back_populates="session", cascade="all, delete-orphan")
|
|
|
|
def __repr__(self):
|
|
return f"<LessonSession {self.id}: {self.subject} ({self.current_phase.value})>"
|
|
|
|
|
|
class PhaseHistoryDB(Base):
|
|
"""
|
|
Einzelner Phasen-Verlaufseintrag.
|
|
|
|
Ermoeglicht detaillierte Statistiken ueber Phasendauern.
|
|
"""
|
|
__tablename__ = 'lesson_phase_history'
|
|
|
|
id = Column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
|
|
session_id = Column(String(36), ForeignKey('lesson_sessions.id'), nullable=False, index=True)
|
|
|
|
phase = Column(SQLEnum(LessonPhaseEnum), nullable=False)
|
|
started_at = Column(DateTime, nullable=False)
|
|
ended_at = Column(DateTime)
|
|
duration_seconds = Column(Integer)
|
|
|
|
# Zusaetzliche Metriken
|
|
was_extended = Column(Boolean, default=False)
|
|
extension_minutes = Column(Integer, default=0)
|
|
pause_count = Column(Integer, default=0)
|
|
total_pause_seconds = Column(Integer, default=0)
|
|
|
|
# Relationship
|
|
session = relationship("LessonSessionDB", back_populates="history_entries")
|
|
|
|
def __repr__(self):
|
|
return f"<PhaseHistory {self.phase.value} ({self.duration_seconds}s)>"
|
|
|
|
|
|
class LessonTemplateDB(Base):
|
|
"""
|
|
Stunden-Vorlage (Feature f37).
|
|
|
|
Ermoeglicht Speicherung von wiederverwendbaren Stundenkonfigurationen.
|
|
"""
|
|
__tablename__ = 'lesson_templates'
|
|
|
|
id = Column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
|
|
teacher_id = Column(String(100), nullable=False, index=True)
|
|
|
|
# Basis-Infos
|
|
name = Column(String(200), nullable=False)
|
|
description = Column(Text, default="")
|
|
subject = Column(String(100), default="")
|
|
grade_level = Column(String(50), default="")
|
|
|
|
# Phasenkonfiguration
|
|
phase_durations = Column(JSON, default=dict)
|
|
|
|
# Vorbelegungen
|
|
default_topic = Column(String(500), default="")
|
|
default_notes = Column(Text, default="")
|
|
|
|
# Metadaten
|
|
is_public = Column(Boolean, default=False)
|
|
usage_count = Column(Integer, default=0)
|
|
|
|
created_at = Column(DateTime, default=datetime.utcnow)
|
|
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
|
|
|
def __repr__(self):
|
|
return f"<LessonTemplate {self.name} ({self.teacher_id})>"
|
|
|
|
|
|
class TeacherSettingsDB(Base):
|
|
"""
|
|
Lehrer-spezifische Einstellungen (Feature f16).
|
|
|
|
Speichert individuelle Praeferenzen wie Phasendauern.
|
|
"""
|
|
__tablename__ = 'teacher_settings'
|
|
|
|
id = Column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
|
|
teacher_id = Column(String(100), unique=True, nullable=False, index=True)
|
|
|
|
# Individuelle Phasendauern
|
|
default_phase_durations = Column(JSON, default=dict)
|
|
|
|
# UI Praeferenzen
|
|
audio_enabled = Column(Boolean, default=True)
|
|
high_contrast = Column(Boolean, default=False)
|
|
|
|
# Statistik-Einstellungen
|
|
show_statistics = Column(Boolean, default=True)
|
|
|
|
created_at = Column(DateTime, default=datetime.utcnow)
|
|
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
|
|
|
def __repr__(self):
|
|
return f"<TeacherSettings {self.teacher_id}>"
|
|
|
|
|
|
class HomeworkStatusEnum(str, enum.Enum):
|
|
"""Status-Enum fuer Hausaufgaben."""
|
|
ASSIGNED = "assigned"
|
|
IN_PROGRESS = "in_progress"
|
|
COMPLETED = "completed"
|
|
OVERDUE = "overdue"
|
|
|
|
|
|
class HomeworkDB(Base):
|
|
"""
|
|
Hausaufgaben-Tracking (Feature f20).
|
|
|
|
Ermoeglicht die Verfolgung von Hausaufgaben ueber Sessions hinweg.
|
|
"""
|
|
__tablename__ = 'homework_assignments'
|
|
|
|
id = Column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
|
|
teacher_id = Column(String(100), nullable=False, index=True)
|
|
class_id = Column(String(50), nullable=False, index=True)
|
|
subject = Column(String(100), nullable=False)
|
|
|
|
# Aufgaben-Details
|
|
title = Column(String(300), nullable=False)
|
|
description = Column(Text, default="")
|
|
|
|
# Verknuepfung zur Session (optional)
|
|
session_id = Column(String(36), ForeignKey('lesson_sessions.id'), nullable=True, index=True)
|
|
|
|
# Faelligkeit und Status
|
|
due_date = Column(DateTime, nullable=True, index=True)
|
|
status = Column(
|
|
SQLEnum(HomeworkStatusEnum),
|
|
default=HomeworkStatusEnum.ASSIGNED,
|
|
nullable=False
|
|
)
|
|
|
|
created_at = Column(DateTime, default=datetime.utcnow)
|
|
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
|
|
|
def __repr__(self):
|
|
return f"<Homework {self.title} ({self.status.value})>"
|
|
|
|
|
|
class MaterialTypeEnum(str, enum.Enum):
|
|
"""Typ-Enum fuer Materialien."""
|
|
DOCUMENT = "document"
|
|
LINK = "link"
|
|
VIDEO = "video"
|
|
IMAGE = "image"
|
|
WORKSHEET = "worksheet"
|
|
PRESENTATION = "presentation"
|
|
OTHER = "other"
|
|
|
|
|
|
class PhaseMaterialDB(Base):
|
|
"""
|
|
Phasen-Materialien (Feature f19).
|
|
|
|
Ermoeglicht das Anhaengen von Dokumenten und Links an Unterrichtsphasen.
|
|
"""
|
|
__tablename__ = 'phase_materials'
|
|
|
|
id = Column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
|
|
teacher_id = Column(String(100), nullable=False, index=True)
|
|
|
|
# Material-Details
|
|
title = Column(String(300), nullable=False)
|
|
material_type = Column(
|
|
SQLEnum(MaterialTypeEnum),
|
|
default=MaterialTypeEnum.DOCUMENT,
|
|
nullable=False
|
|
)
|
|
url = Column(String(2000), nullable=True) # URL oder Dateipfad
|
|
description = Column(Text, default="")
|
|
|
|
# Phasen-Zuordnung
|
|
phase = Column(String(50), nullable=True, index=True) # einstieg, erarbeitung, etc.
|
|
subject = Column(String(100), default="")
|
|
grade_level = Column(String(50), default="")
|
|
|
|
# Tags als JSON Array
|
|
tags = Column(JSON, default=list)
|
|
|
|
# Sharing und Nutzung
|
|
is_public = Column(Boolean, default=False)
|
|
usage_count = Column(Integer, default=0)
|
|
|
|
# Optionale Session-Verknuepfung
|
|
session_id = Column(String(36), ForeignKey('lesson_sessions.id'), nullable=True, index=True)
|
|
|
|
created_at = Column(DateTime, default=datetime.utcnow)
|
|
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
|
|
|
def __repr__(self):
|
|
return f"<PhaseMaterial {self.title} ({self.material_type.value})>"
|
|
|
|
|
|
class LessonReflectionDB(Base):
|
|
"""
|
|
Post-Lesson Reflection (Phase 5: Analytics).
|
|
|
|
Ermoeglicht Lehrern, nach der Stunde Reflexionsnotizen zu speichern.
|
|
"""
|
|
__tablename__ = 'lesson_reflections'
|
|
|
|
id = Column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
|
|
session_id = Column(String(36), ForeignKey('lesson_sessions.id'), nullable=False, unique=True, index=True)
|
|
teacher_id = Column(String(100), nullable=False, index=True)
|
|
|
|
# Reflexions-Inhalt
|
|
notes = Column(Text, default="")
|
|
|
|
# Optionale Selbst-Bewertung (1-5)
|
|
overall_rating = Column(Integer, nullable=True)
|
|
|
|
# Was hat gut funktioniert? (JSON Array)
|
|
what_worked = Column(JSON, default=list)
|
|
|
|
# Was wuerde ich anders machen? (JSON Array)
|
|
improvements = Column(JSON, default=list)
|
|
|
|
# Notizen fuer naechste Stunde
|
|
notes_for_next_lesson = Column(Text, default="")
|
|
|
|
created_at = Column(DateTime, default=datetime.utcnow)
|
|
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
|
|
|
def __repr__(self):
|
|
return f"<LessonReflection session={self.session_id}>"
|
|
|
|
|
|
class FeedbackTypeEnum(str, enum.Enum):
|
|
"""Feedback-Typen fuer Lehrer-Rueckmeldungen."""
|
|
BUG = "bug"
|
|
FEATURE_REQUEST = "feature_request"
|
|
IMPROVEMENT = "improvement"
|
|
PRAISE = "praise"
|
|
QUESTION = "question"
|
|
|
|
|
|
class FeedbackStatusEnum(str, enum.Enum):
|
|
"""Status eines Feedbacks."""
|
|
NEW = "new"
|
|
ACKNOWLEDGED = "acknowledged"
|
|
PLANNED = "planned"
|
|
IMPLEMENTED = "implemented"
|
|
DECLINED = "declined"
|
|
|
|
|
|
class FeedbackPriorityEnum(str, enum.Enum):
|
|
"""Prioritaet eines Feedbacks."""
|
|
CRITICAL = "critical"
|
|
HIGH = "high"
|
|
MEDIUM = "medium"
|
|
LOW = "low"
|
|
|
|
|
|
class TeacherFeedbackDB(Base):
|
|
"""
|
|
Lehrer-Feedback zum Companion-Modul (Phase 7).
|
|
|
|
Ermoeglicht Lehrern, Bug-Reports, Feature-Requests und Verbesserungen
|
|
direkt aus dem Lehrer-Frontend zu senden.
|
|
"""
|
|
__tablename__ = 'teacher_feedback'
|
|
|
|
id = Column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
|
|
|
|
# Wer hat das Feedback gegeben?
|
|
teacher_id = Column(String(100), nullable=False, index=True)
|
|
teacher_name = Column(String(200), default="")
|
|
teacher_email = Column(String(200), default="")
|
|
|
|
# Feedback-Inhalt
|
|
title = Column(String(500), nullable=False)
|
|
description = Column(Text, nullable=False)
|
|
|
|
# Kategorisierung
|
|
feedback_type = Column(
|
|
SQLEnum(FeedbackTypeEnum),
|
|
default=FeedbackTypeEnum.IMPROVEMENT,
|
|
nullable=False
|
|
)
|
|
priority = Column(
|
|
SQLEnum(FeedbackPriorityEnum),
|
|
default=FeedbackPriorityEnum.MEDIUM,
|
|
nullable=False
|
|
)
|
|
status = Column(
|
|
SQLEnum(FeedbackStatusEnum),
|
|
default=FeedbackStatusEnum.NEW,
|
|
nullable=False,
|
|
index=True
|
|
)
|
|
|
|
# Optionale Verknuepfung zu Feature
|
|
related_feature = Column(String(50), nullable=True)
|
|
|
|
# Kontext: Wo war der User als er Feedback gab?
|
|
context_url = Column(String(500), default="")
|
|
context_phase = Column(String(50), default="") # z.B. "einstieg"
|
|
context_session_id = Column(String(36), nullable=True)
|
|
|
|
# Browser/Device Info
|
|
user_agent = Column(String(500), default="")
|
|
|
|
# Entwickler-Antwort
|
|
response = Column(Text, default="")
|
|
responded_at = Column(DateTime, nullable=True)
|
|
responded_by = Column(String(100), nullable=True)
|
|
|
|
created_at = Column(DateTime, default=datetime.utcnow)
|
|
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
|
|
|
def __repr__(self):
|
|
return f"<TeacherFeedback {self.id[:8]} - {self.title[:30]}>"
|