[split-required] Split 700-870 LOC files across all services
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>
This commit is contained in:
281
backend-lehrer/classroom/routes/context_static.py
Normal file
281
backend-lehrer/classroom/routes/context_static.py
Normal file
@@ -0,0 +1,281 @@
|
||||
"""
|
||||
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,
|
||||
}
|
||||
Reference in New Issue
Block a user