""" 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