backend-lehrer (11 files): - llm_gateway/routes/schools.py (867 → 5), recording_api.py (848 → 6) - messenger_api.py (840 → 5), print_generator.py (824 → 5) - unit_analytics_api.py (751 → 5), classroom/routes/context.py (726 → 4) - llm_gateway/routes/edu_search_seeds.py (710 → 4) klausur-service (12 files): - ocr_labeling_api.py (845 → 4), metrics_db.py (833 → 4) - legal_corpus_api.py (790 → 4), page_crop.py (758 → 3) - mail/ai_service.py (747 → 4), github_crawler.py (767 → 3) - trocr_service.py (730 → 4), full_compliance_pipeline.py (723 → 4) - dsfa_rag_api.py (715 → 4), ocr_pipeline_auto.py (705 → 4) website (6 pages): - audit-checklist (867 → 8), content (806 → 6) - screen-flow (790 → 4), scraper (789 → 5) - zeugnisse (776 → 5), modules (745 → 4) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
248 lines
8.2 KiB
Python
248 lines
8.2 KiB
Python
"""
|
|
Classroom API - Context Core Routes
|
|
|
|
Teacher context, onboarding endpoints (Phase 8).
|
|
"""
|
|
|
|
from typing import Dict, Any
|
|
from datetime import datetime
|
|
import logging
|
|
|
|
from fastapi import APIRouter, HTTPException, Query, Depends
|
|
|
|
from classroom_engine import (
|
|
FEDERAL_STATES,
|
|
SCHOOL_TYPES,
|
|
MacroPhaseEnum,
|
|
)
|
|
|
|
from ..models import (
|
|
TeacherContextResponse,
|
|
SchoolInfo,
|
|
SchoolYearInfo,
|
|
MacroPhaseInfo,
|
|
CoreCounts,
|
|
ContextFlags,
|
|
UpdateContextRequest,
|
|
)
|
|
from ..services.persistence import (
|
|
init_db_if_needed,
|
|
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
|
|
|
|
|
|
def _get_macro_phase_label(phase) -> str:
|
|
"""Gibt den Anzeigenamen einer Makro-Phase zurueck."""
|
|
labels = {
|
|
"onboarding": "Einrichtung",
|
|
"schuljahresstart": "Schuljahresstart",
|
|
"unterrichtsaufbau": "Unterrichtsaufbau",
|
|
"leistungsphase_1": "Leistungsphase 1",
|
|
"halbjahresabschluss": "Halbjahresabschluss",
|
|
"leistungsphase_2": "Leistungsphase 2",
|
|
"jahresabschluss": "Jahresabschluss",
|
|
}
|
|
phase_value = phase.value if hasattr(phase, 'value') else str(phase)
|
|
return labels.get(phase_value, phase_value)
|
|
|
|
|
|
# === Context Endpoints ===
|
|
|
|
@router.get("/v1/context", response_model=TeacherContextResponse)
|
|
async def get_teacher_context(
|
|
teacher_id: str = Query(..., description="Teacher ID"),
|
|
db=Depends(get_db)
|
|
):
|
|
"""
|
|
Liefert den aktuellen Makro-Kontext eines Lehrers.
|
|
|
|
Der Kontext beinhaltet:
|
|
- Schul-Informationen (Bundesland, Schulart)
|
|
- Schuljahr-Daten (aktuelles Jahr, Woche)
|
|
- Makro-Phase (ONBOARDING bis JAHRESABSCHLUSS)
|
|
- Zaehler (Klassen, geplante Klausuren, etc.)
|
|
- Status-Flags (Onboarding abgeschlossen, etc.)
|
|
"""
|
|
if DB_ENABLED and db:
|
|
try:
|
|
from classroom_engine.repository import TeacherContextRepository, SchoolyearEventRepository
|
|
repo = TeacherContextRepository(db)
|
|
context = repo.get_or_create(teacher_id)
|
|
|
|
# Zaehler berechnen
|
|
event_repo = SchoolyearEventRepository(db)
|
|
upcoming_exams = event_repo.get_upcoming(teacher_id, days=30)
|
|
exams_count = len([e for e in upcoming_exams if e.event_type.value == "exam"])
|
|
|
|
return TeacherContextResponse(
|
|
schema_version="1.0",
|
|
teacher_id=teacher_id,
|
|
school=SchoolInfo(
|
|
federal_state=context.federal_state or "BY",
|
|
federal_state_name=FEDERAL_STATES.get(context.federal_state, ""),
|
|
school_type=context.school_type or "gymnasium",
|
|
school_type_name=SCHOOL_TYPES.get(context.school_type, ""),
|
|
),
|
|
school_year=SchoolYearInfo(
|
|
id=context.schoolyear or "2024-2025",
|
|
start=context.schoolyear_start.isoformat() if context.schoolyear_start else None,
|
|
current_week=context.current_week or 1,
|
|
),
|
|
macro_phase=MacroPhaseInfo(
|
|
id=context.macro_phase.value,
|
|
label=_get_macro_phase_label(context.macro_phase),
|
|
confidence=1.0,
|
|
),
|
|
core_counts=CoreCounts(
|
|
classes=1 if context.has_classes else 0,
|
|
exams_scheduled=exams_count,
|
|
corrections_pending=0,
|
|
),
|
|
flags=ContextFlags(
|
|
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,
|
|
),
|
|
)
|
|
except Exception as e:
|
|
logger.error(f"Failed to get teacher context: {e}")
|
|
raise HTTPException(status_code=500, detail=f"Fehler beim Laden des Kontexts: {e}")
|
|
|
|
# Fallback ohne DB
|
|
return TeacherContextResponse(
|
|
schema_version="1.0",
|
|
teacher_id=teacher_id,
|
|
school=SchoolInfo(
|
|
federal_state="BY",
|
|
federal_state_name="Bayern",
|
|
school_type="gymnasium",
|
|
school_type_name="Gymnasium",
|
|
),
|
|
school_year=SchoolYearInfo(
|
|
id="2024-2025",
|
|
start=None,
|
|
current_week=1,
|
|
),
|
|
macro_phase=MacroPhaseInfo(
|
|
id="onboarding",
|
|
label="Einrichtung",
|
|
confidence=1.0,
|
|
),
|
|
core_counts=CoreCounts(),
|
|
flags=ContextFlags(),
|
|
)
|
|
|
|
|
|
@router.put("/v1/context", response_model=TeacherContextResponse)
|
|
async def update_teacher_context(
|
|
teacher_id: str,
|
|
request: UpdateContextRequest,
|
|
db=Depends(get_db)
|
|
):
|
|
"""
|
|
Aktualisiert den Kontext eines Lehrers.
|
|
"""
|
|
if not DB_ENABLED or not db:
|
|
raise HTTPException(status_code=503, detail="Datenbank nicht verfuegbar")
|
|
|
|
try:
|
|
from classroom_engine.repository import TeacherContextRepository
|
|
repo = TeacherContextRepository(db)
|
|
|
|
# Validierung
|
|
if request.federal_state and request.federal_state not in FEDERAL_STATES:
|
|
raise HTTPException(status_code=400, detail=f"Ungueltiges Bundesland: {request.federal_state}")
|
|
if request.school_type and request.school_type not in SCHOOL_TYPES:
|
|
raise HTTPException(status_code=400, detail=f"Ungueltige Schulart: {request.school_type}")
|
|
|
|
# Parse datetime if provided
|
|
schoolyear_start = None
|
|
if request.schoolyear_start:
|
|
schoolyear_start = datetime.fromisoformat(request.schoolyear_start.replace('Z', '+00:00'))
|
|
|
|
repo.update_context(
|
|
teacher_id=teacher_id,
|
|
federal_state=request.federal_state,
|
|
school_type=request.school_type,
|
|
schoolyear=request.schoolyear,
|
|
schoolyear_start=schoolyear_start,
|
|
macro_phase=request.macro_phase,
|
|
current_week=request.current_week,
|
|
)
|
|
|
|
return await get_teacher_context(teacher_id, db)
|
|
except HTTPException:
|
|
raise
|
|
except Exception as e:
|
|
logger.error(f"Failed to update teacher context: {e}")
|
|
raise HTTPException(status_code=500, detail=f"Fehler beim Aktualisieren: {e}")
|
|
|
|
|
|
@router.post("/v1/context/complete-onboarding")
|
|
async def complete_onboarding(
|
|
teacher_id: str = Query(...),
|
|
db=Depends(get_db)
|
|
):
|
|
"""Markiert das Onboarding als abgeschlossen."""
|
|
if not DB_ENABLED or not db:
|
|
return {"success": True, "macro_phase": "schuljahresstart", "note": "DB not available"}
|
|
|
|
try:
|
|
from classroom_engine.repository import TeacherContextRepository
|
|
repo = TeacherContextRepository(db)
|
|
context = repo.complete_onboarding(teacher_id)
|
|
return {
|
|
"success": True,
|
|
"macro_phase": context.macro_phase.value,
|
|
"teacher_id": teacher_id,
|
|
}
|
|
except Exception as e:
|
|
logger.error(f"Failed to complete onboarding: {e}")
|
|
raise HTTPException(status_code=500, detail=f"Fehler: {e}")
|
|
|
|
|
|
@router.post("/v1/context/reset-onboarding")
|
|
async def reset_onboarding(
|
|
teacher_id: str = Query(...),
|
|
db=Depends(get_db)
|
|
):
|
|
"""Setzt das Onboarding zurueck (fuer Tests)."""
|
|
if not DB_ENABLED or not db:
|
|
return {"success": True, "macro_phase": "onboarding", "note": "DB not available"}
|
|
|
|
try:
|
|
from classroom_engine.repository import TeacherContextRepository
|
|
repo = TeacherContextRepository(db)
|
|
context = repo.get_or_create(teacher_id)
|
|
context.onboarding_completed = False
|
|
context.macro_phase = MacroPhaseEnum.ONBOARDING
|
|
db.commit()
|
|
db.refresh(context)
|
|
return {
|
|
"success": True,
|
|
"macro_phase": "onboarding",
|
|
"teacher_id": teacher_id,
|
|
}
|
|
except Exception as e:
|
|
logger.error(f"Failed to reset onboarding: {e}")
|
|
raise HTTPException(status_code=500, detail=f"Fehler: {e}")
|