[split-required] Split final 43 files (500-668 LOC) to complete refactoring

klausur-service (11 files):
- cv_gutter_repair, ocr_pipeline_regression, upload_api
- ocr_pipeline_sessions, smart_spell, nru_worksheet_generator
- ocr_pipeline_overlays, mail/aggregator, zeugnis_api
- cv_syllable_detect, self_rag

backend-lehrer (17 files):
- classroom_engine/suggestions, generators/quiz_generator
- worksheets_api, llm_gateway/comparison, state_engine_api
- classroom/models (→ 4 submodules), services/file_processor
- alerts_agent/api/wizard+digests+routes, content_generators/pdf
- classroom/routes/sessions, llm_gateway/inference
- classroom_engine/analytics, auth/keycloak_auth
- alerts_agent/processing/rule_engine, ai_processor/print_versions

agent-core (5 files):
- brain/memory_store, brain/knowledge_graph, brain/context_manager
- orchestrator/supervisor, sessions/session_manager

admin-lehrer (5 components):
- GridOverlay, StepGridReview, DevOpsPipelineSidebar
- DataFlowDiagram, sbom/wizard/page

website (2 files):
- DependencyMap, lehrer/abitur-archiv

Other: nibis_ingestion, grid_detection_service, export-doclayout-onnx

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-04-25 09:41:42 +02:00
parent 451365a312
commit bd4b956e3c
113 changed files with 13790 additions and 14148 deletions

View File

@@ -16,11 +16,9 @@ Unterstützt:
import logging
import uuid
from datetime import datetime
from typing import List, Dict, Any, Optional
from enum import Enum
from typing import Dict
from fastapi import APIRouter, HTTPException, UploadFile, File, Form
from pydantic import BaseModel, Field
from fastapi import APIRouter, HTTPException
from generators import (
MultipleChoiceGenerator,
@@ -28,9 +26,22 @@ from generators import (
MindmapGenerator,
QuizGenerator
)
from generators.mc_generator import Difficulty
from generators.cloze_generator import ClozeType
from generators.quiz_generator import QuizType
from worksheets_models import (
ContentType,
GenerateRequest,
MCGenerateRequest,
ClozeGenerateRequest,
MindmapGenerateRequest,
QuizGenerateRequest,
BatchGenerateRequest,
WorksheetContent,
GenerateResponse,
BatchGenerateResponse,
parse_difficulty,
parse_cloze_type,
parse_quiz_types,
)
logger = logging.getLogger(__name__)
@@ -40,89 +51,6 @@ router = APIRouter(
)
# ============================================================================
# Pydantic Models
# ============================================================================
class ContentType(str, Enum):
"""Verfügbare Content-Typen."""
MULTIPLE_CHOICE = "multiple_choice"
CLOZE = "cloze"
MINDMAP = "mindmap"
QUIZ = "quiz"
class GenerateRequest(BaseModel):
"""Basis-Request für Generierung."""
source_text: str = Field(..., min_length=50, description="Quelltext für Generierung")
topic: Optional[str] = Field(None, description="Thema/Titel")
subject: Optional[str] = Field(None, description="Fach")
grade_level: Optional[str] = Field(None, description="Klassenstufe")
class MCGenerateRequest(GenerateRequest):
"""Request für Multiple-Choice-Generierung."""
num_questions: int = Field(5, ge=1, le=20, description="Anzahl Fragen")
difficulty: str = Field("medium", description="easy, medium, hard")
class ClozeGenerateRequest(GenerateRequest):
"""Request für Lückentext-Generierung."""
num_gaps: int = Field(5, ge=1, le=15, description="Anzahl Lücken")
difficulty: str = Field("medium", description="easy, medium, hard")
cloze_type: str = Field("fill_in", description="fill_in, drag_drop, dropdown")
class MindmapGenerateRequest(GenerateRequest):
"""Request für Mindmap-Generierung."""
max_depth: int = Field(3, ge=2, le=5, description="Maximale Tiefe")
class QuizGenerateRequest(GenerateRequest):
"""Request für Quiz-Generierung."""
quiz_types: List[str] = Field(
["true_false", "matching"],
description="Typen: true_false, matching, sorting, open_ended"
)
num_items: int = Field(5, ge=1, le=10, description="Items pro Typ")
difficulty: str = Field("medium", description="easy, medium, hard")
class BatchGenerateRequest(BaseModel):
"""Request für Batch-Generierung mehrerer Content-Typen."""
source_text: str = Field(..., min_length=50)
content_types: List[str] = Field(..., description="Liste von Content-Typen")
topic: Optional[str] = None
subject: Optional[str] = None
grade_level: Optional[str] = None
difficulty: str = "medium"
class WorksheetContent(BaseModel):
"""Generierter Content."""
id: str
content_type: str
data: Dict[str, Any]
h5p_format: Optional[Dict[str, Any]] = None
created_at: datetime
topic: Optional[str] = None
difficulty: Optional[str] = None
class GenerateResponse(BaseModel):
"""Response mit generiertem Content."""
success: bool
content: Optional[WorksheetContent] = None
error: Optional[str] = None
class BatchGenerateResponse(BaseModel):
"""Response für Batch-Generierung."""
success: bool
contents: List[WorksheetContent] = []
errors: List[str] = []
# ============================================================================
# In-Memory Storage (später durch DB ersetzen)
# ============================================================================
@@ -134,49 +62,12 @@ _generated_content: Dict[str, WorksheetContent] = {}
# Generator Instances
# ============================================================================
# Generatoren ohne LLM-Client (automatische Generierung)
# In Produktion würde hier der LLM-Client injiziert
mc_generator = MultipleChoiceGenerator()
cloze_generator = ClozeGenerator()
mindmap_generator = MindmapGenerator()
quiz_generator = QuizGenerator()
# ============================================================================
# Helper Functions
# ============================================================================
def _parse_difficulty(difficulty_str: str) -> Difficulty:
"""Konvertiert String zu Difficulty Enum."""
mapping = {
"easy": Difficulty.EASY,
"medium": Difficulty.MEDIUM,
"hard": Difficulty.HARD
}
return mapping.get(difficulty_str.lower(), Difficulty.MEDIUM)
def _parse_cloze_type(type_str: str) -> ClozeType:
"""Konvertiert String zu ClozeType Enum."""
mapping = {
"fill_in": ClozeType.FILL_IN,
"drag_drop": ClozeType.DRAG_DROP,
"dropdown": ClozeType.DROPDOWN
}
return mapping.get(type_str.lower(), ClozeType.FILL_IN)
def _parse_quiz_types(type_strs: List[str]) -> List[QuizType]:
"""Konvertiert String-Liste zu QuizType Enums."""
mapping = {
"true_false": QuizType.TRUE_FALSE,
"matching": QuizType.MATCHING,
"sorting": QuizType.SORTING,
"open_ended": QuizType.OPEN_ENDED
}
return [mapping.get(t.lower(), QuizType.TRUE_FALSE) for t in type_strs]
def _store_content(content: WorksheetContent) -> None:
"""Speichert generierten Content."""
_generated_content[content.id] = content
@@ -188,15 +79,9 @@ def _store_content(content: WorksheetContent) -> None:
@router.post("/generate/multiple-choice", response_model=GenerateResponse)
async def generate_multiple_choice(request: MCGenerateRequest):
"""
Generiert Multiple-Choice-Fragen aus Quelltext.
- **source_text**: Text mit mind. 50 Zeichen
- **num_questions**: Anzahl Fragen (1-20)
- **difficulty**: easy, medium, hard
"""
"""Generiert Multiple-Choice-Fragen aus Quelltext."""
try:
difficulty = _parse_difficulty(request.difficulty)
difficulty = parse_difficulty(request.difficulty)
questions = mc_generator.generate(
source_text=request.source_text,
@@ -212,7 +97,6 @@ async def generate_multiple_choice(request: MCGenerateRequest):
error="Keine Fragen generiert. Text möglicherweise zu kurz."
)
# Konvertiere zu Dict
questions_dict = mc_generator.to_dict(questions)
h5p_format = mc_generator.to_h5p_format(questions)
@@ -227,7 +111,6 @@ async def generate_multiple_choice(request: MCGenerateRequest):
)
_store_content(content)
return GenerateResponse(success=True, content=content)
except Exception as e:
@@ -237,15 +120,9 @@ async def generate_multiple_choice(request: MCGenerateRequest):
@router.post("/generate/cloze", response_model=GenerateResponse)
async def generate_cloze(request: ClozeGenerateRequest):
"""
Generiert Lückentext aus Quelltext.
- **source_text**: Text mit mind. 50 Zeichen
- **num_gaps**: Anzahl Lücken (1-15)
- **cloze_type**: fill_in, drag_drop, dropdown
"""
"""Generiert Lückentext aus Quelltext."""
try:
cloze_type = _parse_cloze_type(request.cloze_type)
cloze_type = parse_cloze_type(request.cloze_type)
cloze = cloze_generator.generate(
source_text=request.source_text,
@@ -275,7 +152,6 @@ async def generate_cloze(request: ClozeGenerateRequest):
)
_store_content(content)
return GenerateResponse(success=True, content=content)
except Exception as e:
@@ -285,12 +161,7 @@ async def generate_cloze(request: ClozeGenerateRequest):
@router.post("/generate/mindmap", response_model=GenerateResponse)
async def generate_mindmap(request: MindmapGenerateRequest):
"""
Generiert Mindmap aus Quelltext.
- **source_text**: Text mit mind. 50 Zeichen
- **max_depth**: Maximale Tiefe (2-5)
"""
"""Generiert Mindmap aus Quelltext."""
try:
mindmap = mindmap_generator.generate(
source_text=request.source_text,
@@ -317,14 +188,13 @@ async def generate_mindmap(request: MindmapGenerateRequest):
"mermaid": mermaid,
"json_tree": json_tree
},
h5p_format=None, # Mindmaps haben kein H5P-Format
h5p_format=None,
created_at=datetime.utcnow(),
topic=request.topic,
difficulty=None
)
_store_content(content)
return GenerateResponse(success=True, content=content)
except Exception as e:
@@ -334,17 +204,10 @@ async def generate_mindmap(request: MindmapGenerateRequest):
@router.post("/generate/quiz", response_model=GenerateResponse)
async def generate_quiz(request: QuizGenerateRequest):
"""
Generiert Quiz mit verschiedenen Fragetypen.
- **source_text**: Text mit mind. 50 Zeichen
- **quiz_types**: Liste von true_false, matching, sorting, open_ended
- **num_items**: Items pro Typ (1-10)
"""
"""Generiert Quiz mit verschiedenen Fragetypen."""
try:
quiz_types = _parse_quiz_types(request.quiz_types)
quiz_types = parse_quiz_types(request.quiz_types)
# Generate quiz for each type and combine results
all_questions = []
quizzes = []
@@ -365,7 +228,6 @@ async def generate_quiz(request: QuizGenerateRequest):
error="Quiz konnte nicht generiert werden. Text möglicherweise zu kurz."
)
# Combine all quizzes into a single dict
combined_quiz_dict = {
"quiz_types": [qt.value for qt in quiz_types],
"title": f"Combined Quiz - {request.topic or 'Various Topics'}",
@@ -374,12 +236,10 @@ async def generate_quiz(request: QuizGenerateRequest):
"questions": []
}
# Add questions from each quiz
for quiz in quizzes:
quiz_dict = quiz_generator.to_dict(quiz)
combined_quiz_dict["questions"].extend(quiz_dict.get("questions", []))
# Use first quiz's H5P format as base (or empty if none)
h5p_format = quiz_generator.to_h5p_format(quizzes[0]) if quizzes else {}
content = WorksheetContent(
@@ -393,7 +253,6 @@ async def generate_quiz(request: QuizGenerateRequest):
)
_store_content(content)
return GenerateResponse(success=True, content=content)
except Exception as e:
@@ -403,22 +262,10 @@ async def generate_quiz(request: QuizGenerateRequest):
@router.post("/generate/batch", response_model=BatchGenerateResponse)
async def generate_batch(request: BatchGenerateRequest):
"""
Generiert mehrere Content-Typen aus einem Quelltext.
Ideal für die Erstellung kompletter Arbeitsblätter mit
verschiedenen Übungstypen.
"""
"""Generiert mehrere Content-Typen aus einem Quelltext."""
contents = []
errors = []
type_mapping = {
"multiple_choice": MCGenerateRequest,
"cloze": ClozeGenerateRequest,
"mindmap": MindmapGenerateRequest,
"quiz": QuizGenerateRequest
}
for content_type in request.content_types:
try:
if content_type == "multiple_choice":