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:
403
backend/llm_gateway/routes/communication.py
Normal file
403
backend/llm_gateway/routes/communication.py
Normal file
@@ -0,0 +1,403 @@
|
||||
"""
|
||||
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."
|
||||
}
|
||||
Reference in New Issue
Block a user