""" Classroom API - Static Data, Suggestions & Sidebar Routes Federal states, school types, macro phases, event/routine types, suggestions, sidebar model, and school year path. """ from typing import Optional import logging from fastapi import APIRouter, HTTPException, Query, Depends from classroom_engine import FEDERAL_STATES, SCHOOL_TYPES from ..services.persistence import ( DB_ENABLED, SessionLocal, ) logger = logging.getLogger(__name__) router = APIRouter(tags=["Context"]) def get_db(): """Database session dependency.""" if DB_ENABLED and SessionLocal: db = SessionLocal() try: yield db finally: db.close() else: yield None # === Static Data Endpoints === @router.get("/v1/federal-states") async def get_federal_states(): """Gibt alle Bundeslaender zurueck.""" return { "federal_states": [{"id": k, "name": v} for k, v in FEDERAL_STATES.items()] } @router.get("/v1/school-types") async def get_school_types(): """Gibt alle Schularten zurueck.""" return { "school_types": [{"id": k, "name": v} for k, v in SCHOOL_TYPES.items()] } @router.get("/v1/macro-phases") async def get_macro_phases(): """Gibt alle Makro-Phasen mit Beschreibungen zurueck.""" phases = [ {"id": "onboarding", "label": "Einrichtung", "description": "Ersteinrichtung (Klassen, Stundenplan)", "order": 1}, {"id": "schuljahresstart", "label": "Schuljahresstart", "description": "Erste 2-3 Wochen des Schuljahres", "order": 2}, {"id": "unterrichtsaufbau", "label": "Unterrichtsaufbau", "description": "Routinen etablieren, erste Bewertungen", "order": 3}, {"id": "leistungsphase_1", "label": "Leistungsphase 1", "description": "Erste Klassenarbeiten und Klausuren", "order": 4}, {"id": "halbjahresabschluss", "label": "Halbjahresabschluss", "description": "Notenschluss, Zeugnisse, Konferenzen", "order": 5}, {"id": "leistungsphase_2", "label": "Leistungsphase 2", "description": "Zweites Halbjahr, Pruefungsvorbereitung", "order": 6}, {"id": "jahresabschluss", "label": "Jahresabschluss", "description": "Finale Noten, Versetzung, Schuljahresende", "order": 7}, ] return {"macro_phases": phases} @router.get("/v1/event-types") async def get_event_types(): """Gibt alle Event-Typen zurueck.""" types = [ {"id": "exam", "label": "Klassenarbeit/Klausur"}, {"id": "parent_evening", "label": "Elternabend"}, {"id": "trip", "label": "Klassenfahrt/Ausflug"}, {"id": "project", "label": "Projektwoche"}, {"id": "internship", "label": "Praktikum"}, {"id": "presentation", "label": "Referate/Praesentationen"}, {"id": "sports_day", "label": "Sporttag"}, {"id": "school_festival", "label": "Schulfest"}, {"id": "parent_consultation", "label": "Elternsprechtag"}, {"id": "grade_deadline", "label": "Notenschluss"}, {"id": "report_cards", "label": "Zeugnisausgabe"}, {"id": "holiday_start", "label": "Ferienbeginn"}, {"id": "holiday_end", "label": "Ferienende"}, {"id": "other", "label": "Sonstiges"}, ] return {"event_types": types} @router.get("/v1/routine-types") async def get_routine_types(): """Gibt alle Routine-Typen zurueck.""" types = [ {"id": "teacher_conference", "label": "Lehrerkonferenz"}, {"id": "subject_conference", "label": "Fachkonferenz"}, {"id": "office_hours", "label": "Sprechstunde"}, {"id": "team_meeting", "label": "Teamsitzung"}, {"id": "supervision", "label": "Pausenaufsicht"}, {"id": "correction_time", "label": "Korrekturzeit"}, {"id": "prep_time", "label": "Vorbereitungszeit"}, {"id": "other", "label": "Sonstiges"}, ] return {"routine_types": types} # === Suggestions & Sidebar === @router.get("/v1/suggestions") async def get_suggestions( teacher_id: str = Query(...), limit: int = Query(5, ge=1, le=20), db=Depends(get_db) ): """Generiert kontextbasierte Vorschlaege fuer einen Lehrer.""" if DB_ENABLED and db: try: from classroom_engine.suggestions import SuggestionGenerator generator = SuggestionGenerator(db) result = generator.generate(teacher_id, limit=limit) return result except Exception as e: logger.error(f"Failed to generate suggestions: {e}") raise HTTPException(status_code=500, detail=f"Fehler: {e}") return { "active_contexts": [], "suggestions": [], "signals_summary": { "macro_phase": "onboarding", "current_week": 1, "has_classes": False, "exams_soon": 0, "routines_today": 0, }, "total_suggestions": 0, } @router.get("/v1/sidebar") async def get_sidebar( teacher_id: str = Query(...), mode: str = Query("companion"), db=Depends(get_db) ): """Generiert das dynamische Sidebar-Model.""" if mode == "companion": now_relevant = [] if DB_ENABLED and db: try: from classroom_engine.suggestions import SuggestionGenerator generator = SuggestionGenerator(db) result = generator.generate(teacher_id, limit=5) now_relevant = [ { "id": s["id"], "label": s["title"], "state": "recommended" if s["priority"] > 70 else "default", "badge": s.get("badge"), "icon": s.get("icon", "lightbulb"), "action_url": s.get("action_url"), } for s in result.get("suggestions", []) ] except Exception as e: logger.warning(f"Failed to get suggestions for sidebar: {e}") return { "mode": "companion", "sections": [ {"id": "SEARCH", "type": "search_bar", "placeholder": "Suchen..."}, { "id": "NOW_RELEVANT", "type": "list", "title": "Jetzt relevant", "items": now_relevant if now_relevant else [ {"id": "no_suggestions", "label": "Keine Vorschlaege", "state": "default", "icon": "check_circle"} ], }, { "id": "ALL_MODULES", "type": "folder", "label": "Alle Module", "icon": "folder", "collapsed": True, "items": [ {"id": "lesson", "label": "Stundenmodus", "icon": "timer"}, {"id": "classes", "label": "Klassen", "icon": "groups"}, {"id": "exams", "label": "Klausuren", "icon": "quiz"}, {"id": "grades", "label": "Noten", "icon": "calculate"}, {"id": "calendar", "label": "Kalender", "icon": "calendar_month"}, {"id": "materials", "label": "Materialien", "icon": "folder_open"}, ], }, { "id": "QUICK_ACTIONS", "type": "actions", "title": "Kurzaktionen", "items": [ {"id": "scan", "label": "Scan hochladen", "icon": "upload_file"}, {"id": "note", "label": "Notiz erstellen", "icon": "note_add"}, ], }, ], } else: return { "mode": "classic", "sections": [ { "id": "NAVIGATION", "type": "tree", "items": [ {"id": "dashboard", "label": "Dashboard", "icon": "dashboard", "url": "/dashboard"}, {"id": "lesson", "label": "Stundenmodus", "icon": "timer", "url": "/lesson"}, {"id": "classes", "label": "Klassen", "icon": "groups", "url": "/classes"}, {"id": "exams", "label": "Klausuren", "icon": "quiz", "url": "/exams"}, {"id": "grades", "label": "Noten", "icon": "calculate", "url": "/grades"}, {"id": "calendar", "label": "Kalender", "icon": "calendar_month", "url": "/calendar"}, {"id": "materials", "label": "Materialien", "icon": "folder_open", "url": "/materials"}, {"id": "settings", "label": "Einstellungen", "icon": "settings", "url": "/settings"}, ], }, ], } @router.get("/v1/path") async def get_schoolyear_path(teacher_id: str = Query(...), db=Depends(get_db)): """Generiert den Schuljahres-Pfad mit Meilensteinen.""" current_phase = "onboarding" if DB_ENABLED and db: try: from classroom_engine.repository import TeacherContextRepository repo = TeacherContextRepository(db) context = repo.get_or_create(teacher_id) current_phase = context.macro_phase.value except Exception as e: logger.warning(f"Failed to get context for path: {e}") phase_order = [ "onboarding", "schuljahresstart", "unterrichtsaufbau", "leistungsphase_1", "halbjahresabschluss", "leistungsphase_2", "jahresabschluss", ] current_index = phase_order.index(current_phase) if current_phase in phase_order else 0 milestones = [ {"id": "MS_START", "label": "Start", "phase": "onboarding", "icon": "flag"}, {"id": "MS_SETUP", "label": "Einrichtung", "phase": "schuljahresstart", "icon": "tune"}, {"id": "MS_ROUTINE", "label": "Routinen", "phase": "unterrichtsaufbau", "icon": "repeat"}, {"id": "MS_EXAM_1", "label": "Klausuren", "phase": "leistungsphase_1", "icon": "quiz"}, {"id": "MS_HALFYEAR", "label": "Halbjahr", "phase": "halbjahresabschluss", "icon": "event"}, {"id": "MS_EXAM_2", "label": "Pruefungen", "phase": "leistungsphase_2", "icon": "school"}, {"id": "MS_END", "label": "Abschluss", "phase": "jahresabschluss", "icon": "celebration"}, ] for milestone in milestones: phase = milestone["phase"] phase_index = phase_order.index(phase) if phase in phase_order else 999 if phase_index < current_index: milestone["status"] = "done" elif phase_index == current_index: milestone["status"] = "current" else: milestone["status"] = "upcoming" current_milestone_id = next( (m["id"] for m in milestones if m["status"] == "current"), milestones[0]["id"] ) progress = int((current_index / (len(phase_order) - 1)) * 100) if len(phase_order) > 1 else 0 return { "milestones": milestones, "current_milestone_id": current_milestone_id, "progress_percent": progress, "current_phase": current_phase, }