backend-lehrer (10 files): - game/database.py (785 → 5), correction_api.py (683 → 4) - classroom_engine/antizipation.py (676 → 5) - llm_gateway schools/edu_search already done in prior batch klausur-service (12 files): - orientation_crop_api.py (694 → 5), pdf_export.py (677 → 4) - zeugnis_crawler.py (676 → 5), grid_editor_api.py (671 → 5) - eh_templates.py (658 → 5), mail/api.py (651 → 5) - qdrant_service.py (638 → 5), training_api.py (625 → 4) website (6 pages): - middleware (696 → 8), mail (733 → 6), consent (628 → 8) - compliance/risks (622 → 5), export (502 → 5), brandbook (629 → 7) studio-v2 (3 components): - B2BMigrationWizard (848 → 3), CleanupPanel (765 → 2) - dashboard-experimental (739 → 2) admin-lehrer (4 files): - uebersetzungen (769 → 4), manager (670 → 2) - ChunkBrowserQA (675 → 6), dsfa/page (674 → 5) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
135 lines
4.2 KiB
Python
135 lines
4.2 KiB
Python
"""
|
|
Correction API - Helper functions for grading, feedback, and OCR processing.
|
|
"""
|
|
|
|
import logging
|
|
from typing import List, Dict
|
|
|
|
from correction_models import AnswerEvaluation, CorrectionStatus, Correction
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
# FileProcessor requires OpenCV with libGL - make optional for CI
|
|
try:
|
|
from services.file_processor import FileProcessor, ProcessingResult
|
|
_ocr_available = True
|
|
except (ImportError, OSError):
|
|
FileProcessor = None # type: ignore
|
|
ProcessingResult = None # type: ignore
|
|
_ocr_available = False
|
|
|
|
# PDF service requires WeasyPrint with system libraries - make optional for CI
|
|
try:
|
|
from services.pdf_service import PDFService, CorrectionData, StudentInfo
|
|
_pdf_available = True
|
|
except (ImportError, OSError):
|
|
PDFService = None # type: ignore
|
|
CorrectionData = None # type: ignore
|
|
StudentInfo = None # type: ignore
|
|
_pdf_available = False
|
|
|
|
|
|
# ============================================================================
|
|
# In-Memory Storage (spaeter durch DB ersetzen)
|
|
# ============================================================================
|
|
|
|
corrections_store: Dict[str, Correction] = {}
|
|
|
|
|
|
# ============================================================================
|
|
# Helper Functions
|
|
# ============================================================================
|
|
|
|
def calculate_grade(percentage: float) -> str:
|
|
"""Berechnet Note aus Prozent (deutsches System)."""
|
|
if percentage >= 92:
|
|
return "1"
|
|
elif percentage >= 81:
|
|
return "2"
|
|
elif percentage >= 67:
|
|
return "3"
|
|
elif percentage >= 50:
|
|
return "4"
|
|
elif percentage >= 30:
|
|
return "5"
|
|
else:
|
|
return "6"
|
|
|
|
|
|
def generate_ai_feedback(
|
|
evaluations: List[AnswerEvaluation],
|
|
total_points: float,
|
|
max_points: float,
|
|
subject: str
|
|
) -> str:
|
|
"""Generiert KI-Feedback basierend auf Bewertung."""
|
|
# Ohne LLM: Einfaches Template-basiertes Feedback
|
|
percentage = (total_points / max_points * 100) if max_points > 0 else 0
|
|
correct_count = sum(1 for e in evaluations if e.is_correct)
|
|
total_count = len(evaluations)
|
|
|
|
if percentage >= 90:
|
|
intro = "Hervorragende Leistung!"
|
|
elif percentage >= 75:
|
|
intro = "Gute Arbeit!"
|
|
elif percentage >= 60:
|
|
intro = "Insgesamt eine solide Leistung."
|
|
elif percentage >= 50:
|
|
intro = "Die Arbeit zeigt Grundkenntnisse, aber es gibt Verbesserungsbedarf."
|
|
else:
|
|
intro = "Es sind deutliche Wissensluecken erkennbar."
|
|
|
|
# Finde Verbesserungsbereiche
|
|
weak_areas = [e for e in evaluations if not e.is_correct]
|
|
strengths = [e for e in evaluations if e.is_correct and e.confidence > 0.8]
|
|
|
|
feedback_parts = [intro]
|
|
|
|
if strengths:
|
|
feedback_parts.append(
|
|
f"Besonders gut geloest: Aufgabe(n) {', '.join(str(s.question_number) for s in strengths[:3])}."
|
|
)
|
|
|
|
if weak_areas:
|
|
feedback_parts.append(
|
|
f"Uebungsbedarf bei: Aufgabe(n) {', '.join(str(w.question_number) for w in weak_areas[:3])}."
|
|
)
|
|
|
|
feedback_parts.append(
|
|
f"Ergebnis: {correct_count} von {total_count} Aufgaben korrekt ({percentage:.1f}%)."
|
|
)
|
|
|
|
return " ".join(feedback_parts)
|
|
|
|
|
|
async def process_ocr(correction_id: str, file_path: str):
|
|
"""Background Task fuer OCR-Verarbeitung."""
|
|
from datetime import datetime
|
|
|
|
correction = corrections_store.get(correction_id)
|
|
if not correction:
|
|
return
|
|
|
|
try:
|
|
correction.status = CorrectionStatus.PROCESSING
|
|
corrections_store[correction_id] = correction
|
|
|
|
# OCR durchfuehren
|
|
processor = FileProcessor()
|
|
result = processor.process_file(file_path)
|
|
|
|
if result.success and result.text:
|
|
correction.extracted_text = result.text
|
|
correction.status = CorrectionStatus.OCR_COMPLETE
|
|
else:
|
|
correction.status = CorrectionStatus.ERROR
|
|
|
|
correction.updated_at = datetime.utcnow()
|
|
corrections_store[correction_id] = correction
|
|
|
|
except Exception as e:
|
|
logger.error(f"OCR error for {correction_id}: {e}")
|
|
correction.status = CorrectionStatus.ERROR
|
|
correction.updated_at = datetime.utcnow()
|
|
corrections_store[correction_id] = correction
|