Files
breakpilot-lehrer/backend-lehrer/classroom_engine/repository_context.py
Benjamin Admin b2a0126f14 [split-required] Split remaining Python monoliths (Phase 1 continued)
klausur-service (7 monoliths):
- grid_editor_helpers.py (1,737 → 5 files: columns, filters, headers, zones)
- cv_cell_grid.py (1,675 → 7 files: build, legacy, streaming, merge, vocab)
- worksheet_editor_api.py (1,305 → 4 files: models, AI, reconstruct, routes)
- legal_corpus_ingestion.py (1,280 → 3 files: registry, chunking, ingestion)
- cv_review.py (1,248 → 4 files: pipeline, spell, LLM, barrel)
- cv_preprocessing.py (1,166 → 3 files: deskew, dewarp, barrel)
- rbac.py, admin_api.py, routes/eh.py remain (next batch)

backend-lehrer (1 monolith):
- classroom_engine/repository.py (1,705 → 7 files by domain)

All re-export barrels preserve backward compatibility.
Zero import errors verified.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-24 22:47:59 +02:00

454 lines
16 KiB
Python

"""
Teacher Context, Schoolyear Event & Recurring Routine Repositories.
CRUD-Operationen fuer Schuljahres-Kontext (Phase 8).
"""
from datetime import datetime
from typing import Optional, List, Dict, Any
from sqlalchemy.orm import Session as DBSession
from .context_models import (
TeacherContextDB, SchoolyearEventDB, RecurringRoutineDB,
MacroPhaseEnum, EventTypeEnum, EventStatusEnum,
RoutineTypeEnum, RecurrencePatternEnum,
FEDERAL_STATES, SCHOOL_TYPES,
)
class TeacherContextRepository:
"""Repository fuer Lehrer-Kontext CRUD-Operationen (Phase 8)."""
def __init__(self, db: DBSession):
self.db = db
# ==================== CREATE / GET-OR-CREATE ====================
def get_or_create(self, teacher_id: str) -> TeacherContextDB:
"""
Holt den Kontext eines Lehrers oder erstellt einen neuen.
Args:
teacher_id: ID des Lehrers
Returns:
TeacherContextDB Model
"""
context = self.get_by_teacher_id(teacher_id)
if context:
return context
# Neuen Kontext erstellen
from uuid import uuid4
context = TeacherContextDB(
id=str(uuid4()),
teacher_id=teacher_id,
macro_phase=MacroPhaseEnum.ONBOARDING,
)
self.db.add(context)
self.db.commit()
self.db.refresh(context)
return context
# ==================== READ ====================
def get_by_teacher_id(self, teacher_id: str) -> Optional[TeacherContextDB]:
"""Holt den Kontext eines Lehrers."""
return self.db.query(TeacherContextDB).filter(
TeacherContextDB.teacher_id == teacher_id
).first()
# ==================== UPDATE ====================
def update_context(
self,
teacher_id: str,
federal_state: str = None,
school_type: str = None,
schoolyear: str = None,
schoolyear_start: datetime = None,
macro_phase: str = None,
current_week: int = None,
) -> Optional[TeacherContextDB]:
"""Aktualisiert den Kontext eines Lehrers."""
context = self.get_or_create(teacher_id)
if federal_state is not None:
context.federal_state = federal_state
if school_type is not None:
context.school_type = school_type
if schoolyear is not None:
context.schoolyear = schoolyear
if schoolyear_start is not None:
context.schoolyear_start = schoolyear_start
if macro_phase is not None:
context.macro_phase = MacroPhaseEnum(macro_phase)
if current_week is not None:
context.current_week = current_week
self.db.commit()
self.db.refresh(context)
return context
def complete_onboarding(self, teacher_id: str) -> TeacherContextDB:
"""Markiert Onboarding als abgeschlossen."""
context = self.get_or_create(teacher_id)
context.onboarding_completed = True
context.macro_phase = MacroPhaseEnum.SCHULJAHRESSTART
self.db.commit()
self.db.refresh(context)
return context
def update_flags(
self,
teacher_id: str,
has_classes: bool = None,
has_schedule: bool = None,
is_exam_period: bool = None,
is_before_holidays: bool = None,
) -> TeacherContextDB:
"""Aktualisiert die Status-Flags eines Kontexts."""
context = self.get_or_create(teacher_id)
if has_classes is not None:
context.has_classes = has_classes
if has_schedule is not None:
context.has_schedule = has_schedule
if is_exam_period is not None:
context.is_exam_period = is_exam_period
if is_before_holidays is not None:
context.is_before_holidays = is_before_holidays
self.db.commit()
self.db.refresh(context)
return context
def to_dict(self, context: TeacherContextDB) -> Dict[str, Any]:
"""Konvertiert DB-Model zu Dictionary."""
return {
"id": context.id,
"teacher_id": context.teacher_id,
"school": {
"federal_state": context.federal_state,
"federal_state_name": FEDERAL_STATES.get(context.federal_state, ""),
"school_type": context.school_type,
"school_type_name": SCHOOL_TYPES.get(context.school_type, ""),
},
"school_year": {
"id": context.schoolyear,
"start": context.schoolyear_start.isoformat() if context.schoolyear_start else None,
"current_week": context.current_week,
},
"macro_phase": {
"id": context.macro_phase.value,
"label": self._get_phase_label(context.macro_phase),
},
"flags": {
"onboarding_completed": context.onboarding_completed,
"has_classes": context.has_classes,
"has_schedule": context.has_schedule,
"is_exam_period": context.is_exam_period,
"is_before_holidays": context.is_before_holidays,
},
"created_at": context.created_at.isoformat() if context.created_at else None,
"updated_at": context.updated_at.isoformat() if context.updated_at else None,
}
def _get_phase_label(self, phase: MacroPhaseEnum) -> str:
"""Gibt den Anzeigenamen einer Makro-Phase zurueck."""
labels = {
MacroPhaseEnum.ONBOARDING: "Einrichtung",
MacroPhaseEnum.SCHULJAHRESSTART: "Schuljahresstart",
MacroPhaseEnum.UNTERRICHTSAUFBAU: "Unterrichtsaufbau",
MacroPhaseEnum.LEISTUNGSPHASE_1: "Leistungsphase 1",
MacroPhaseEnum.HALBJAHRESABSCHLUSS: "Halbjahresabschluss",
MacroPhaseEnum.LEISTUNGSPHASE_2: "Leistungsphase 2",
MacroPhaseEnum.JAHRESABSCHLUSS: "Jahresabschluss",
}
return labels.get(phase, phase.value)
class SchoolyearEventRepository:
"""Repository fuer Schuljahr-Events (Phase 8)."""
def __init__(self, db: DBSession):
self.db = db
def create(
self,
teacher_id: str,
title: str,
start_date: datetime,
event_type: str = "other",
end_date: datetime = None,
class_id: str = None,
subject: str = None,
description: str = "",
needs_preparation: bool = True,
reminder_days_before: int = 7,
extra_data: Dict[str, Any] = None,
) -> SchoolyearEventDB:
"""Erstellt ein neues Schuljahr-Event."""
from uuid import uuid4
event = SchoolyearEventDB(
id=str(uuid4()),
teacher_id=teacher_id,
title=title,
event_type=EventTypeEnum(event_type),
start_date=start_date,
end_date=end_date,
class_id=class_id,
subject=subject,
description=description,
needs_preparation=needs_preparation,
reminder_days_before=reminder_days_before,
extra_data=extra_data or {},
)
self.db.add(event)
self.db.commit()
self.db.refresh(event)
return event
def get_by_id(self, event_id: str) -> Optional[SchoolyearEventDB]:
"""Holt ein Event nach ID."""
return self.db.query(SchoolyearEventDB).filter(
SchoolyearEventDB.id == event_id
).first()
def get_by_teacher(
self,
teacher_id: str,
status: str = None,
event_type: str = None,
limit: int = 50,
) -> List[SchoolyearEventDB]:
"""Holt Events eines Lehrers."""
query = self.db.query(SchoolyearEventDB).filter(
SchoolyearEventDB.teacher_id == teacher_id
)
if status:
query = query.filter(SchoolyearEventDB.status == EventStatusEnum(status))
if event_type:
query = query.filter(SchoolyearEventDB.event_type == EventTypeEnum(event_type))
return query.order_by(SchoolyearEventDB.start_date).limit(limit).all()
def get_upcoming(
self,
teacher_id: str,
days: int = 30,
limit: int = 10,
) -> List[SchoolyearEventDB]:
"""Holt anstehende Events der naechsten X Tage."""
from datetime import timedelta
now = datetime.utcnow()
end = now + timedelta(days=days)
return self.db.query(SchoolyearEventDB).filter(
SchoolyearEventDB.teacher_id == teacher_id,
SchoolyearEventDB.start_date >= now,
SchoolyearEventDB.start_date <= end,
SchoolyearEventDB.status != EventStatusEnum.CANCELLED,
).order_by(SchoolyearEventDB.start_date).limit(limit).all()
def update_status(
self,
event_id: str,
status: str,
preparation_done: bool = None,
) -> Optional[SchoolyearEventDB]:
"""Aktualisiert den Status eines Events."""
event = self.get_by_id(event_id)
if not event:
return None
event.status = EventStatusEnum(status)
if preparation_done is not None:
event.preparation_done = preparation_done
self.db.commit()
self.db.refresh(event)
return event
def delete(self, event_id: str) -> bool:
"""Loescht ein Event."""
event = self.get_by_id(event_id)
if not event:
return False
self.db.delete(event)
self.db.commit()
return True
def to_dict(self, event: SchoolyearEventDB) -> Dict[str, Any]:
"""Konvertiert DB-Model zu Dictionary."""
return {
"id": event.id,
"teacher_id": event.teacher_id,
"event_type": event.event_type.value,
"title": event.title,
"description": event.description,
"start_date": event.start_date.isoformat() if event.start_date else None,
"end_date": event.end_date.isoformat() if event.end_date else None,
"class_id": event.class_id,
"subject": event.subject,
"status": event.status.value,
"needs_preparation": event.needs_preparation,
"preparation_done": event.preparation_done,
"reminder_days_before": event.reminder_days_before,
"extra_data": event.extra_data,
"created_at": event.created_at.isoformat() if event.created_at else None,
}
class RecurringRoutineRepository:
"""Repository fuer wiederkehrende Routinen (Phase 8)."""
def __init__(self, db: DBSession):
self.db = db
def create(
self,
teacher_id: str,
title: str,
routine_type: str = "other",
recurrence_pattern: str = "weekly",
day_of_week: int = None,
day_of_month: int = None,
time_of_day: str = None, # Format: "14:00"
duration_minutes: int = 60,
description: str = "",
valid_from: datetime = None,
valid_until: datetime = None,
) -> RecurringRoutineDB:
"""Erstellt eine neue wiederkehrende Routine."""
from uuid import uuid4
from datetime import time as dt_time
time_obj = None
if time_of_day:
parts = time_of_day.split(":")
time_obj = dt_time(int(parts[0]), int(parts[1]))
routine = RecurringRoutineDB(
id=str(uuid4()),
teacher_id=teacher_id,
title=title,
routine_type=RoutineTypeEnum(routine_type),
recurrence_pattern=RecurrencePatternEnum(recurrence_pattern),
day_of_week=day_of_week,
day_of_month=day_of_month,
time_of_day=time_obj,
duration_minutes=duration_minutes,
description=description,
valid_from=valid_from,
valid_until=valid_until,
)
self.db.add(routine)
self.db.commit()
self.db.refresh(routine)
return routine
def get_by_id(self, routine_id: str) -> Optional[RecurringRoutineDB]:
"""Holt eine Routine nach ID."""
return self.db.query(RecurringRoutineDB).filter(
RecurringRoutineDB.id == routine_id
).first()
def get_by_teacher(
self,
teacher_id: str,
is_active: bool = True,
routine_type: str = None,
) -> List[RecurringRoutineDB]:
"""Holt Routinen eines Lehrers."""
query = self.db.query(RecurringRoutineDB).filter(
RecurringRoutineDB.teacher_id == teacher_id
)
if is_active is not None:
query = query.filter(RecurringRoutineDB.is_active == is_active)
if routine_type:
query = query.filter(RecurringRoutineDB.routine_type == RoutineTypeEnum(routine_type))
return query.all()
def get_today(self, teacher_id: str) -> List[RecurringRoutineDB]:
"""Holt Routinen die heute stattfinden."""
today = datetime.utcnow()
day_of_week = today.weekday() # 0 = Montag
day_of_month = today.day
routines = self.get_by_teacher(teacher_id, is_active=True)
today_routines = []
for routine in routines:
if routine.recurrence_pattern == RecurrencePatternEnum.DAILY:
today_routines.append(routine)
elif routine.recurrence_pattern == RecurrencePatternEnum.WEEKLY:
if routine.day_of_week == day_of_week:
today_routines.append(routine)
elif routine.recurrence_pattern == RecurrencePatternEnum.BIWEEKLY:
# Vereinfacht: Pruefen ob Tag passt (echte Logik braucht Startdatum)
if routine.day_of_week == day_of_week:
today_routines.append(routine)
elif routine.recurrence_pattern == RecurrencePatternEnum.MONTHLY:
if routine.day_of_month == day_of_month:
today_routines.append(routine)
return today_routines
def update(
self,
routine_id: str,
title: str = None,
is_active: bool = None,
day_of_week: int = None,
time_of_day: str = None,
) -> Optional[RecurringRoutineDB]:
"""Aktualisiert eine Routine."""
routine = self.get_by_id(routine_id)
if not routine:
return None
if title is not None:
routine.title = title
if is_active is not None:
routine.is_active = is_active
if day_of_week is not None:
routine.day_of_week = day_of_week
if time_of_day is not None:
from datetime import time as dt_time
parts = time_of_day.split(":")
routine.time_of_day = dt_time(int(parts[0]), int(parts[1]))
self.db.commit()
self.db.refresh(routine)
return routine
def delete(self, routine_id: str) -> bool:
"""Loescht eine Routine."""
routine = self.get_by_id(routine_id)
if not routine:
return False
self.db.delete(routine)
self.db.commit()
return True
def to_dict(self, routine: RecurringRoutineDB) -> Dict[str, Any]:
"""Konvertiert DB-Model zu Dictionary."""
return {
"id": routine.id,
"teacher_id": routine.teacher_id,
"routine_type": routine.routine_type.value,
"title": routine.title,
"description": routine.description,
"recurrence_pattern": routine.recurrence_pattern.value,
"day_of_week": routine.day_of_week,
"day_of_month": routine.day_of_month,
"time_of_day": routine.time_of_day.isoformat() if routine.time_of_day else None,
"duration_minutes": routine.duration_minutes,
"is_active": routine.is_active,
"valid_from": routine.valid_from.isoformat() if routine.valid_from else None,
"valid_until": routine.valid_until.isoformat() if routine.valid_until else None,
"created_at": routine.created_at.isoformat() if routine.created_at else None,
}