""" Communication API Routes. API-Endpoints für KI-gestützte Lehrer-Eltern-Kommunikation. Basiert auf den Prinzipien der gewaltfreien Kommunikation (GFK) und deutschen Schulgesetzen. """ import logging from typing import Optional, List from datetime import datetime from fastapi import APIRouter, HTTPException, Depends from pydantic import BaseModel, Field from ..services.communication_service import ( get_communication_service, CommunicationService, CommunicationType, CommunicationTone, ) from ..services.inference import InferenceService, get_inference_service logger = logging.getLogger(__name__) router = APIRouter(prefix="/communication", tags=["communication"]) # ============================================================================= # Pydantic Models # ============================================================================= class CommunicationTypeResponse(BaseModel): """Response für Kommunikationstypen.""" value: str label: str class ToneResponse(BaseModel): """Response für Tonalitäten.""" value: str label: str class StateResponse(BaseModel): """Response für Bundesländer.""" value: str label: str class LegalReferenceResponse(BaseModel): """Response für rechtliche Referenzen.""" law: str paragraph: str title: str summary: str relevance: str class GFKPrincipleResponse(BaseModel): """Response für GFK-Prinzipien.""" principle: str description: str example: str class GenerateRequest(BaseModel): """Request für Nachrichtengenerierung.""" communication_type: str = Field(..., description="Art der Kommunikation (z.B. 'behavior', 'academic')") tone: str = Field("professional", description="Tonalität (formal, professional, warm, concerned, appreciative)") state: str = Field("NRW", description="Bundesland für rechtliche Referenzen") student_name: str = Field(..., description="Name des Schülers/der Schülerin") parent_name: str = Field(..., description="Name der Eltern (z.B. 'Frau Müller')") situation: str = Field(..., description="Beschreibung der Situation") additional_info: Optional[str] = Field(None, description="Zusätzliche Informationen") class GenerateResponse(BaseModel): """Response für generierte Nachrichten.""" message: str subject: str validation: dict legal_references: List[LegalReferenceResponse] gfk_principles: List[GFKPrincipleResponse] class ValidateRequest(BaseModel): """Request für Textvalidierung.""" text: str = Field(..., description="Der zu validierende Text") class ValidateResponse(BaseModel): """Response für Validierung.""" is_valid: bool issues: List[str] suggestions: List[str] positive_elements: List[str] gfk_score: float # ============================================================================= # Endpoints # ============================================================================= @router.get("/types", response_model=List[CommunicationTypeResponse]) async def get_communication_types(): """ Gibt alle verfügbaren Kommunikationstypen zurück. Returns: Liste aller Kommunikationstypen mit Wert und Label """ service = get_communication_service() return service.get_all_communication_types() @router.get("/tones", response_model=List[ToneResponse]) async def get_tones(): """ Gibt alle verfügbaren Tonalitäten zurück. Returns: Liste aller Tonalitäten mit Wert und Label """ service = get_communication_service() return service.get_all_tones() @router.get("/states", response_model=List[StateResponse]) async def get_states(): """ Gibt alle verfügbaren Bundesländer zurück. Returns: Liste aller Bundesländer mit Wert und Label """ service = get_communication_service() return service.get_states() @router.get("/legal-references/{state}") async def get_legal_references(state: str): """ Gibt rechtliche Referenzen für ein Bundesland zurück. Args: state: Bundesland-Kürzel (z.B. NRW, BY) Returns: Rechtliche Referenzen für das Bundesland """ service = get_communication_service() refs = service.get_legal_references(state, "elternpflichten") return [ LegalReferenceResponse( law=ref.law, paragraph=ref.paragraph, title=ref.title, summary=ref.summary, relevance=ref.relevance ) for ref in refs ] @router.get("/gfk-principles", response_model=List[GFKPrincipleResponse]) async def get_gfk_principles(): """ Gibt die Prinzipien der gewaltfreien Kommunikation zurück. Returns: Liste der GFK-Prinzipien mit Beschreibung und Beispielen """ service = get_communication_service() principles = service.get_gfk_guidance(CommunicationType.GENERAL_INFO) return [ GFKPrincipleResponse( principle=p.principle, description=p.description, example=p.example ) for p in principles ] @router.post("/generate", response_model=GenerateResponse) async def generate_communication(request: GenerateRequest): """ Generiert einen Elternbrief basierend auf dem Kontext. Args: request: GenerateRequest mit allen nötigen Informationen Returns: GenerateResponse mit generiertem Text und Metadaten """ service = get_communication_service() # Kommunikationstyp validieren try: comm_type = CommunicationType(request.communication_type) except ValueError: raise HTTPException( status_code=400, detail=f"Ungültiger Kommunikationstyp: {request.communication_type}" ) # Tonalität validieren try: tone = CommunicationTone(request.tone) except ValueError: tone = CommunicationTone.PROFESSIONAL # System- und User-Prompt erstellen system_prompt = service.build_system_prompt(comm_type, request.state, tone) user_prompt = service.build_user_prompt(comm_type, { "student_name": request.student_name, "parent_name": request.parent_name, "situation": request.situation, "additional_info": request.additional_info, }) # Inference-Service aufrufen try: inference_service = get_inference_service() response = await inference_service.generate( prompt=user_prompt, system_prompt=system_prompt, temperature=0.7, # Etwas kreativ, aber kontrolliert max_tokens=2000, ) generated_message = response.get("content", "") except Exception as e: logger.error(f"Fehler bei der Nachrichtengenerierung: {e}") # Fallback: Vorlage verwenden template = service.get_template(comm_type) generated_message = f"""{template['opening'].format( parent_name=request.parent_name, student_name=request.student_name, topic=request.situation[:50] + '...' if len(request.situation) > 50 else request.situation )} {request.situation} {template['closing'].format( student_name=request.student_name, legal_reference=f"des Schulgesetzes" )}""" # Validierung durchführen validation = service.validate_communication(generated_message) # Rechtliche Referenzen holen topic_map = { CommunicationType.ATTENDANCE: "schulpflicht", CommunicationType.BEHAVIOR: "ordnungsmassnahmen", CommunicationType.ACADEMIC: "foerderung", CommunicationType.SPECIAL_NEEDS: "foerderung", } topic = topic_map.get(comm_type, "elternpflichten") legal_refs = service.get_legal_references(request.state, topic) # GFK-Prinzipien gfk_principles = service.get_gfk_guidance(comm_type) # Betreff generieren template = service.get_template(comm_type) subject = template.get("subject", "Mitteilung der Schule").format( student_name=request.student_name, topic=request.situation[:30] + '...' if len(request.situation) > 30 else request.situation ) return GenerateResponse( message=generated_message, subject=subject, validation=validation, legal_references=[ LegalReferenceResponse( law=ref.law, paragraph=ref.paragraph, title=ref.title, summary=ref.summary, relevance=ref.relevance ) for ref in legal_refs ], gfk_principles=[ GFKPrincipleResponse( principle=p.principle, description=p.description, example=p.example ) for p in gfk_principles ] ) @router.post("/validate", response_model=ValidateResponse) async def validate_communication(request: ValidateRequest): """ Validiert einen Text auf GFK-Konformität. Args: request: ValidateRequest mit dem zu prüfenden Text Returns: ValidateResponse mit Validierungsergebnissen """ service = get_communication_service() result = service.validate_communication(request.text) return ValidateResponse( is_valid=result["is_valid"], issues=result["issues"], suggestions=result["suggestions"], positive_elements=result["positive_elements"], gfk_score=result["gfk_score"] ) @router.post("/improve") async def improve_communication(request: ValidateRequest): """ Verbessert einen bestehenden Text nach GFK-Prinzipien. Args: request: ValidateRequest mit dem zu verbessernden Text Returns: Verbesserter Text mit Änderungsvorschlägen """ service = get_communication_service() # Erst validieren validation = service.validate_communication(request.text) if validation["is_valid"] and validation["gfk_score"] >= 0.8: return { "improved_text": request.text, "changes": [], "was_improved": False, "message": "Der Text entspricht bereits den GFK-Prinzipien." } # System-Prompt für Verbesserung system_prompt = """Du bist ein Experte für gewaltfreie Kommunikation (GFK) nach Marshall Rosenberg. Deine Aufgabe ist es, einen Elternbrief zu verbessern, sodass er den GFK-Prinzipien entspricht. VERBESSERUNGSREGELN: 1. Ersetze Bewertungen durch Beobachtungen 2. Ersetze "Sie müssen/sollten" durch Ich-Botschaften und Bitten 3. Entferne Schuldzuweisungen 4. Füge empathische Elemente hinzu 5. Behalte den sachlichen Inhalt bei Gib den verbesserten Text zurück und erkläre kurz die wichtigsten Änderungen.""" user_prompt = f"""Bitte verbessere folgenden Elternbrief nach den GFK-Prinzipien: --- {request.text} --- Identifizierte Probleme: {', '.join(validation['issues']) if validation['issues'] else 'Keine spezifischen Probleme gefunden, aber GFK-Score könnte verbessert werden.'} Vorschläge: {', '.join(validation['suggestions']) if validation['suggestions'] else 'Allgemeine Verbesserungen möglich.'}""" try: inference_service = get_inference_service() response = await inference_service.generate( prompt=user_prompt, system_prompt=system_prompt, temperature=0.5, max_tokens=2500, ) improved_text = response.get("content", request.text) # Nochmal validieren new_validation = service.validate_communication(improved_text) return { "improved_text": improved_text, "original_issues": validation["issues"], "was_improved": True, "old_score": validation["gfk_score"], "new_score": new_validation["gfk_score"], "remaining_issues": new_validation["issues"], } except Exception as e: logger.error(f"Fehler bei der Textverbesserung: {e}") return { "improved_text": request.text, "changes": [], "was_improved": False, "error": str(e), "message": "Die automatische Verbesserung ist derzeit nicht verfügbar." }