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>
This commit is contained in:
614
backend/llm_gateway/services/communication_service.py
Normal file
614
backend/llm_gateway/services/communication_service.py
Normal file
@@ -0,0 +1,614 @@
|
||||
"""
|
||||
Communication Service - KI-gestützte Lehrer-Eltern-Kommunikation.
|
||||
|
||||
Unterstützt Lehrkräfte bei der Erstellung professioneller, rechtlich fundierter
|
||||
Kommunikation mit Eltern. Basiert auf den Prinzipien der gewaltfreien Kommunikation
|
||||
(GFK nach Marshall Rosenberg) und deutschen Schulgesetzen.
|
||||
|
||||
Die rechtlichen Referenzen werden dynamisch aus der Datenbank geladen
|
||||
(edu_search_documents Tabelle), nicht mehr hardcoded.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import os
|
||||
from typing import Optional, List, Dict, Any
|
||||
from enum import Enum, auto
|
||||
from dataclasses import dataclass
|
||||
|
||||
import httpx
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Legal Crawler API URL (für dynamische Rechtsinhalte)
|
||||
LEGAL_CRAWLER_API_URL = os.getenv(
|
||||
"LEGAL_CRAWLER_API_URL",
|
||||
"http://localhost:8000/v1/legal-crawler"
|
||||
)
|
||||
|
||||
|
||||
class CommunicationType(str, Enum):
|
||||
"""Arten von Eltern-Kommunikation."""
|
||||
GENERAL_INFO = "general_info" # Allgemeine Information
|
||||
BEHAVIOR = "behavior" # Verhalten/Disziplin
|
||||
ACADEMIC = "academic" # Schulleistungen
|
||||
ATTENDANCE = "attendance" # Anwesenheit/Fehlzeiten
|
||||
MEETING_INVITE = "meeting_invite" # Einladung zum Gespräch
|
||||
POSITIVE_FEEDBACK = "positive_feedback" # Positives Feedback
|
||||
CONCERN = "concern" # Bedenken äußern
|
||||
CONFLICT = "conflict" # Konfliktlösung
|
||||
SPECIAL_NEEDS = "special_needs" # Förderbedarf
|
||||
|
||||
|
||||
class CommunicationTone(str, Enum):
|
||||
"""Tonalität der Kommunikation."""
|
||||
FORMAL = "formal" # Sehr förmlich
|
||||
PROFESSIONAL = "professional" # Professionell-freundlich
|
||||
WARM = "warm" # Warmherzig
|
||||
CONCERNED = "concerned" # Besorgt
|
||||
APPRECIATIVE = "appreciative" # Wertschätzend
|
||||
|
||||
|
||||
@dataclass
|
||||
class LegalReference:
|
||||
"""Rechtliche Referenz für Kommunikation."""
|
||||
law: str # z.B. "SchulG NRW"
|
||||
paragraph: str # z.B. "§ 42"
|
||||
title: str # z.B. "Pflichten der Eltern"
|
||||
summary: str # Kurzzusammenfassung
|
||||
relevance: str # Warum relevant für diesen Fall
|
||||
|
||||
|
||||
@dataclass
|
||||
class GFKPrinciple:
|
||||
"""Prinzip der Gewaltfreien Kommunikation."""
|
||||
principle: str # z.B. "Beobachtung"
|
||||
description: str # Erklärung
|
||||
example: str # Beispiel im Kontext
|
||||
|
||||
|
||||
# Fallback Rechtliche Grundlagen (nur verwendet wenn DB leer)
|
||||
# Die primäre Quelle sind gecrawlte Dokumente in der edu_search_documents Tabelle
|
||||
FALLBACK_LEGAL_REFERENCES: Dict[str, Dict[str, LegalReference]] = {
|
||||
"DEFAULT": {
|
||||
"elternpflichten": LegalReference(
|
||||
law="Landesschulgesetz",
|
||||
paragraph="(je nach Bundesland)",
|
||||
title="Pflichten der Eltern",
|
||||
summary="Eltern haben die Pflicht, die schulische Entwicklung zu unterstützen.",
|
||||
relevance="Grundlage für Kooperationsaufforderungen"
|
||||
),
|
||||
"schulpflicht": LegalReference(
|
||||
law="Landesschulgesetz",
|
||||
paragraph="(je nach Bundesland)",
|
||||
title="Schulpflicht",
|
||||
summary="Kinder sind schulpflichtig. Eltern sind verantwortlich für regelmäßigen Schulbesuch.",
|
||||
relevance="Bei Fehlzeiten und Anwesenheitsproblemen"
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async def fetch_legal_references_from_db(state: str) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Lädt rechtliche Referenzen aus der Datenbank (via Legal Crawler API).
|
||||
|
||||
Args:
|
||||
state: Bundesland-Kürzel (z.B. "NRW", "BY", "NW")
|
||||
|
||||
Returns:
|
||||
Liste von Rechtsdokumenten mit Paragraphen
|
||||
"""
|
||||
try:
|
||||
async with httpx.AsyncClient(timeout=10.0) as client:
|
||||
response = await client.get(
|
||||
f"{LEGAL_CRAWLER_API_URL}/references/{state}"
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
return data.get("documents", [])
|
||||
else:
|
||||
logger.warning(f"Legal API returned {response.status_code} for state {state}")
|
||||
return []
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler beim Laden rechtlicher Referenzen für {state}: {e}")
|
||||
return []
|
||||
|
||||
|
||||
def parse_db_references_to_legal_refs(
|
||||
db_docs: List[Dict[str, Any]],
|
||||
topic: str
|
||||
) -> List[LegalReference]:
|
||||
"""
|
||||
Konvertiert DB-Dokumente in LegalReference-Objekte.
|
||||
|
||||
Filtert nach relevanten Paragraphen basierend auf dem Topic.
|
||||
"""
|
||||
references = []
|
||||
|
||||
# Topic zu relevanten Paragraph-Nummern mapping
|
||||
topic_keywords = {
|
||||
"elternpflichten": ["42", "76", "85", "eltern", "pflicht"],
|
||||
"schulpflicht": ["41", "35", "schulpflicht", "pflicht"],
|
||||
"ordnungsmassnahmen": ["53", "ordnung", "erzieh", "maßnahm"],
|
||||
"datenschutz": ["120", "daten", "schutz"],
|
||||
"foerderung": ["2", "förder", "bildung", "auftrag"],
|
||||
}
|
||||
|
||||
keywords = topic_keywords.get(topic, ["eltern"])
|
||||
|
||||
for doc in db_docs:
|
||||
law_name = doc.get("law_name", doc.get("title", "Schulgesetz"))
|
||||
paragraphs = doc.get("paragraphs", [])
|
||||
|
||||
if not paragraphs:
|
||||
# Wenn keine Paragraphen extrahiert, allgemeine Referenz erstellen
|
||||
references.append(LegalReference(
|
||||
law=law_name,
|
||||
paragraph="(siehe Gesetzestext)",
|
||||
title=doc.get("title", "Schulgesetz"),
|
||||
summary=f"Rechtliche Grundlage aus {law_name}",
|
||||
relevance=f"Relevant für {topic}"
|
||||
))
|
||||
continue
|
||||
|
||||
# Relevante Paragraphen finden
|
||||
for para in paragraphs[:10]: # Max 10 Paragraphen prüfen
|
||||
para_nr = para.get("nr", "")
|
||||
para_title = para.get("title", "")
|
||||
|
||||
# Prüfen ob Paragraph relevant ist
|
||||
is_relevant = False
|
||||
for keyword in keywords:
|
||||
if keyword.lower() in para_nr.lower() or keyword.lower() in para_title.lower():
|
||||
is_relevant = True
|
||||
break
|
||||
|
||||
if is_relevant:
|
||||
references.append(LegalReference(
|
||||
law=law_name,
|
||||
paragraph=para_nr,
|
||||
title=para_title[:100],
|
||||
summary=f"{para_title[:150]}",
|
||||
relevance=f"Relevant für {topic}"
|
||||
))
|
||||
|
||||
return references
|
||||
|
||||
# GFK-Prinzipien
|
||||
GFK_PRINCIPLES = [
|
||||
GFKPrinciple(
|
||||
principle="Beobachtung",
|
||||
description="Konkrete Handlungen beschreiben ohne Bewertung oder Interpretation",
|
||||
example="'Ich habe bemerkt, dass Max in den letzten zwei Wochen dreimal ohne Hausaufgaben kam.' statt 'Max ist faul.'"
|
||||
),
|
||||
GFKPrinciple(
|
||||
principle="Gefühle",
|
||||
description="Eigene Gefühle ausdrücken (Ich-Botschaften)",
|
||||
example="'Ich mache mir Sorgen...' statt 'Sie müssen endlich...'"
|
||||
),
|
||||
GFKPrinciple(
|
||||
principle="Bedürfnisse",
|
||||
description="Dahinterliegende Bedürfnisse benennen",
|
||||
example="'Mir ist wichtig, dass Max sein Potential entfalten kann.' statt 'Sie müssen mehr kontrollieren.'"
|
||||
),
|
||||
GFKPrinciple(
|
||||
principle="Bitten",
|
||||
description="Konkrete, erfüllbare Bitten formulieren",
|
||||
example="'Wären Sie bereit, täglich die Hausaufgaben zu prüfen?' statt 'Tun Sie endlich etwas!'"
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
# Kommunikationsvorlagen
|
||||
COMMUNICATION_TEMPLATES: Dict[CommunicationType, Dict[str, str]] = {
|
||||
CommunicationType.GENERAL_INFO: {
|
||||
"subject": "Information: {topic}",
|
||||
"opening": "Sehr geehrte/r {parent_name},\n\nich möchte Sie über folgendes informieren:",
|
||||
"closing": "Bei Fragen stehe ich Ihnen gerne zur Verfügung.\n\nMit freundlichen Grüßen",
|
||||
},
|
||||
CommunicationType.BEHAVIOR: {
|
||||
"subject": "Gesprächswunsch: {student_name}",
|
||||
"opening": "Sehr geehrte/r {parent_name},\n\nich wende mich heute an Sie, da mir das Wohlergehen von {student_name} sehr am Herzen liegt.",
|
||||
"closing": "Ich bin überzeugt, dass wir gemeinsam eine gute Lösung finden können. Ich würde mich über ein Gespräch freuen.\n\nMit freundlichen Grüßen",
|
||||
},
|
||||
CommunicationType.ACADEMIC: {
|
||||
"subject": "Schulische Entwicklung: {student_name}",
|
||||
"opening": "Sehr geehrte/r {parent_name},\n\nich möchte Sie über die schulische Entwicklung von {student_name} informieren.",
|
||||
"closing": "Ich würde mich freuen, wenn wir gemeinsam überlegen könnten, wie wir {student_name} optimal unterstützen können.\n\nMit freundlichen Grüßen",
|
||||
},
|
||||
CommunicationType.ATTENDANCE: {
|
||||
"subject": "Fehlzeiten: {student_name}",
|
||||
"opening": "Sehr geehrte/r {parent_name},\n\nich wende mich an Sie bezüglich der Anwesenheit von {student_name}.",
|
||||
"closing": "Gemäß {legal_reference} sind regelmäßige Fehlzeiten meldepflichtig. Ich bin sicher, dass wir gemeinsam eine Lösung finden.\n\nMit freundlichen Grüßen",
|
||||
},
|
||||
CommunicationType.MEETING_INVITE: {
|
||||
"subject": "Einladung zum Elterngespräch",
|
||||
"opening": "Sehr geehrte/r {parent_name},\n\nich würde mich freuen, Sie zu einem persönlichen Gespräch einzuladen.",
|
||||
"closing": "Bitte teilen Sie mir mit, ob einer der vorgeschlagenen Termine für Sie passt, oder nennen Sie mir einen Alternativtermin.\n\nMit freundlichen Grüßen",
|
||||
},
|
||||
CommunicationType.POSITIVE_FEEDBACK: {
|
||||
"subject": "Positive Rückmeldung: {student_name}",
|
||||
"opening": "Sehr geehrte/r {parent_name},\n\nich freue mich, Ihnen heute eine erfreuliche Nachricht mitteilen zu können.",
|
||||
"closing": "Ich freue mich, {student_name} auf diesem positiven Weg weiter begleiten zu dürfen.\n\nMit herzlichen Grüßen",
|
||||
},
|
||||
CommunicationType.CONCERN: {
|
||||
"subject": "Gemeinsame Sorge: {student_name}",
|
||||
"opening": "Sehr geehrte/r {parent_name},\n\nich wende mich heute an Sie, weil mir etwas aufgefallen ist, das ich gerne mit Ihnen besprechen würde.",
|
||||
"closing": "Ich bin überzeugt, dass wir im Sinne von {student_name} gemeinsam eine gute Lösung finden werden.\n\nMit freundlichen Grüßen",
|
||||
},
|
||||
CommunicationType.CONFLICT: {
|
||||
"subject": "Bitte um ein klärendes Gespräch",
|
||||
"opening": "Sehr geehrte/r {parent_name},\n\nich möchte das Gespräch mit Ihnen suchen, da mir eine konstruktive Zusammenarbeit sehr wichtig ist.",
|
||||
"closing": "Mir liegt eine gute Kooperation zum Wohl von {student_name} am Herzen. Ich bin überzeugt, dass wir im Dialog eine für alle Seiten gute Lösung finden können.\n\nMit freundlichen Grüßen",
|
||||
},
|
||||
CommunicationType.SPECIAL_NEEDS: {
|
||||
"subject": "Förderung: {student_name}",
|
||||
"opening": "Sehr geehrte/r {parent_name},\n\nich möchte mit Ihnen über die individuelle Förderung von {student_name} sprechen.",
|
||||
"closing": "Gemäß dem Bildungsauftrag ({legal_reference}) ist es uns ein besonderes Anliegen, jedes Kind optimal zu fördern. Lassen Sie uns gemeinsam überlegen, wie wir {student_name} bestmöglich unterstützen können.\n\nMit freundlichen Grüßen",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
class CommunicationService:
|
||||
"""
|
||||
Service zur Unterstützung von Lehrer-Eltern-Kommunikation.
|
||||
|
||||
Generiert professionelle, rechtlich fundierte und empathische Nachrichten
|
||||
basierend auf den Prinzipien der gewaltfreien Kommunikation.
|
||||
|
||||
Rechtliche Referenzen werden dynamisch aus der DB geladen (via Legal Crawler API).
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.fallback_references = FALLBACK_LEGAL_REFERENCES
|
||||
self.gfk_principles = GFK_PRINCIPLES
|
||||
self.templates = COMMUNICATION_TEMPLATES
|
||||
# Cache für DB-Referenzen (um wiederholte API-Calls zu vermeiden)
|
||||
self._cached_references: Dict[str, List[LegalReference]] = {}
|
||||
|
||||
async def get_legal_references_async(
|
||||
self,
|
||||
state: str,
|
||||
topic: str
|
||||
) -> List[LegalReference]:
|
||||
"""
|
||||
Gibt relevante rechtliche Referenzen für ein Bundesland und Thema zurück.
|
||||
Lädt aus DB via Legal Crawler API.
|
||||
|
||||
Args:
|
||||
state: Bundesland-Kürzel (z.B. "NRW", "BY", "NW")
|
||||
topic: Themenbereich (z.B. "elternpflichten", "schulpflicht")
|
||||
|
||||
Returns:
|
||||
Liste relevanter LegalReference-Objekte
|
||||
"""
|
||||
cache_key = f"{state}:{topic}"
|
||||
|
||||
# Cache prüfen
|
||||
if cache_key in self._cached_references:
|
||||
return self._cached_references[cache_key]
|
||||
|
||||
# Aus DB laden
|
||||
db_docs = await fetch_legal_references_from_db(state)
|
||||
|
||||
if db_docs:
|
||||
# DB-Dokumente in LegalReference konvertieren
|
||||
references = parse_db_references_to_legal_refs(db_docs, topic)
|
||||
if references:
|
||||
self._cached_references[cache_key] = references
|
||||
return references
|
||||
|
||||
# Fallback wenn DB leer
|
||||
logger.info(f"Keine DB-Referenzen für {state}/{topic}, nutze Fallback")
|
||||
return self._get_fallback_references(state, topic)
|
||||
|
||||
def get_legal_references(
|
||||
self,
|
||||
state: str,
|
||||
topic: str
|
||||
) -> List[LegalReference]:
|
||||
"""
|
||||
Synchrone Methode für Rückwärtskompatibilität.
|
||||
Nutzt nur Fallback-Referenzen (für non-async Kontexte).
|
||||
|
||||
Für dynamische DB-Referenzen bitte get_legal_references_async() verwenden.
|
||||
"""
|
||||
return self._get_fallback_references(state, topic)
|
||||
|
||||
def _get_fallback_references(
|
||||
self,
|
||||
state: str,
|
||||
topic: str
|
||||
) -> List[LegalReference]:
|
||||
"""Gibt Fallback-Referenzen zurück."""
|
||||
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]:
|
||||
"""
|
||||
Gibt GFK-Leitlinien für einen Kommunikationstyp zurück.
|
||||
"""
|
||||
return self.gfk_principles
|
||||
|
||||
def get_template(
|
||||
self,
|
||||
comm_type: CommunicationType
|
||||
) -> Dict[str, str]:
|
||||
"""
|
||||
Gibt die Vorlage für einen Kommunikationstyp zurück.
|
||||
"""
|
||||
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 für die KI-gestützte Nachrichtengenerierung.
|
||||
|
||||
Args:
|
||||
comm_type: Art der Kommunikation
|
||||
state: Bundesland für rechtliche Referenzen
|
||||
tone: Gewünschte Tonalität
|
||||
|
||||
Returns:
|
||||
System-Prompt für LLM
|
||||
"""
|
||||
# Rechtliche Referenzen sammeln
|
||||
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"
|
||||
|
||||
# Tonalität beschreiben
|
||||
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: "Drücke aufrichtige Sorge und Empathie aus.",
|
||||
CommunicationTone.APPRECIATIVE: "Betone Wertschätzung und positives Feedback.",
|
||||
}
|
||||
tone_desc = tone_descriptions.get(tone, tone_descriptions[CommunicationTone.PROFESSIONAL])
|
||||
|
||||
system_prompt = f"""Du bist ein erfahrener Kommunikationsberater für Lehrkräfte 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. GEFÜHLE: Drücke Gefühle als Ich-Botschaften aus
|
||||
Beispiel: "Ich mache mir Sorgen..." statt "Sie müssen..."
|
||||
|
||||
3. BEDÜRFNISSE: Benenne dahinterliegende Bedürfnisse
|
||||
Beispiel: "Mir ist wichtig, dass..." statt "Sie sollten..."
|
||||
|
||||
4. BITTEN: Formuliere konkrete, erfüllbare Bitten
|
||||
Beispiel: "Wären Sie bereit, ...?" statt "Tun Sie endlich...!"
|
||||
|
||||
WICHTIGE REGELN:
|
||||
- Immer die Würde aller Beteiligten wahren
|
||||
- Keine Schuldzuweisungen oder Vorwürfe
|
||||
- Lösungsorientiert statt problemfokussiert
|
||||
- Auf Augenhöhe kommunizieren
|
||||
- Kooperation statt Konfrontation
|
||||
- Deutsche Sprache, förmliche Anrede (Sie)
|
||||
- Sachlich, aber empathisch
|
||||
{legal_context}
|
||||
|
||||
TONALITÄT:
|
||||
{tone_desc}
|
||||
|
||||
FORMAT:
|
||||
- Verfasse den Brief als vollständigen, versandfertigen Text
|
||||
- Beginne mit der Anrede
|
||||
- Strukturiere den Inhalt klar und verständlich
|
||||
- Schließe mit einer freundlichen Grußformel
|
||||
- Die Signatur (Name der Lehrkraft) wird später hinzugefügt
|
||||
|
||||
WICHTIG: Der Brief soll professionell und rechtlich einwandfrei sein, aber gleichzeitig
|
||||
menschlich und einladend wirken. Ziel ist immer eine konstruktive Zusammenarbeit."""
|
||||
|
||||
return system_prompt
|
||||
|
||||
def build_user_prompt(
|
||||
self,
|
||||
comm_type: CommunicationType,
|
||||
context: Dict[str, Any]
|
||||
) -> str:
|
||||
"""
|
||||
Erstellt den User-Prompt aus dem Kontext.
|
||||
|
||||
Args:
|
||||
comm_type: Art der Kommunikation
|
||||
context: Kontextinformationen (student_name, parent_name, situation, etc.)
|
||||
|
||||
Returns:
|
||||
User-Prompt für LLM
|
||||
"""
|
||||
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 Elterngespräch",
|
||||
CommunicationType.POSITIVE_FEEDBACK: "positives Feedback",
|
||||
CommunicationType.CONCERN: "eine Sorge oder ein Anliegen",
|
||||
CommunicationType.CONFLICT: "eine konflikthafte Situation",
|
||||
CommunicationType.SPECIAL_NEEDS: "Förderbedarf oder besondere Unterstützung",
|
||||
}
|
||||
type_desc = type_descriptions.get(comm_type, "ein Anliegen")
|
||||
|
||||
user_prompt = f"""Schreibe einen Elternbrief zu folgendem Anlass: {type_desc}
|
||||
|
||||
Schülername: {student_name}
|
||||
Elternname: {parent_name}
|
||||
|
||||
Situation:
|
||||
{situation}
|
||||
"""
|
||||
|
||||
if additional_info:
|
||||
user_prompt += f"\nZusätzliche 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)
|
||||
- Verständnis und Sorge ausdrücken (Gefühle)
|
||||
- Das gemeinsame Ziel betonen (Bedürfnisse)
|
||||
- Einen konstruktiven Vorschlag machen (Bitte)
|
||||
"""
|
||||
|
||||
return user_prompt
|
||||
|
||||
def validate_communication(self, text: str) -> Dict[str, Any]:
|
||||
"""
|
||||
Validiert eine generierte Kommunikation auf GFK-Konformität.
|
||||
|
||||
Args:
|
||||
text: Der zu prüfende Text
|
||||
|
||||
Returns:
|
||||
Validierungsergebnis mit Verbesserungsvorschlägen
|
||||
"""
|
||||
issues = []
|
||||
suggestions = []
|
||||
|
||||
# Prüfe auf problematische Formulierungen
|
||||
problematic_patterns = [
|
||||
("Sie müssen", "Vorschlag: 'Wären Sie bereit, ...' oder 'Ich bitte Sie, ...'"),
|
||||
("Sie sollten", "Vorschlag: 'Ich würde mir wünschen, ...'"),
|
||||
("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"),
|
||||
("unverschämt", "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)
|
||||
|
||||
# Prüfe auf positive Elemente
|
||||
positive_elements = []
|
||||
positive_patterns = [
|
||||
("Ich habe bemerkt", "Gute Beobachtung"),
|
||||
("Ich möchte", "Gute Ich-Botschaft"),
|
||||
("gemeinsam", "Gute Kooperationsorientierung"),
|
||||
("wichtig", "Gutes Bedürfnis-Statement"),
|
||||
("freuen", "Positive Tonalität"),
|
||||
("Wären 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]]:
|
||||
"""Gibt alle verfügbaren Kommunikationstypen zurück."""
|
||||
return [
|
||||
{"value": ct.value, "label": self._get_type_label(ct)}
|
||||
for ct in CommunicationType
|
||||
]
|
||||
|
||||
def _get_type_label(self, ct: CommunicationType) -> str:
|
||||
"""Gibt das deutsche Label für einen Kommunikationstyp zurück."""
|
||||
labels = {
|
||||
CommunicationType.GENERAL_INFO: "Allgemeine Information",
|
||||
CommunicationType.BEHAVIOR: "Verhalten/Disziplin",
|
||||
CommunicationType.ACADEMIC: "Schulleistungen",
|
||||
CommunicationType.ATTENDANCE: "Fehlzeiten",
|
||||
CommunicationType.MEETING_INVITE: "Einladung zum Gespräch",
|
||||
CommunicationType.POSITIVE_FEEDBACK: "Positives Feedback",
|
||||
CommunicationType.CONCERN: "Bedenken äußern",
|
||||
CommunicationType.CONFLICT: "Konfliktlösung",
|
||||
CommunicationType.SPECIAL_NEEDS: "Förderbedarf",
|
||||
}
|
||||
return labels.get(ct, ct.value)
|
||||
|
||||
def get_all_tones(self) -> List[Dict[str, str]]:
|
||||
"""Gibt alle verfügbaren Tonalitäten zurück."""
|
||||
labels = {
|
||||
CommunicationTone.FORMAL: "Sehr förmlich",
|
||||
CommunicationTone.PROFESSIONAL: "Professionell-freundlich",
|
||||
CommunicationTone.WARM: "Warmherzig",
|
||||
CommunicationTone.CONCERNED: "Besorgt",
|
||||
CommunicationTone.APPRECIATIVE: "Wertschätzend",
|
||||
}
|
||||
return [
|
||||
{"value": t.value, "label": labels.get(t, t.value)}
|
||||
for t in CommunicationTone
|
||||
]
|
||||
|
||||
def get_states(self) -> List[Dict[str, str]]:
|
||||
"""Gibt alle verfügbaren Bundesländer zurück."""
|
||||
return [
|
||||
{"value": "NRW", "label": "Nordrhein-Westfalen"},
|
||||
{"value": "BY", "label": "Bayern"},
|
||||
{"value": "BW", "label": "Baden-Württemberg"},
|
||||
{"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": "Thüringen"},
|
||||
{"value": "HH", "label": "Hamburg"},
|
||||
{"value": "HB", "label": "Bremen"},
|
||||
{"value": "SL", "label": "Saarland"},
|
||||
]
|
||||
|
||||
|
||||
# Singleton-Instanz
|
||||
_communication_service: Optional[CommunicationService] = None
|
||||
|
||||
|
||||
def get_communication_service() -> CommunicationService:
|
||||
"""Gibt die Singleton-Instanz des CommunicationService zurück."""
|
||||
global _communication_service
|
||||
if _communication_service is None:
|
||||
_communication_service = CommunicationService()
|
||||
return _communication_service
|
||||
Reference in New Issue
Block a user