""" Classroom API - Analytics Routes Analytics and reflection endpoints (Phase 5). """ import uuid from typing import Dict, Any from datetime import datetime, timedelta import logging from fastapi import APIRouter, HTTPException, Query from classroom_engine import LessonReflection from ..models import ( SessionSummaryResponse, TeacherAnalyticsResponse, ReflectionCreate, ReflectionUpdate, ReflectionResponse, ) from ..services.persistence import ( init_db_if_needed, DB_ENABLED, SessionLocal, ) logger = logging.getLogger(__name__) router = APIRouter(tags=["Analytics"]) # === Analytics Endpoints === @router.get("/analytics/session/{session_id}") async def get_session_summary(session_id: str) -> SessionSummaryResponse: """ Gibt die Analytics-Zusammenfassung einer Session zurueck. Berechnet Phasen-Dauer Statistiken, Overtime und Pausen-Analyse. """ if not DB_ENABLED: raise HTTPException( status_code=503, detail="Database not available" ) init_db_if_needed() with SessionLocal() as db: from classroom_engine.repository import AnalyticsRepository repo = AnalyticsRepository(db) summary = repo.get_session_summary(session_id) if not summary: raise HTTPException( status_code=404, detail=f"Session {session_id} not found" ) return SessionSummaryResponse(**summary.to_dict()) @router.get("/analytics/teacher/{teacher_id}") async def get_teacher_analytics( teacher_id: str, days: int = Query(30, ge=1, le=365) ) -> TeacherAnalyticsResponse: """ Gibt aggregierte Analytics fuer einen Lehrer zurueck. Berechnet Trends ueber den angegebenen Zeitraum. """ if not DB_ENABLED: raise HTTPException( status_code=503, detail="Database not available" ) init_db_if_needed() period_end = datetime.utcnow() period_start = period_end - timedelta(days=days) with SessionLocal() as db: from classroom_engine.repository import AnalyticsRepository repo = AnalyticsRepository(db) analytics = repo.get_teacher_analytics( teacher_id, period_start, period_end ) return TeacherAnalyticsResponse(**analytics.to_dict()) @router.get("/analytics/phase-trends/{teacher_id}/{phase}") async def get_phase_trends( teacher_id: str, phase: str, limit: int = Query(20, ge=1, le=100) ) -> Dict[str, Any]: """ Gibt die Dauer-Trends fuer eine Phase zurueck. Nuetzlich fuer Charts und Visualisierungen. """ if phase not in ["einstieg", "erarbeitung", "sicherung", "transfer", "reflexion"]: raise HTTPException( status_code=400, detail=f"Invalid phase: {phase}" ) if not DB_ENABLED: raise HTTPException( status_code=503, detail="Database not available" ) init_db_if_needed() with SessionLocal() as db: from classroom_engine.repository import AnalyticsRepository repo = AnalyticsRepository(db) trends = repo.get_phase_duration_trends(teacher_id, phase, limit) return { "teacher_id": teacher_id, "phase": phase, "data_points": trends, "count": len(trends) } @router.get("/analytics/overtime/{teacher_id}") async def get_overtime_analysis( teacher_id: str, limit: int = Query(30, ge=1, le=100) ) -> Dict[str, Any]: """ Analysiert Overtime-Muster nach Phase. Zeigt welche Phasen am haeufigsten ueberzogen werden. """ if not DB_ENABLED: raise HTTPException( status_code=503, detail="Database not available" ) init_db_if_needed() with SessionLocal() as db: from classroom_engine.repository import AnalyticsRepository repo = AnalyticsRepository(db) analysis = repo.get_overtime_analysis(teacher_id, limit) return { "teacher_id": teacher_id, "sessions_analyzed": limit, "phases": analysis } # === Reflection Endpoints === @router.post("/reflections", status_code=201) async def create_reflection(data: ReflectionCreate) -> ReflectionResponse: """ Erstellt eine Post-Lesson Reflection. Erlaubt Lehrern, nach der Stunde Notizen zu speichern. """ if not DB_ENABLED: raise HTTPException( status_code=503, detail="Database not available" ) init_db_if_needed() with SessionLocal() as db: from classroom_engine.repository import ReflectionRepository repo = ReflectionRepository(db) # Pruefen ob schon eine Reflection existiert existing = repo.get_by_session(data.session_id) if existing: raise HTTPException( status_code=409, detail=f"Reflection for session {data.session_id} already exists" ) reflection = LessonReflection( reflection_id=str(uuid.uuid4()), session_id=data.session_id, teacher_id=data.teacher_id, notes=data.notes, overall_rating=data.overall_rating, what_worked=data.what_worked, improvements=data.improvements, notes_for_next_lesson=data.notes_for_next_lesson, created_at=datetime.utcnow(), ) db_reflection = repo.create(reflection) result = repo.to_dataclass(db_reflection) return ReflectionResponse(**result.to_dict()) @router.get("/reflections/session/{session_id}") async def get_reflection_by_session(session_id: str) -> ReflectionResponse: """ Holt die Reflection einer Session. """ if not DB_ENABLED: raise HTTPException( status_code=503, detail="Database not available" ) init_db_if_needed() with SessionLocal() as db: from classroom_engine.repository import ReflectionRepository repo = ReflectionRepository(db) db_reflection = repo.get_by_session(session_id) if not db_reflection: raise HTTPException( status_code=404, detail=f"No reflection for session {session_id}" ) result = repo.to_dataclass(db_reflection) return ReflectionResponse(**result.to_dict()) @router.get("/reflections/teacher/{teacher_id}") async def get_reflections_by_teacher( teacher_id: str, limit: int = Query(20, ge=1, le=100), offset: int = Query(0, ge=0) ) -> Dict[str, Any]: """ Holt alle Reflections eines Lehrers. """ if not DB_ENABLED: raise HTTPException( status_code=503, detail="Database not available" ) init_db_if_needed() with SessionLocal() as db: from classroom_engine.repository import ReflectionRepository repo = ReflectionRepository(db) db_reflections = repo.get_by_teacher(teacher_id, limit, offset) reflections = [ repo.to_dataclass(r).to_dict() for r in db_reflections ] return { "teacher_id": teacher_id, "reflections": reflections, "count": len(reflections), "offset": offset, "limit": limit } @router.put("/reflections/{reflection_id}") async def update_reflection( reflection_id: str, data: ReflectionUpdate, teacher_id: str = Query(...) ) -> ReflectionResponse: """ Aktualisiert eine Reflection. """ if not DB_ENABLED: raise HTTPException( status_code=503, detail="Database not available" ) init_db_if_needed() with SessionLocal() as db: from classroom_engine.repository import ReflectionRepository repo = ReflectionRepository(db) db_reflection = repo.get_by_id(reflection_id) if not db_reflection: raise HTTPException( status_code=404, detail=f"Reflection {reflection_id} not found" ) if db_reflection.teacher_id != teacher_id: raise HTTPException( status_code=403, detail="Not authorized to update this reflection" ) # Vorhandene Werte beibehalten wenn nicht im Update reflection = repo.to_dataclass(db_reflection) if data.notes is not None: reflection.notes = data.notes if data.overall_rating is not None: reflection.overall_rating = data.overall_rating if data.what_worked is not None: reflection.what_worked = data.what_worked if data.improvements is not None: reflection.improvements = data.improvements if data.notes_for_next_lesson is not None: reflection.notes_for_next_lesson = data.notes_for_next_lesson reflection.updated_at = datetime.utcnow() db_updated = repo.update(reflection) result = repo.to_dataclass(db_updated) return ReflectionResponse(**result.to_dict()) @router.delete("/reflections/{reflection_id}") async def delete_reflection( reflection_id: str, teacher_id: str = Query(...) ) -> Dict[str, Any]: """ Loescht eine Reflection. """ if not DB_ENABLED: raise HTTPException( status_code=503, detail="Database not available" ) init_db_if_needed() with SessionLocal() as db: from classroom_engine.repository import ReflectionRepository repo = ReflectionRepository(db) db_reflection = repo.get_by_id(reflection_id) if not db_reflection: raise HTTPException( status_code=404, detail=f"Reflection {reflection_id} not found" ) if db_reflection.teacher_id != teacher_id: raise HTTPException( status_code=403, detail="Not authorized to delete this reflection" ) success = repo.delete(reflection_id) return { "success": success, "deleted_id": reflection_id }