Files
breakpilot-lehrer/backend-lehrer/llm_gateway/services/communication_service.py
Benjamin Admin 451365a312 [split-required] Split remaining 500-680 LOC files (final batch)
website (17 pages + 3 components):
- multiplayer/wizard, middleware/wizard+test-wizard, communication
- builds/wizard, staff-search, voice, sbom/wizard
- foerderantrag, mail/tasks, tools/communication, sbom
- compliance/evidence, uni-crawler, brandbook (already done)
- CollectionsTab, IngestionTab, RiskHeatmap

backend-lehrer (5 files):
- letters_api (641 → 2), certificates_api (636 → 2)
- alerts_agent/db/models (636 → 3)
- llm_gateway/communication_service (614 → 2)
- game/database already done in prior batch

klausur-service (2 files):
- hybrid_vocab_extractor (664 → 2)
- klausur-service/frontend: api.ts (620 → 3), EHUploadWizard (591 → 2)

voice-service (3 files):
- bqas/rag_judge (618 → 3), runner (529 → 2)
- enhanced_task_orchestrator (519 → 2)

studio-v2 (6 files):
- korrektur/[klausurId] (578 → 4), fairness (569 → 2)
- AlertsWizard (552 → 2), OnboardingWizard (513 → 2)
- korrektur/api.ts (506 → 3), geo-lernwelt (501 → 2)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-25 08:56:45 +02:00

302 lines
12 KiB
Python

"""
Communication Service - KI-gestuetzte Lehrer-Eltern-Kommunikation.
Split into:
- communication_types.py: Enums, data classes, templates, legal references
- communication_service.py (this file): CommunicationService class
All symbols are re-exported here for backward compatibility.
"""
import logging
from typing import Optional, List, Dict, Any
from .communication_types import (
CommunicationType,
CommunicationTone,
LegalReference,
GFKPrinciple,
FALLBACK_LEGAL_REFERENCES,
GFK_PRINCIPLES,
COMMUNICATION_TEMPLATES,
fetch_legal_references_from_db,
parse_db_references_to_legal_refs,
)
# Re-export for backward compatibility
__all__ = [
"CommunicationType",
"CommunicationTone",
"LegalReference",
"GFKPrinciple",
"CommunicationService",
"get_communication_service",
"fetch_legal_references_from_db",
"parse_db_references_to_legal_refs",
]
logger = logging.getLogger(__name__)
class CommunicationService:
"""
Service zur Unterstuetzung von Lehrer-Eltern-Kommunikation.
Generiert professionelle, rechtlich fundierte und empathische Nachrichten
basierend auf den Prinzipien der gewaltfreien Kommunikation.
"""
def __init__(self):
self.fallback_references = FALLBACK_LEGAL_REFERENCES
self.gfk_principles = GFK_PRINCIPLES
self.templates = COMMUNICATION_TEMPLATES
self._cached_references: Dict[str, List[LegalReference]] = {}
async def get_legal_references_async(
self, state: str, topic: str
) -> List[LegalReference]:
"""Gibt relevante rechtliche Referenzen fuer ein Bundesland und Thema zurueck."""
cache_key = f"{state}:{topic}"
if cache_key in self._cached_references:
return self._cached_references[cache_key]
db_docs = await fetch_legal_references_from_db(state)
if db_docs:
references = parse_db_references_to_legal_refs(db_docs, topic)
if references:
self._cached_references[cache_key] = references
return references
logger.info(f"Keine DB-Referenzen fuer {state}/{topic}, nutze Fallback")
return self._get_fallback_references(state, topic)
def get_legal_references(self, state: str, topic: str) -> List[LegalReference]:
"""Synchrone Methode fuer Rueckwaertskompatibilitaet."""
return self._get_fallback_references(state, topic)
def _get_fallback_references(self, state: str, topic: str) -> List[LegalReference]:
"""Gibt Fallback-Referenzen zurueck."""
state_refs = self.fallback_references.get("DEFAULT", {})
if topic in state_refs:
return [state_refs[topic]]
return list(state_refs.values())
def get_gfk_guidance(self, comm_type: CommunicationType) -> List[GFKPrinciple]:
return self.gfk_principles
def get_template(self, comm_type: CommunicationType) -> Dict[str, str]:
return self.templates.get(comm_type, self.templates[CommunicationType.GENERAL_INFO])
def build_system_prompt(
self, comm_type: CommunicationType, state: str, tone: CommunicationTone
) -> str:
"""Erstellt den System-Prompt fuer die KI-gestuetzte Nachrichtengenerierung."""
topic_map = {
CommunicationType.ATTENDANCE: "schulpflicht",
CommunicationType.BEHAVIOR: "ordnungsmassnahmen",
CommunicationType.ACADEMIC: "foerderung",
CommunicationType.SPECIAL_NEEDS: "foerderung",
CommunicationType.CONCERN: "elternpflichten",
CommunicationType.CONFLICT: "elternpflichten",
}
topic = topic_map.get(comm_type, "elternpflichten")
legal_refs = self.get_legal_references(state, topic)
legal_context = ""
if legal_refs:
legal_context = "\n\nRechtliche Grundlagen:\n"
for ref in legal_refs:
legal_context += f"- {ref.law} {ref.paragraph} ({ref.title}): {ref.summary}\n"
tone_descriptions = {
CommunicationTone.FORMAL: "Verwende eine sehr formelle, sachliche Sprache.",
CommunicationTone.PROFESSIONAL: "Verwende eine professionelle, aber freundliche Sprache.",
CommunicationTone.WARM: "Verwende eine warmherzige, einladende Sprache.",
CommunicationTone.CONCERNED: "Druecke aufrichtige Sorge und Empathie aus.",
CommunicationTone.APPRECIATIVE: "Betone Wertschaetzung und positives Feedback.",
}
tone_desc = tone_descriptions.get(tone, tone_descriptions[CommunicationTone.PROFESSIONAL])
return f"""Du bist ein erfahrener Kommunikationsberater fuer Lehrkraefte im deutschen Schulsystem.
Deine Aufgabe ist es, professionelle, empathische und rechtlich fundierte Elternbriefe zu verfassen.
GRUNDPRINZIPIEN (Gewaltfreie Kommunikation nach Marshall Rosenberg):
1. BEOBACHTUNG: Beschreibe konkrete Handlungen ohne Bewertung
Beispiel: "Ich habe bemerkt, dass..." statt "Das Kind ist..."
2. GEFUEHLE: Druecke Gefuehle als Ich-Botschaften aus
Beispiel: "Ich mache mir Sorgen..." statt "Sie muessen..."
3. BEDUERFNISSE: Benenne dahinterliegende Beduerfnisse
Beispiel: "Mir ist wichtig, dass..." statt "Sie sollten..."
4. BITTEN: Formuliere konkrete, erfuellbare Bitten
Beispiel: "Waeren Sie bereit, ...?" statt "Tun Sie endlich...!"
WICHTIGE REGELN:
- Immer die Wuerde aller Beteiligten wahren
- Keine Schuldzuweisungen oder Vorwuerfe
- Loesungsorientiert statt problemfokussiert
- Auf Augenhoehe kommunizieren
- Kooperation statt Konfrontation
- Deutsche Sprache, foermliche Anrede (Sie)
- Sachlich, aber empathisch
{legal_context}
TONALITAET:
{tone_desc}
FORMAT:
- Verfasse den Brief als vollstaendigen, versandfertigen Text
- Beginne mit der Anrede
- Strukturiere den Inhalt klar und verstaendlich
- Schliesse mit einer freundlichen Grussformel
- Die Signatur (Name der Lehrkraft) wird spaeter hinzugefuegt
WICHTIG: Der Brief soll professionell und rechtlich einwandfrei sein, aber gleichzeitig
menschlich und einladend wirken. Ziel ist immer eine konstruktive Zusammenarbeit."""
def build_user_prompt(
self, comm_type: CommunicationType, context: Dict[str, Any]
) -> str:
"""Erstellt den User-Prompt aus dem Kontext."""
student_name = context.get("student_name", "das Kind")
parent_name = context.get("parent_name", "Frau/Herr")
situation = context.get("situation", "")
additional_info = context.get("additional_info", "")
type_descriptions = {
CommunicationType.GENERAL_INFO: "eine allgemeine Information",
CommunicationType.BEHAVIOR: "ein Verhalten, das besprochen werden sollte",
CommunicationType.ACADEMIC: "die schulische Entwicklung",
CommunicationType.ATTENDANCE: "Fehlzeiten oder Anwesenheitsprobleme",
CommunicationType.MEETING_INVITE: "eine Einladung zum Elterngespraech",
CommunicationType.POSITIVE_FEEDBACK: "positives Feedback",
CommunicationType.CONCERN: "eine Sorge oder ein Anliegen",
CommunicationType.CONFLICT: "eine konflikthafte Situation",
CommunicationType.SPECIAL_NEEDS: "Foerderbedarf oder besondere Unterstuetzung",
}
type_desc = type_descriptions.get(comm_type, "ein Anliegen")
user_prompt = f"""Schreibe einen Elternbrief zu folgendem Anlass: {type_desc}
Schuelername: {student_name}
Elternname: {parent_name}
Situation:
{situation}
"""
if additional_info:
user_prompt += f"\nZusaetzliche Informationen:\n{additional_info}\n"
user_prompt += """
Bitte verfasse einen professionellen, empathischen Brief nach den GFK-Prinzipien.
Der Brief sollte:
- Die Situation sachlich beschreiben (Beobachtung)
- Verstaendnis und Sorge ausdruecken (Gefuehle)
- Das gemeinsame Ziel betonen (Beduerfnisse)
- Einen konstruktiven Vorschlag machen (Bitte)
"""
return user_prompt
def validate_communication(self, text: str) -> Dict[str, Any]:
"""Validiert eine generierte Kommunikation auf GFK-Konformitaet."""
issues = []
suggestions = []
problematic_patterns = [
("Sie muessen", "Vorschlag: 'Waeren Sie bereit, ...' oder 'Ich bitte Sie, ...'"),
("Sie sollten", "Vorschlag: 'Ich wuerde mir wuenschen, ...'"),
("Das Kind ist", "Vorschlag: 'Ich habe beobachtet, dass ...'"),
("immer", "Vorsicht bei Verallgemeinerungen - besser konkrete Beispiele"),
("nie", "Vorsicht bei Verallgemeinerungen - besser konkrete Beispiele"),
("faul", "Vorschlag: Verhalten konkret beschreiben statt bewerten"),
("unverschaemt", "Vorschlag: Verhalten konkret beschreiben statt bewerten"),
("respektlos", "Vorschlag: Verhalten konkret beschreiben statt bewerten"),
]
for pattern, suggestion in problematic_patterns:
if pattern.lower() in text.lower():
issues.append(f"Problematische Formulierung gefunden: '{pattern}'")
suggestions.append(suggestion)
positive_elements = []
positive_patterns = [
("Ich habe bemerkt", "Gute Beobachtung"),
("Ich moechte", "Gute Ich-Botschaft"),
("gemeinsam", "Gute Kooperationsorientierung"),
("wichtig", "Gutes Beduerfnis-Statement"),
("freuen", "Positive Tonalitaet"),
("Waeren Sie bereit", "Gute Bitte-Formulierung"),
]
for pattern, feedback in positive_patterns:
if pattern.lower() in text.lower():
positive_elements.append(feedback)
return {
"is_valid": len(issues) == 0,
"issues": issues,
"suggestions": suggestions,
"positive_elements": positive_elements,
"gfk_score": max(0, 100 - len(issues) * 15 + len(positive_elements) * 10) / 100
}
def get_all_communication_types(self) -> List[Dict[str, str]]:
return [{"value": ct.value, "label": self._get_type_label(ct)} for ct in CommunicationType]
def _get_type_label(self, ct: CommunicationType) -> str:
labels = {
CommunicationType.GENERAL_INFO: "Allgemeine Information",
CommunicationType.BEHAVIOR: "Verhalten/Disziplin",
CommunicationType.ACADEMIC: "Schulleistungen",
CommunicationType.ATTENDANCE: "Fehlzeiten",
CommunicationType.MEETING_INVITE: "Einladung zum Gespraech",
CommunicationType.POSITIVE_FEEDBACK: "Positives Feedback",
CommunicationType.CONCERN: "Bedenken aeussern",
CommunicationType.CONFLICT: "Konfliktloesung",
CommunicationType.SPECIAL_NEEDS: "Foerderbedarf",
}
return labels.get(ct, ct.value)
def get_all_tones(self) -> List[Dict[str, str]]:
labels = {
CommunicationTone.FORMAL: "Sehr foermlich",
CommunicationTone.PROFESSIONAL: "Professionell-freundlich",
CommunicationTone.WARM: "Warmherzig",
CommunicationTone.CONCERNED: "Besorgt",
CommunicationTone.APPRECIATIVE: "Wertschaetzend",
}
return [{"value": t.value, "label": labels.get(t, t.value)} for t in CommunicationTone]
def get_states(self) -> List[Dict[str, str]]:
return [
{"value": "NRW", "label": "Nordrhein-Westfalen"},
{"value": "BY", "label": "Bayern"},
{"value": "BW", "label": "Baden-Wuerttemberg"},
{"value": "NI", "label": "Niedersachsen"},
{"value": "HE", "label": "Hessen"},
{"value": "SN", "label": "Sachsen"},
{"value": "RP", "label": "Rheinland-Pfalz"},
{"value": "SH", "label": "Schleswig-Holstein"},
{"value": "BE", "label": "Berlin"},
{"value": "BB", "label": "Brandenburg"},
{"value": "MV", "label": "Mecklenburg-Vorpommern"},
{"value": "ST", "label": "Sachsen-Anhalt"},
{"value": "TH", "label": "Thueringen"},
{"value": "HH", "label": "Hamburg"},
{"value": "HB", "label": "Bremen"},
{"value": "SL", "label": "Saarland"},
]
_communication_service: Optional[CommunicationService] = None
def get_communication_service() -> CommunicationService:
"""Gibt die Singleton-Instanz des CommunicationService zurueck."""
global _communication_service
if _communication_service is None:
_communication_service = CommunicationService()
return _communication_service