website (17 pages + 3 components): - multiplayer/wizard, middleware/wizard+test-wizard, communication - builds/wizard, staff-search, voice, sbom/wizard - foerderantrag, mail/tasks, tools/communication, sbom - compliance/evidence, uni-crawler, brandbook (already done) - CollectionsTab, IngestionTab, RiskHeatmap backend-lehrer (5 files): - letters_api (641 → 2), certificates_api (636 → 2) - alerts_agent/db/models (636 → 3) - llm_gateway/communication_service (614 → 2) - game/database already done in prior batch klausur-service (2 files): - hybrid_vocab_extractor (664 → 2) - klausur-service/frontend: api.ts (620 → 3), EHUploadWizard (591 → 2) voice-service (3 files): - bqas/rag_judge (618 → 3), runner (529 → 2) - enhanced_task_orchestrator (519 → 2) studio-v2 (6 files): - korrektur/[klausurId] (578 → 4), fairness (569 → 2) - AlertsWizard (552 → 2), OnboardingWizard (513 → 2) - korrektur/api.ts (506 → 3), geo-lernwelt (501 → 2) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
196 lines
6.2 KiB
Python
196 lines
6.2 KiB
Python
"""
|
|
Letters Models - Pydantic models and enums for Elternbrief-Verwaltung.
|
|
"""
|
|
from datetime import datetime
|
|
from typing import Optional, List
|
|
from enum import Enum
|
|
|
|
from pydantic import BaseModel, Field
|
|
|
|
|
|
# =============================================================================
|
|
# Enums
|
|
# =============================================================================
|
|
|
|
class LetterType(str, Enum):
|
|
"""Typen von Elternbriefen."""
|
|
GENERAL = "general"
|
|
HALBJAHR = "halbjahr"
|
|
FEHLZEITEN = "fehlzeiten"
|
|
ELTERNABEND = "elternabend"
|
|
LOB = "lob"
|
|
CUSTOM = "custom"
|
|
|
|
|
|
class LetterTone(str, Enum):
|
|
"""Tonalitaet der Briefe."""
|
|
FORMAL = "formal"
|
|
PROFESSIONAL = "professional"
|
|
WARM = "warm"
|
|
CONCERNED = "concerned"
|
|
APPRECIATIVE = "appreciative"
|
|
|
|
|
|
class LetterStatus(str, Enum):
|
|
"""Status eines Briefes."""
|
|
DRAFT = "draft"
|
|
SENT = "sent"
|
|
ARCHIVED = "archived"
|
|
|
|
|
|
# =============================================================================
|
|
# Pydantic Models
|
|
# =============================================================================
|
|
|
|
class SchoolInfoModel(BaseModel):
|
|
"""Schulinformationen fuer Briefkopf."""
|
|
name: str
|
|
address: str
|
|
phone: str
|
|
email: str
|
|
website: Optional[str] = None
|
|
principal: Optional[str] = None
|
|
logo_path: Optional[str] = None
|
|
|
|
|
|
class LegalReferenceModel(BaseModel):
|
|
"""Rechtliche Referenz."""
|
|
law: str
|
|
paragraph: str
|
|
title: str
|
|
summary: Optional[str] = None
|
|
relevance: Optional[str] = None
|
|
|
|
|
|
class LetterCreateRequest(BaseModel):
|
|
"""Request zum Erstellen eines neuen Briefes."""
|
|
recipient_name: str = Field(..., description="Name des Empfaengers")
|
|
recipient_address: str = Field(..., description="Adresse des Empfaengers")
|
|
student_name: str = Field(..., description="Name des Schuelers")
|
|
student_class: str = Field(..., description="Klasse des Schuelers")
|
|
subject: str = Field(..., description="Betreff des Briefes")
|
|
content: str = Field(..., description="Inhalt des Briefes")
|
|
letter_type: LetterType = Field(LetterType.GENERAL, description="Art des Briefes")
|
|
tone: LetterTone = Field(LetterTone.PROFESSIONAL, description="Tonalitaet des Briefes")
|
|
teacher_name: str = Field(..., description="Name des Lehrers")
|
|
teacher_title: Optional[str] = Field(None, description="Titel des Lehrers")
|
|
school_info: Optional[SchoolInfoModel] = Field(None, description="Schulinformationen")
|
|
legal_references: Optional[List[LegalReferenceModel]] = Field(None, description="Rechtliche Referenzen")
|
|
gfk_principles_applied: Optional[List[str]] = Field(None, description="Angewandte GFK-Prinzipien")
|
|
|
|
|
|
class LetterUpdateRequest(BaseModel):
|
|
"""Request zum Aktualisieren eines Briefes."""
|
|
recipient_name: Optional[str] = None
|
|
recipient_address: Optional[str] = None
|
|
student_name: Optional[str] = None
|
|
student_class: Optional[str] = None
|
|
subject: Optional[str] = None
|
|
content: Optional[str] = None
|
|
letter_type: Optional[LetterType] = None
|
|
tone: Optional[LetterTone] = None
|
|
teacher_name: Optional[str] = None
|
|
teacher_title: Optional[str] = None
|
|
school_info: Optional[SchoolInfoModel] = None
|
|
legal_references: Optional[List[LegalReferenceModel]] = None
|
|
gfk_principles_applied: Optional[List[str]] = None
|
|
status: Optional[LetterStatus] = None
|
|
|
|
|
|
class LetterResponse(BaseModel):
|
|
"""Response mit Briefdaten."""
|
|
id: str
|
|
recipient_name: str
|
|
recipient_address: str
|
|
student_name: str
|
|
student_class: str
|
|
subject: str
|
|
content: str
|
|
letter_type: LetterType
|
|
tone: LetterTone
|
|
teacher_name: str
|
|
teacher_title: Optional[str]
|
|
school_info: Optional[SchoolInfoModel]
|
|
legal_references: Optional[List[LegalReferenceModel]]
|
|
gfk_principles_applied: Optional[List[str]]
|
|
gfk_score: Optional[float]
|
|
status: LetterStatus
|
|
pdf_path: Optional[str]
|
|
dsms_cid: Optional[str]
|
|
sent_at: Optional[datetime]
|
|
created_at: datetime
|
|
updated_at: datetime
|
|
|
|
|
|
class LetterListResponse(BaseModel):
|
|
"""Response mit Liste von Briefen."""
|
|
letters: List[LetterResponse]
|
|
total: int
|
|
page: int
|
|
page_size: int
|
|
|
|
|
|
class ExportPDFRequest(BaseModel):
|
|
"""Request zum PDF-Export."""
|
|
letter_id: Optional[str] = Field(None, description="ID eines gespeicherten Briefes")
|
|
letter_data: Optional[LetterCreateRequest] = Field(None, description="Oder direkte Briefdaten")
|
|
|
|
|
|
class ImproveRequest(BaseModel):
|
|
"""Request zur GFK-Verbesserung."""
|
|
content: str = Field(..., description="Text zur Verbesserung")
|
|
communication_type: Optional[str] = Field("general_info", description="Art der Kommunikation")
|
|
tone: Optional[str] = Field("professional", description="Gewuenschte Tonalitaet")
|
|
|
|
|
|
class ImproveResponse(BaseModel):
|
|
"""Response mit verbessertem Text."""
|
|
improved_content: str
|
|
changes: List[str]
|
|
gfk_score: float
|
|
gfk_principles_applied: List[str]
|
|
|
|
|
|
class SendEmailRequest(BaseModel):
|
|
"""Request zum Email-Versand."""
|
|
letter_id: str
|
|
recipient_email: str
|
|
cc_emails: Optional[List[str]] = None
|
|
include_pdf: bool = True
|
|
|
|
|
|
class SendEmailResponse(BaseModel):
|
|
"""Response nach Email-Versand."""
|
|
success: bool
|
|
message: str
|
|
sent_at: Optional[datetime]
|
|
|
|
|
|
# =============================================================================
|
|
# Helper Functions
|
|
# =============================================================================
|
|
|
|
def get_type_label(letter_type: LetterType) -> str:
|
|
"""Gibt menschenlesbare Labels fuer Brieftypen zurueck."""
|
|
labels = {
|
|
LetterType.GENERAL: "Allgemeine Information",
|
|
LetterType.HALBJAHR: "Halbjahresinformation",
|
|
LetterType.FEHLZEITEN: "Fehlzeiten-Mitteilung",
|
|
LetterType.ELTERNABEND: "Einladung Elternabend",
|
|
LetterType.LOB: "Positives Feedback",
|
|
LetterType.CUSTOM: "Benutzerdefiniert",
|
|
}
|
|
return labels.get(letter_type, letter_type.value)
|
|
|
|
|
|
def get_tone_label(tone: LetterTone) -> str:
|
|
"""Gibt menschenlesbare Labels fuer Tonalitaeten zurueck."""
|
|
labels = {
|
|
LetterTone.FORMAL: "Sehr foermlich",
|
|
LetterTone.PROFESSIONAL: "Professionell-freundlich",
|
|
LetterTone.WARM: "Warmherzig",
|
|
LetterTone.CONCERNED: "Besorgt",
|
|
LetterTone.APPRECIATIVE: "Wertschaetzend",
|
|
}
|
|
return labels.get(tone, tone.value)
|