Services: Admin-Lehrer, Backend-Lehrer, Studio v2, Website, Klausur-Service, School-Service, Voice-Service, Geo-Service, BreakPilot Drive, Agent-Core Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
404 lines
12 KiB
Python
404 lines
12 KiB
Python
"""
|
|
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."
|
|
}
|