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>
383 lines
13 KiB
Python
383 lines
13 KiB
Python
"""
|
|
Homework & Material Repositories.
|
|
|
|
CRUD-Operationen fuer Hausaufgaben (Feature f20) und Phasen-Materialien (Feature f19).
|
|
"""
|
|
from datetime import datetime
|
|
from typing import Optional, List
|
|
|
|
from sqlalchemy.orm import Session as DBSession
|
|
|
|
from .db_models import (
|
|
HomeworkDB, HomeworkStatusEnum, PhaseMaterialDB, MaterialTypeEnum,
|
|
)
|
|
from .models import (
|
|
Homework, HomeworkStatus, PhaseMaterial, MaterialType,
|
|
)
|
|
|
|
|
|
class HomeworkRepository:
|
|
"""Repository fuer Hausaufgaben-Tracking (Feature f20)."""
|
|
|
|
def __init__(self, db: DBSession):
|
|
self.db = db
|
|
|
|
# ==================== CREATE ====================
|
|
|
|
def create(self, homework: Homework) -> HomeworkDB:
|
|
"""Erstellt eine neue Hausaufgabe."""
|
|
db_homework = HomeworkDB(
|
|
id=homework.homework_id,
|
|
teacher_id=homework.teacher_id,
|
|
class_id=homework.class_id,
|
|
subject=homework.subject,
|
|
title=homework.title,
|
|
description=homework.description,
|
|
session_id=homework.session_id,
|
|
due_date=homework.due_date,
|
|
status=HomeworkStatusEnum(homework.status.value),
|
|
)
|
|
self.db.add(db_homework)
|
|
self.db.commit()
|
|
self.db.refresh(db_homework)
|
|
return db_homework
|
|
|
|
# ==================== READ ====================
|
|
|
|
def get_by_id(self, homework_id: str) -> Optional[HomeworkDB]:
|
|
"""Holt eine Hausaufgabe nach ID."""
|
|
return self.db.query(HomeworkDB).filter(
|
|
HomeworkDB.id == homework_id
|
|
).first()
|
|
|
|
def get_by_teacher(
|
|
self,
|
|
teacher_id: str,
|
|
status: Optional[str] = None,
|
|
limit: int = 50
|
|
) -> List[HomeworkDB]:
|
|
"""Holt alle Hausaufgaben eines Lehrers."""
|
|
query = self.db.query(HomeworkDB).filter(
|
|
HomeworkDB.teacher_id == teacher_id
|
|
)
|
|
if status:
|
|
query = query.filter(HomeworkDB.status == HomeworkStatusEnum(status))
|
|
return query.order_by(
|
|
HomeworkDB.due_date.asc().nullslast(),
|
|
HomeworkDB.created_at.desc()
|
|
).limit(limit).all()
|
|
|
|
def get_by_class(
|
|
self,
|
|
class_id: str,
|
|
teacher_id: str,
|
|
include_completed: bool = False,
|
|
limit: int = 20
|
|
) -> List[HomeworkDB]:
|
|
"""Holt alle Hausaufgaben einer Klasse."""
|
|
query = self.db.query(HomeworkDB).filter(
|
|
HomeworkDB.class_id == class_id,
|
|
HomeworkDB.teacher_id == teacher_id
|
|
)
|
|
if not include_completed:
|
|
query = query.filter(HomeworkDB.status != HomeworkStatusEnum.COMPLETED)
|
|
return query.order_by(
|
|
HomeworkDB.due_date.asc().nullslast(),
|
|
HomeworkDB.created_at.desc()
|
|
).limit(limit).all()
|
|
|
|
def get_by_session(self, session_id: str) -> List[HomeworkDB]:
|
|
"""Holt alle Hausaufgaben einer Session."""
|
|
return self.db.query(HomeworkDB).filter(
|
|
HomeworkDB.session_id == session_id
|
|
).order_by(HomeworkDB.created_at.desc()).all()
|
|
|
|
def get_pending(
|
|
self,
|
|
teacher_id: str,
|
|
days_ahead: int = 7
|
|
) -> List[HomeworkDB]:
|
|
"""Holt anstehende Hausaufgaben der naechsten X Tage."""
|
|
from datetime import timedelta
|
|
cutoff = datetime.utcnow() + timedelta(days=days_ahead)
|
|
return self.db.query(HomeworkDB).filter(
|
|
HomeworkDB.teacher_id == teacher_id,
|
|
HomeworkDB.status.in_([HomeworkStatusEnum.ASSIGNED, HomeworkStatusEnum.IN_PROGRESS]),
|
|
HomeworkDB.due_date <= cutoff
|
|
).order_by(HomeworkDB.due_date.asc()).all()
|
|
|
|
# ==================== UPDATE ====================
|
|
|
|
def update_status(
|
|
self,
|
|
homework_id: str,
|
|
status: HomeworkStatus
|
|
) -> Optional[HomeworkDB]:
|
|
"""Aktualisiert den Status einer Hausaufgabe."""
|
|
db_homework = self.get_by_id(homework_id)
|
|
if not db_homework:
|
|
return None
|
|
|
|
db_homework.status = HomeworkStatusEnum(status.value)
|
|
self.db.commit()
|
|
self.db.refresh(db_homework)
|
|
return db_homework
|
|
|
|
def update(self, homework: Homework) -> Optional[HomeworkDB]:
|
|
"""Aktualisiert eine Hausaufgabe."""
|
|
db_homework = self.get_by_id(homework.homework_id)
|
|
if not db_homework:
|
|
return None
|
|
|
|
db_homework.title = homework.title
|
|
db_homework.description = homework.description
|
|
db_homework.due_date = homework.due_date
|
|
db_homework.status = HomeworkStatusEnum(homework.status.value)
|
|
|
|
self.db.commit()
|
|
self.db.refresh(db_homework)
|
|
return db_homework
|
|
|
|
# ==================== DELETE ====================
|
|
|
|
def delete(self, homework_id: str) -> bool:
|
|
"""Loescht eine Hausaufgabe."""
|
|
db_homework = self.get_by_id(homework_id)
|
|
if not db_homework:
|
|
return False
|
|
|
|
self.db.delete(db_homework)
|
|
self.db.commit()
|
|
return True
|
|
|
|
# ==================== CONVERSION ====================
|
|
|
|
def to_dataclass(self, db_homework: HomeworkDB) -> Homework:
|
|
"""Konvertiert DB-Model zu Dataclass."""
|
|
return Homework(
|
|
homework_id=db_homework.id,
|
|
teacher_id=db_homework.teacher_id,
|
|
class_id=db_homework.class_id,
|
|
subject=db_homework.subject,
|
|
title=db_homework.title,
|
|
description=db_homework.description or "",
|
|
session_id=db_homework.session_id,
|
|
due_date=db_homework.due_date,
|
|
status=HomeworkStatus(db_homework.status.value),
|
|
created_at=db_homework.created_at,
|
|
updated_at=db_homework.updated_at,
|
|
)
|
|
|
|
|
|
class MaterialRepository:
|
|
"""Repository fuer Phasen-Materialien (Feature f19)."""
|
|
|
|
def __init__(self, db: DBSession):
|
|
self.db = db
|
|
|
|
# ==================== CREATE ====================
|
|
|
|
def create(self, material: PhaseMaterial) -> PhaseMaterialDB:
|
|
"""Erstellt ein neues Material."""
|
|
db_material = PhaseMaterialDB(
|
|
id=material.material_id,
|
|
teacher_id=material.teacher_id,
|
|
title=material.title,
|
|
material_type=MaterialTypeEnum(material.material_type.value),
|
|
url=material.url,
|
|
description=material.description,
|
|
phase=material.phase,
|
|
subject=material.subject,
|
|
grade_level=material.grade_level,
|
|
tags=material.tags,
|
|
is_public=material.is_public,
|
|
usage_count=material.usage_count,
|
|
session_id=material.session_id,
|
|
)
|
|
self.db.add(db_material)
|
|
self.db.commit()
|
|
self.db.refresh(db_material)
|
|
return db_material
|
|
|
|
# ==================== READ ====================
|
|
|
|
def get_by_id(self, material_id: str) -> Optional[PhaseMaterialDB]:
|
|
"""Holt ein Material nach ID."""
|
|
return self.db.query(PhaseMaterialDB).filter(
|
|
PhaseMaterialDB.id == material_id
|
|
).first()
|
|
|
|
def get_by_teacher(
|
|
self,
|
|
teacher_id: str,
|
|
phase: Optional[str] = None,
|
|
subject: Optional[str] = None,
|
|
limit: int = 50
|
|
) -> List[PhaseMaterialDB]:
|
|
"""Holt alle Materialien eines Lehrers."""
|
|
query = self.db.query(PhaseMaterialDB).filter(
|
|
PhaseMaterialDB.teacher_id == teacher_id
|
|
)
|
|
if phase:
|
|
query = query.filter(PhaseMaterialDB.phase == phase)
|
|
if subject:
|
|
query = query.filter(PhaseMaterialDB.subject == subject)
|
|
|
|
return query.order_by(
|
|
PhaseMaterialDB.usage_count.desc(),
|
|
PhaseMaterialDB.created_at.desc()
|
|
).limit(limit).all()
|
|
|
|
def get_by_phase(
|
|
self,
|
|
phase: str,
|
|
teacher_id: str,
|
|
include_public: bool = True
|
|
) -> List[PhaseMaterialDB]:
|
|
"""Holt alle Materialien fuer eine bestimmte Phase."""
|
|
if include_public:
|
|
return self.db.query(PhaseMaterialDB).filter(
|
|
PhaseMaterialDB.phase == phase,
|
|
(PhaseMaterialDB.teacher_id == teacher_id) |
|
|
(PhaseMaterialDB.is_public == True)
|
|
).order_by(
|
|
PhaseMaterialDB.usage_count.desc()
|
|
).all()
|
|
else:
|
|
return self.db.query(PhaseMaterialDB).filter(
|
|
PhaseMaterialDB.phase == phase,
|
|
PhaseMaterialDB.teacher_id == teacher_id
|
|
).order_by(
|
|
PhaseMaterialDB.created_at.desc()
|
|
).all()
|
|
|
|
def get_by_session(self, session_id: str) -> List[PhaseMaterialDB]:
|
|
"""Holt alle Materialien einer Session."""
|
|
return self.db.query(PhaseMaterialDB).filter(
|
|
PhaseMaterialDB.session_id == session_id
|
|
).order_by(PhaseMaterialDB.phase, PhaseMaterialDB.created_at).all()
|
|
|
|
def get_public_materials(
|
|
self,
|
|
phase: Optional[str] = None,
|
|
subject: Optional[str] = None,
|
|
limit: int = 20
|
|
) -> List[PhaseMaterialDB]:
|
|
"""Holt oeffentliche Materialien."""
|
|
query = self.db.query(PhaseMaterialDB).filter(
|
|
PhaseMaterialDB.is_public == True
|
|
)
|
|
if phase:
|
|
query = query.filter(PhaseMaterialDB.phase == phase)
|
|
if subject:
|
|
query = query.filter(PhaseMaterialDB.subject == subject)
|
|
|
|
return query.order_by(
|
|
PhaseMaterialDB.usage_count.desc()
|
|
).limit(limit).all()
|
|
|
|
def search_by_tags(
|
|
self,
|
|
tags: List[str],
|
|
teacher_id: Optional[str] = None
|
|
) -> List[PhaseMaterialDB]:
|
|
"""Sucht Materialien nach Tags."""
|
|
query = self.db.query(PhaseMaterialDB)
|
|
if teacher_id:
|
|
query = query.filter(
|
|
(PhaseMaterialDB.teacher_id == teacher_id) |
|
|
(PhaseMaterialDB.is_public == True)
|
|
)
|
|
else:
|
|
query = query.filter(PhaseMaterialDB.is_public == True)
|
|
|
|
# Filter by tags - vereinfachte Implementierung
|
|
results = []
|
|
for material in query.all():
|
|
if material.tags and any(tag in material.tags for tag in tags):
|
|
results.append(material)
|
|
return results[:50]
|
|
|
|
# ==================== UPDATE ====================
|
|
|
|
def update(self, material: PhaseMaterial) -> Optional[PhaseMaterialDB]:
|
|
"""Aktualisiert ein Material."""
|
|
db_material = self.get_by_id(material.material_id)
|
|
if not db_material:
|
|
return None
|
|
|
|
db_material.title = material.title
|
|
db_material.material_type = MaterialTypeEnum(material.material_type.value)
|
|
db_material.url = material.url
|
|
db_material.description = material.description
|
|
db_material.phase = material.phase
|
|
db_material.subject = material.subject
|
|
db_material.grade_level = material.grade_level
|
|
db_material.tags = material.tags
|
|
db_material.is_public = material.is_public
|
|
|
|
self.db.commit()
|
|
self.db.refresh(db_material)
|
|
return db_material
|
|
|
|
def increment_usage(self, material_id: str) -> Optional[PhaseMaterialDB]:
|
|
"""Erhoeht den Usage-Counter eines Materials."""
|
|
db_material = self.get_by_id(material_id)
|
|
if not db_material:
|
|
return None
|
|
|
|
db_material.usage_count += 1
|
|
self.db.commit()
|
|
self.db.refresh(db_material)
|
|
return db_material
|
|
|
|
def attach_to_session(
|
|
self,
|
|
material_id: str,
|
|
session_id: str
|
|
) -> Optional[PhaseMaterialDB]:
|
|
"""Verknuepft ein Material mit einer Session."""
|
|
db_material = self.get_by_id(material_id)
|
|
if not db_material:
|
|
return None
|
|
|
|
db_material.session_id = session_id
|
|
db_material.usage_count += 1
|
|
self.db.commit()
|
|
self.db.refresh(db_material)
|
|
return db_material
|
|
|
|
# ==================== DELETE ====================
|
|
|
|
def delete(self, material_id: str) -> bool:
|
|
"""Loescht ein Material."""
|
|
db_material = self.get_by_id(material_id)
|
|
if not db_material:
|
|
return False
|
|
|
|
self.db.delete(db_material)
|
|
self.db.commit()
|
|
return True
|
|
|
|
# ==================== CONVERSION ====================
|
|
|
|
def to_dataclass(self, db_material: PhaseMaterialDB) -> PhaseMaterial:
|
|
"""Konvertiert DB-Model zu Dataclass."""
|
|
return PhaseMaterial(
|
|
material_id=db_material.id,
|
|
teacher_id=db_material.teacher_id,
|
|
title=db_material.title,
|
|
material_type=MaterialType(db_material.material_type.value),
|
|
url=db_material.url,
|
|
description=db_material.description or "",
|
|
phase=db_material.phase,
|
|
subject=db_material.subject or "",
|
|
grade_level=db_material.grade_level or "",
|
|
tags=db_material.tags or [],
|
|
is_public=db_material.is_public,
|
|
usage_count=db_material.usage_count,
|
|
session_id=db_material.session_id,
|
|
created_at=db_material.created_at,
|
|
updated_at=db_material.updated_at,
|
|
)
|