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>
This commit is contained in:
429
backend/classroom_engine/db_models.py
Normal file
429
backend/classroom_engine/db_models.py
Normal file
@@ -0,0 +1,429 @@
|
||||
"""
|
||||
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]}>"
|
||||
Reference in New Issue
Block a user