Files
breakpilot-lehrer/backend-lehrer/classroom_engine/suggestions.py
Benjamin Admin bd4b956e3c [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>
2026-04-25 09:41:42 +02:00

190 lines
5.7 KiB
Python

"""
Phasenspezifische Content-Vorschlaege (Feature f18 erweitert).
Generiert Aktivitaets-Vorschlaege basierend auf der aktuellen Unterrichtsphase
und optional dem Fach.
"""
from typing import List, Dict, Any, Optional
from .models import LessonPhase, LessonSession, PhaseSuggestion
from .suggestion_data import (
SUPPORTED_SUBJECTS,
SUBJECT_SUGGESTIONS,
PHASE_SUGGESTIONS,
)
class SuggestionEngine:
"""
Engine zur Generierung von phasenspezifischen Vorschlaegen (Feature f18 erweitert).
Liefert Aktivitaets-Vorschlaege basierend auf:
- Aktueller Phase
- Fach (priorisiert fachspezifische Vorschlaege)
- Optional: Klassenstufe, bisherige Nutzung
"""
def _normalize_subject(self, subject: str) -> Optional[str]:
"""Normalisiert Fachnamen fuer die Suche."""
if not subject:
return None
normalized = subject.lower().strip()
# Mapping zu Hauptkategorien
mappings = {
"mathe": "mathematik",
"math": "mathematik",
"bio": "biologie",
"english": "englisch",
"erdkunde": "geografie",
}
return mappings.get(normalized, normalized)
def _get_subject_suggestions(
self,
subject: str,
phase: LessonPhase
) -> List[Dict[str, Any]]:
"""Holt fachspezifische Vorschlaege (Feature f18)."""
normalized = self._normalize_subject(subject)
if not normalized:
return []
subject_data = SUBJECT_SUGGESTIONS.get(normalized, {})
return subject_data.get(phase, [])
def get_suggestions(
self,
session: LessonSession,
limit: int = 3
) -> List[PhaseSuggestion]:
"""
Gibt Vorschlaege fuer die aktuelle Phase zurueck.
Priorisiert fachspezifische Vorschlaege (Feature f18),
ergaenzt mit allgemeinen Vorschlaegen.
Args:
session: Die aktuelle Session
limit: Maximale Anzahl Vorschlaege
Returns:
Liste von PhaseSuggestion Objekten
"""
# Keine Vorschlaege fuer inaktive Phasen
if session.current_phase in [LessonPhase.NOT_STARTED, LessonPhase.ENDED]:
return []
suggestions = []
seen_ids = set()
# 1. Fachspezifische Vorschlaege zuerst (Feature f18)
if session.subject:
subject_suggestions = self._get_subject_suggestions(
session.subject,
session.current_phase
)
for s in subject_suggestions:
if s["id"] not in seen_ids:
suggestions.append(PhaseSuggestion(**s))
seen_ids.add(s["id"])
if len(suggestions) >= limit:
return suggestions
# 2. Allgemeine Vorschlaege ergaenzen
phase_suggestions = PHASE_SUGGESTIONS.get(session.current_phase, [])
for s in phase_suggestions:
if s["id"] not in seen_ids:
suggestions.append(PhaseSuggestion(**s))
seen_ids.add(s["id"])
if len(suggestions) >= limit:
break
return suggestions
def get_all_suggestions(self, session: LessonSession) -> List[PhaseSuggestion]:
"""
Gibt alle Vorschlaege fuer die aktuelle Phase zurueck.
Inkludiert fachspezifische und allgemeine Vorschlaege (Feature f18).
Args:
session: Die aktuelle Session
Returns:
Alle Vorschlaege fuer die Phase
"""
return self.get_suggestions(session, limit=100)
def get_suggestion_by_id(
self,
session: LessonSession,
suggestion_id: str
) -> Optional[PhaseSuggestion]:
"""
Gibt einen spezifischen Vorschlag zurueck.
Args:
session: Die aktuelle Session
suggestion_id: ID des Vorschlags
Returns:
Der Vorschlag oder None
"""
all_suggestions = self.get_all_suggestions(session)
for s in all_suggestions:
if s.id == suggestion_id:
return s
return None
def get_suggestions_by_type(
self,
session: LessonSession,
activity_type: str
) -> List[PhaseSuggestion]:
"""
Gibt Vorschlaege eines bestimmten Typs zurueck.
Args:
session: Die aktuelle Session
activity_type: Der Aktivitaetstyp (z.B. "warmup", "group_work")
Returns:
Gefilterte Vorschlaege
"""
all_suggestions = self.get_all_suggestions(session)
return [s for s in all_suggestions if s.activity_type == activity_type]
def get_suggestions_response(
self,
session: LessonSession,
limit: int = 3
) -> Dict[str, Any]:
"""
Gibt die Vorschlaege als API-Response-Format zurueck (Feature f18 erweitert).
Args:
session: Die aktuelle Session
limit: Maximale Anzahl Vorschlaege
Returns:
Dictionary fuer API-Response
"""
suggestions = self.get_suggestions(session, limit)
# Zaehle fachspezifische und allgemeine Vorschlaege
general_count = len(PHASE_SUGGESTIONS.get(session.current_phase, []))
subject_count = len(self._get_subject_suggestions(
session.subject,
session.current_phase
)) if session.subject else 0
return {
"suggestions": [s.to_dict() for s in suggestions],
"current_phase": session.current_phase.value,
"phase_display_name": session.get_phase_display_name(),
"total_available": general_count + subject_count,
"subject_specific_available": subject_count,
"subject": session.subject,
}