[split-required] Split 500-850 LOC files (batch 2)
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>
This commit is contained in:
134
backend-lehrer/correction_helpers.py
Normal file
134
backend-lehrer/correction_helpers.py
Normal file
@@ -0,0 +1,134 @@
|
||||
"""
|
||||
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
|
||||
Reference in New Issue
Block a user