This repository has been archived on 2026-02-15. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
breakpilot-pwa/backend/classroom_engine/suggestions.py
Benjamin Admin bfdaf63ba9 fix: Restore all files lost during destructive rebase
A previous `git pull --rebase origin main` dropped 177 local commits,
losing 3400+ files across admin-v2, backend, studio-v2, website,
klausur-service, and many other services. The partial restore attempt
(660295e2) only recovered some files.

This commit restores all missing files from pre-rebase ref 98933f5e
while preserving post-rebase additions (night-scheduler, night-mode UI,
NightModeWidget dashboard integration).

Restored features include:
- AI Module Sidebar (FAB), OCR Labeling, OCR Compare
- GPU Dashboard, RAG Pipeline, Magic Help
- Klausur-Korrektur (8 files), Abitur-Archiv (5+ files)
- Companion, Zeugnisse-Crawler, Screen Flow
- Full backend, studio-v2, website, klausur-service
- All compliance SDKs, agent-core, voice-service
- CI/CD configs, documentation, scripts

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 09:51:32 +01:00

669 lines
23 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
# Unterstuetzte Faecher fuer fachspezifische Vorschlaege
SUPPORTED_SUBJECTS = [
"mathematik", "mathe", "math",
"deutsch",
"englisch", "english",
"biologie", "bio",
"physik",
"chemie",
"geschichte",
"geografie", "erdkunde",
"kunst",
"musik",
"sport",
"informatik",
]
# Fachspezifische Vorschlaege (Feature f18)
SUBJECT_SUGGESTIONS: Dict[str, Dict[LessonPhase, List[Dict[str, Any]]]] = {
"mathematik": {
LessonPhase.EINSTIEG: [
{
"id": "math_warm_up",
"title": "Kopfrechnen-Challenge",
"description": "5 schnelle Kopfrechenaufgaben zum Aufwaermen",
"activity_type": "warmup",
"estimated_minutes": 3,
"icon": "calculate",
"subjects": ["mathematik", "mathe"],
},
{
"id": "math_puzzle",
"title": "Mathematisches Raetsel",
"description": "Ein kniffliges Zahlenraetsel als Einstieg",
"activity_type": "motivation",
"estimated_minutes": 5,
"icon": "extension",
"subjects": ["mathematik", "mathe"],
},
],
LessonPhase.ERARBEITUNG: [
{
"id": "math_geogebra",
"title": "GeoGebra-Exploration",
"description": "Interaktive Visualisierung mit GeoGebra",
"activity_type": "individual_work",
"estimated_minutes": 15,
"icon": "functions",
"subjects": ["mathematik", "mathe"],
},
{
"id": "math_peer_explain",
"title": "Rechenweg erklaeren",
"description": "Schueler erklaeren sich gegenseitig ihre Loesungswege",
"activity_type": "partner_work",
"estimated_minutes": 10,
"icon": "groups",
"subjects": ["mathematik", "mathe"],
},
],
LessonPhase.SICHERUNG: [
{
"id": "math_formula_card",
"title": "Formelkarte erstellen",
"description": "Wichtigste Formeln auf einer Karte festhalten",
"activity_type": "documentation",
"estimated_minutes": 5,
"icon": "note_alt",
"subjects": ["mathematik", "mathe"],
},
],
},
"deutsch": {
LessonPhase.EINSTIEG: [
{
"id": "deutsch_wordle",
"title": "Wordle-Variante",
"description": "Wort des Tages erraten",
"activity_type": "warmup",
"estimated_minutes": 4,
"icon": "abc",
"subjects": ["deutsch"],
},
{
"id": "deutsch_zitat",
"title": "Zitat-Interpretation",
"description": "Ein literarisches Zitat gemeinsam deuten",
"activity_type": "motivation",
"estimated_minutes": 5,
"icon": "format_quote",
"subjects": ["deutsch"],
},
],
LessonPhase.ERARBEITUNG: [
{
"id": "deutsch_textarbeit",
"title": "Textanalyse in Gruppen",
"description": "Gruppenarbeit zu verschiedenen Textabschnitten",
"activity_type": "group_work",
"estimated_minutes": 15,
"icon": "menu_book",
"subjects": ["deutsch"],
},
{
"id": "deutsch_schreibworkshop",
"title": "Schreibwerkstatt",
"description": "Kreatives Schreiben mit Peer-Feedback",
"activity_type": "individual_work",
"estimated_minutes": 20,
"icon": "edit_note",
"subjects": ["deutsch"],
},
],
LessonPhase.SICHERUNG: [
{
"id": "deutsch_zusammenfassung",
"title": "Text-Zusammenfassung",
"description": "Die wichtigsten Punkte in 3 Saetzen formulieren",
"activity_type": "summary",
"estimated_minutes": 5,
"icon": "summarize",
"subjects": ["deutsch"],
},
],
},
"englisch": {
LessonPhase.EINSTIEG: [
{
"id": "english_smalltalk",
"title": "Small Talk Warm-Up",
"description": "2-Minuten Gespraeche zu einem Alltagsthema",
"activity_type": "warmup",
"estimated_minutes": 4,
"icon": "chat",
"subjects": ["englisch", "english"],
},
{
"id": "english_video",
"title": "Authentic Video Clip",
"description": "Kurzer Clip aus einer englischen Serie oder Nachricht",
"activity_type": "motivation",
"estimated_minutes": 5,
"icon": "movie",
"subjects": ["englisch", "english"],
},
],
LessonPhase.ERARBEITUNG: [
{
"id": "english_role_play",
"title": "Role Play Activity",
"description": "Dialoguebung in authentischen Situationen",
"activity_type": "partner_work",
"estimated_minutes": 12,
"icon": "theater_comedy",
"subjects": ["englisch", "english"],
},
{
"id": "english_reading_circle",
"title": "Reading Circle",
"description": "Gemeinsames Lesen mit verteilten Rollen",
"activity_type": "group_work",
"estimated_minutes": 15,
"icon": "auto_stories",
"subjects": ["englisch", "english"],
},
],
},
"biologie": {
LessonPhase.EINSTIEG: [
{
"id": "bio_nature_question",
"title": "Naturfrage",
"description": "Eine spannende Frage aus der Natur diskutieren",
"activity_type": "motivation",
"estimated_minutes": 5,
"icon": "eco",
"subjects": ["biologie", "bio"],
},
],
LessonPhase.ERARBEITUNG: [
{
"id": "bio_experiment",
"title": "Mini-Experiment",
"description": "Einfaches Experiment zum Thema durchfuehren",
"activity_type": "group_work",
"estimated_minutes": 20,
"icon": "science",
"subjects": ["biologie", "bio"],
},
{
"id": "bio_diagram",
"title": "Biologische Zeichnung",
"description": "Beschriftete Zeichnung eines Organismus",
"activity_type": "individual_work",
"estimated_minutes": 15,
"icon": "draw",
"subjects": ["biologie", "bio"],
},
],
},
"physik": {
LessonPhase.EINSTIEG: [
{
"id": "physik_demo",
"title": "Phaenomen-Demo",
"description": "Ein physikalisches Phaenomen vorfuehren",
"activity_type": "motivation",
"estimated_minutes": 5,
"icon": "bolt",
"subjects": ["physik"],
},
],
LessonPhase.ERARBEITUNG: [
{
"id": "physik_simulation",
"title": "PhET-Simulation",
"description": "Interaktive Simulation von phet.colorado.edu",
"activity_type": "individual_work",
"estimated_minutes": 15,
"icon": "smart_toy",
"subjects": ["physik"],
},
{
"id": "physik_rechnung",
"title": "Physikalische Rechnung",
"description": "Rechenaufgabe mit physikalischem Kontext",
"activity_type": "partner_work",
"estimated_minutes": 12,
"icon": "calculate",
"subjects": ["physik"],
},
],
},
"informatik": {
LessonPhase.EINSTIEG: [
{
"id": "info_code_puzzle",
"title": "Code-Puzzle",
"description": "Kurzen Code-Schnipsel analysieren - was macht er?",
"activity_type": "warmup",
"estimated_minutes": 4,
"icon": "code",
"subjects": ["informatik"],
},
],
LessonPhase.ERARBEITUNG: [
{
"id": "info_live_coding",
"title": "Live Coding",
"description": "Gemeinsam Code entwickeln mit Erklaerungen",
"activity_type": "instruction",
"estimated_minutes": 15,
"icon": "terminal",
"subjects": ["informatik"],
},
{
"id": "info_pair_programming",
"title": "Pair Programming",
"description": "Zu zweit programmieren - Driver und Navigator",
"activity_type": "partner_work",
"estimated_minutes": 20,
"icon": "computer",
"subjects": ["informatik"],
},
],
},
}
# Vordefinierte allgemeine Vorschlaege pro Phase
PHASE_SUGGESTIONS: Dict[LessonPhase, List[Dict[str, Any]]] = {
LessonPhase.EINSTIEG: [
{
"id": "warmup_quiz",
"title": "Kurzes Quiz zum Einstieg",
"description": "Aktivieren Sie das Vorwissen der Schueler mit 3-5 Fragen zum Thema",
"activity_type": "warmup",
"estimated_minutes": 3,
"icon": "quiz"
},
{
"id": "problem_story",
"title": "Problemgeschichte erzaehlen",
"description": "Stellen Sie ein alltagsnahes Problem vor, das zum Thema fuehrt",
"activity_type": "motivation",
"estimated_minutes": 5,
"icon": "auto_stories"
},
{
"id": "video_intro",
"title": "Kurzes Erklaervideo",
"description": "Zeigen Sie ein 2-3 Minuten Video zur Einfuehrung ins Thema",
"activity_type": "motivation",
"estimated_minutes": 4,
"icon": "play_circle"
},
{
"id": "brainstorming",
"title": "Brainstorming",
"description": "Sammeln Sie Ideen und Vorkenntnisse der Schueler an der Tafel",
"activity_type": "warmup",
"estimated_minutes": 5,
"icon": "psychology"
},
{
"id": "daily_challenge",
"title": "Tagesaufgabe vorstellen",
"description": "Praesentieren Sie die zentrale Frage oder Aufgabe der Stunde",
"activity_type": "problem_introduction",
"estimated_minutes": 3,
"icon": "flag"
}
],
LessonPhase.ERARBEITUNG: [
{
"id": "think_pair_share",
"title": "Think-Pair-Share",
"description": "Schueler denken erst einzeln nach, tauschen sich dann zu zweit aus und praesentieren im Plenum",
"activity_type": "partner_work",
"estimated_minutes": 10,
"icon": "groups"
},
{
"id": "worksheet_digital",
"title": "Digitales Arbeitsblatt",
"description": "Schueler bearbeiten ein interaktives Arbeitsblatt am Tablet oder Computer",
"activity_type": "individual_work",
"estimated_minutes": 15,
"icon": "description"
},
{
"id": "station_learning",
"title": "Stationenlernen",
"description": "Verschiedene Stationen mit unterschiedlichen Aufgaben und Materialien",
"activity_type": "group_work",
"estimated_minutes": 20,
"icon": "hub"
},
{
"id": "expert_puzzle",
"title": "Expertenrunde (Jigsaw)",
"description": "Schueler werden Experten fuer ein Teilthema und lehren es anderen",
"activity_type": "group_work",
"estimated_minutes": 15,
"icon": "extension"
},
{
"id": "guided_instruction",
"title": "Geleitete Instruktion",
"description": "Schrittweise Erklaerung mit Uebungsphasen zwischendurch",
"activity_type": "instruction",
"estimated_minutes": 12,
"icon": "school"
},
{
"id": "pair_programming",
"title": "Partnerarbeit",
"description": "Zwei Schueler loesen gemeinsam eine Aufgabe",
"activity_type": "partner_work",
"estimated_minutes": 10,
"icon": "people"
}
],
LessonPhase.SICHERUNG: [
{
"id": "mindmap_class",
"title": "Gemeinsame Mindmap",
"description": "Ergebnisse als Mindmap an der Tafel oder digital sammeln und strukturieren",
"activity_type": "visualization",
"estimated_minutes": 8,
"icon": "account_tree"
},
{
"id": "exit_ticket",
"title": "Exit Ticket",
"description": "Schueler notieren 3 Dinge die sie gelernt haben und 1 offene Frage",
"activity_type": "summary",
"estimated_minutes": 5,
"icon": "sticky_note_2"
},
{
"id": "gallery_walk",
"title": "Galerie-Rundgang",
"description": "Schueler praesentieren ihre Ergebnisse und geben sich Feedback",
"activity_type": "presentation",
"estimated_minutes": 10,
"icon": "photo_library"
},
{
"id": "key_points",
"title": "Kernpunkte zusammenfassen",
"description": "Gemeinsam die wichtigsten Erkenntnisse der Stunde formulieren",
"activity_type": "summary",
"estimated_minutes": 5,
"icon": "format_list_bulleted"
},
{
"id": "quick_check",
"title": "Schneller Wissenscheck",
"description": "5 kurze Fragen zur Ueberpruefung des Verstaendnisses",
"activity_type": "documentation",
"estimated_minutes": 5,
"icon": "fact_check"
}
],
LessonPhase.TRANSFER: [
{
"id": "real_world_example",
"title": "Alltagsbeispiele finden",
"description": "Schueler suchen Beispiele aus ihrem Alltag, wo das Gelernte vorkommt",
"activity_type": "application",
"estimated_minutes": 5,
"icon": "public"
},
{
"id": "challenge_task",
"title": "Knobelaufgabe",
"description": "Eine anspruchsvollere Aufgabe fuer schnelle Schueler oder als Bonus",
"activity_type": "differentiation",
"estimated_minutes": 7,
"icon": "psychology"
},
{
"id": "creative_application",
"title": "Kreative Anwendung",
"description": "Schueler wenden das Gelernte in einem kreativen Projekt an",
"activity_type": "application",
"estimated_minutes": 10,
"icon": "palette"
},
{
"id": "peer_teaching",
"title": "Peer-Teaching",
"description": "Schueler erklaeren sich gegenseitig das Gelernte",
"activity_type": "real_world_connection",
"estimated_minutes": 5,
"icon": "supervisor_account"
}
],
LessonPhase.REFLEXION: [
{
"id": "thumbs_feedback",
"title": "Daumen-Feedback",
"description": "Schnelle Stimmungsabfrage: Daumen hoch/mitte/runter",
"activity_type": "feedback",
"estimated_minutes": 2,
"icon": "thumb_up"
},
{
"id": "homework_assign",
"title": "Hausaufgabe vergeben",
"description": "Passende Hausaufgabe zur Vertiefung des Gelernten",
"activity_type": "homework",
"estimated_minutes": 3,
"icon": "home_work"
},
{
"id": "one_word",
"title": "Ein-Wort-Reflexion",
"description": "Jeder Schueler nennt ein Wort, das die Stunde beschreibt",
"activity_type": "feedback",
"estimated_minutes": 3,
"icon": "chat"
},
{
"id": "preview_next",
"title": "Ausblick naechste Stunde",
"description": "Kurzer Ausblick auf das Thema der naechsten Stunde",
"activity_type": "preview",
"estimated_minutes": 2,
"icon": "event"
},
{
"id": "learning_log",
"title": "Lerntagebuch",
"description": "Schueler notieren ihre wichtigsten Erkenntnisse im Lerntagebuch",
"activity_type": "feedback",
"estimated_minutes": 4,
"icon": "menu_book"
}
]
}
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,
}