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