Files
Benjamin Boenisch 5a31f52310 Initial commit: breakpilot-lehrer - Lehrer KI Platform
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>
2026-02-11 23:47:26 +01:00

456 lines
15 KiB
Python

"""
Unified Inbox Mail Models
Pydantic models for API requests/responses and internal data structures.
Database schema is defined in mail_db.py.
"""
from datetime import datetime
from enum import Enum
from typing import Optional, List, Dict, Any
from pydantic import BaseModel, Field, EmailStr
import uuid
# =============================================================================
# Enums
# =============================================================================
class AccountStatus(str, Enum):
"""Status of an email account connection."""
ACTIVE = "active"
INACTIVE = "inactive"
ERROR = "error"
PENDING = "pending"
class TaskStatus(str, Enum):
"""Status of an inbox task."""
PENDING = "pending"
IN_PROGRESS = "in_progress"
COMPLETED = "completed"
ARCHIVED = "archived"
class TaskPriority(str, Enum):
"""Priority level for tasks."""
LOW = "low"
MEDIUM = "medium"
HIGH = "high"
URGENT = "urgent"
class EmailCategory(str, Enum):
"""AI-detected email categories."""
DIENSTLICH = "dienstlich" # Official government/authority
PERSONAL = "personal" # Staff/HR matters
FINANZEN = "finanzen" # Finance/budget
ELTERN = "eltern" # Parent communication
SCHUELER = "schueler" # Student matters
KOLLEGIUM = "kollegium" # Teacher colleagues
FORTBILDUNG = "fortbildung" # Professional development
VERANSTALTUNG = "veranstaltung" # Events
SICHERHEIT = "sicherheit" # Safety/security
TECHNIK = "technik" # IT/technical
NEWSLETTER = "newsletter" # Newsletters
WERBUNG = "werbung" # Advertising/spam
SONSTIGES = "sonstiges" # Other
class SenderType(str, Enum):
"""Type of sender for classification."""
KULTUSMINISTERIUM = "kultusministerium"
LANDESSCHULBEHOERDE = "landesschulbehoerde"
RLSB = "rlsb" # Regionales Landesamt für Schule und Bildung
SCHULAMT = "schulamt"
NIBIS = "nibis"
SCHULTRAEGER = "schultraeger"
ELTERNVERTRETER = "elternvertreter"
GEWERKSCHAFT = "gewerkschaft"
FORTBILDUNGSINSTITUT = "fortbildungsinstitut"
PRIVATPERSON = "privatperson"
UNTERNEHMEN = "unternehmen"
UNBEKANNT = "unbekannt"
# =============================================================================
# Known Authority Domains (Niedersachsen)
# =============================================================================
KNOWN_AUTHORITIES_NI = {
"@mk.niedersachsen.de": {"type": SenderType.KULTUSMINISTERIUM, "name": "Kultusministerium Niedersachsen"},
"@rlsb.de": {"type": SenderType.RLSB, "name": "Regionales Landesamt für Schule und Bildung"},
"@rlsb-bs.niedersachsen.de": {"type": SenderType.RLSB, "name": "RLSB Braunschweig"},
"@rlsb-h.niedersachsen.de": {"type": SenderType.RLSB, "name": "RLSB Hannover"},
"@rlsb-lg.niedersachsen.de": {"type": SenderType.RLSB, "name": "RLSB Lüneburg"},
"@rlsb-os.niedersachsen.de": {"type": SenderType.RLSB, "name": "RLSB Osnabrück"},
"@landesschulbehoerde-nds.de": {"type": SenderType.LANDESSCHULBEHOERDE, "name": "Landesschulbehörde"},
"@nibis.de": {"type": SenderType.NIBIS, "name": "NiBiS"},
"@schule.niedersachsen.de": {"type": SenderType.LANDESSCHULBEHOERDE, "name": "Schulnetzwerk NI"},
"@nlq.nibis.de": {"type": SenderType.FORTBILDUNGSINSTITUT, "name": "NLQ"},
"@gew-nds.de": {"type": SenderType.GEWERKSCHAFT, "name": "GEW Niedersachsen"},
"@vbe-nds.de": {"type": SenderType.GEWERKSCHAFT, "name": "VBE Niedersachsen"},
}
# =============================================================================
# Email Account Models
# =============================================================================
class EmailAccountBase(BaseModel):
"""Base model for email account."""
email: EmailStr = Field(..., description="Email address")
display_name: str = Field(..., description="Display name for the account")
account_type: str = Field("personal", description="Type: personal, schulleitung, personal, verwaltung")
imap_host: str = Field(..., description="IMAP server hostname")
imap_port: int = Field(993, description="IMAP port (default: 993 for SSL)")
imap_ssl: bool = Field(True, description="Use SSL for IMAP")
smtp_host: str = Field(..., description="SMTP server hostname")
smtp_port: int = Field(465, description="SMTP port (default: 465 for SSL)")
smtp_ssl: bool = Field(True, description="Use SSL for SMTP")
class EmailAccountCreate(EmailAccountBase):
"""Model for creating a new email account."""
password: str = Field(..., description="Password (will be stored in Vault)")
class EmailAccountUpdate(BaseModel):
"""Model for updating an email account."""
display_name: Optional[str] = None
account_type: Optional[str] = None
imap_host: Optional[str] = None
imap_port: Optional[int] = None
smtp_host: Optional[str] = None
smtp_port: Optional[int] = None
password: Optional[str] = None # Only if changing
class EmailAccount(EmailAccountBase):
"""Full email account model (without password)."""
id: str
user_id: str
tenant_id: str
status: AccountStatus = AccountStatus.PENDING
last_sync: Optional[datetime] = None
sync_error: Optional[str] = None
email_count: int = 0
unread_count: int = 0
created_at: datetime
updated_at: datetime
class Config:
from_attributes = True
class AccountTestResult(BaseModel):
"""Result of testing email account connection."""
success: bool
imap_connected: bool = False
smtp_connected: bool = False
error_message: Optional[str] = None
folders_found: List[str] = []
# =============================================================================
# Aggregated Email Models
# =============================================================================
class AggregatedEmailBase(BaseModel):
"""Base model for an aggregated email."""
subject: str
sender_email: str
sender_name: Optional[str] = None
recipients: List[str] = []
cc: List[str] = []
body_preview: Optional[str] = None
has_attachments: bool = False
class AggregatedEmail(AggregatedEmailBase):
"""Full aggregated email model."""
id: str
account_id: str
message_id: str # Original IMAP message ID
folder: str = "INBOX"
is_read: bool = False
is_starred: bool = False
is_deleted: bool = False
body_text: Optional[str] = None
body_html: Optional[str] = None
attachments: List[Dict[str, Any]] = []
headers: Dict[str, str] = {}
date_sent: datetime
date_received: datetime
# AI-enriched fields
category: Optional[EmailCategory] = None
sender_type: Optional[SenderType] = None
sender_authority_name: Optional[str] = None
detected_deadlines: List[Dict[str, Any]] = []
suggested_priority: Optional[TaskPriority] = None
ai_summary: Optional[str] = None
created_at: datetime
class Config:
from_attributes = True
class EmailSearchParams(BaseModel):
"""Parameters for searching emails."""
query: Optional[str] = None
account_ids: Optional[List[str]] = None
categories: Optional[List[EmailCategory]] = None
is_read: Optional[bool] = None
is_starred: Optional[bool] = None
has_deadline: Optional[bool] = None
date_from: Optional[datetime] = None
date_to: Optional[datetime] = None
limit: int = Field(50, ge=1, le=200)
offset: int = Field(0, ge=0)
# =============================================================================
# Inbox Task Models (Arbeitsvorrat)
# =============================================================================
class TaskBase(BaseModel):
"""Base model for inbox task."""
title: str = Field(..., description="Task title")
description: Optional[str] = None
priority: TaskPriority = TaskPriority.MEDIUM
deadline: Optional[datetime] = None
class TaskCreate(TaskBase):
"""Model for creating a task manually."""
email_id: Optional[str] = None # Link to source email
class TaskUpdate(BaseModel):
"""Model for updating a task."""
title: Optional[str] = None
description: Optional[str] = None
priority: Optional[TaskPriority] = None
status: Optional[TaskStatus] = None
deadline: Optional[datetime] = None
completed_at: Optional[datetime] = None
class InboxTask(TaskBase):
"""Full inbox task model."""
id: str
user_id: str
tenant_id: str
email_id: Optional[str] = None
account_id: Optional[str] = None
status: TaskStatus = TaskStatus.PENDING
# Source information
source_email_subject: Optional[str] = None
source_sender: Optional[str] = None
source_sender_type: Optional[SenderType] = None
# AI-extracted information
ai_extracted: bool = False
confidence_score: Optional[float] = None
completed_at: Optional[datetime] = None
reminder_at: Optional[datetime] = None
created_at: datetime
updated_at: datetime
class Config:
from_attributes = True
class TaskDashboardStats(BaseModel):
"""Dashboard statistics for tasks."""
total_tasks: int = 0
pending_tasks: int = 0
in_progress_tasks: int = 0
completed_tasks: int = 0
overdue_tasks: int = 0
due_today: int = 0
due_this_week: int = 0
by_priority: Dict[str, int] = {}
by_sender_type: Dict[str, int] = {}
# =============================================================================
# AI Analysis Models
# =============================================================================
class SenderClassification(BaseModel):
"""Result of AI sender classification."""
sender_type: SenderType
authority_name: Optional[str] = None
confidence: float = Field(..., ge=0, le=1)
domain_matched: bool = False
ai_classified: bool = False
class DeadlineExtraction(BaseModel):
"""Extracted deadline from email."""
deadline_date: datetime
description: str
confidence: float = Field(..., ge=0, le=1)
source_text: str # Original text containing the deadline
is_firm: bool = True # True for "bis zum", False for "etwa"
class EmailAnalysisResult(BaseModel):
"""Complete AI analysis result for an email."""
email_id: str
category: EmailCategory
category_confidence: float
sender_classification: SenderClassification
deadlines: List[DeadlineExtraction] = []
suggested_priority: TaskPriority
summary: Optional[str] = None
suggested_actions: List[str] = []
auto_create_task: bool = False
class ResponseSuggestion(BaseModel):
"""AI-generated response suggestion."""
template_type: str # "acknowledgment", "request_info", "delegation", etc.
subject: str
body: str
confidence: float
# =============================================================================
# Email Template Models
# =============================================================================
class EmailTemplateBase(BaseModel):
"""Base model for email template."""
name: str
category: str # "acknowledgment", "request", "forwarding", etc.
subject_template: str
body_template: str
variables: List[str] = [] # e.g., ["sender_name", "deadline", "topic"]
class EmailTemplateCreate(EmailTemplateBase):
"""Model for creating a template."""
pass
class EmailTemplate(EmailTemplateBase):
"""Full email template model."""
id: str
user_id: Optional[str] = None # None = system template
tenant_id: Optional[str] = None
is_system: bool = False
usage_count: int = 0
created_at: datetime
updated_at: datetime
class Config:
from_attributes = True
# =============================================================================
# Compose Email Models
# =============================================================================
class EmailComposeRequest(BaseModel):
"""Request to compose/send an email."""
account_id: str = Field(..., description="Account to send from")
to: List[EmailStr]
cc: Optional[List[EmailStr]] = []
bcc: Optional[List[EmailStr]] = []
subject: str
body: str
is_html: bool = False
reply_to_message_id: Optional[str] = None
attachments: Optional[List[Dict[str, Any]]] = None
class EmailSendResult(BaseModel):
"""Result of sending an email."""
success: bool
message_id: Optional[str] = None
error: Optional[str] = None
# =============================================================================
# Statistics & Health Models
# =============================================================================
class MailStats(BaseModel):
"""Overall mail system statistics."""
total_accounts: int = 0
active_accounts: int = 0
error_accounts: int = 0
total_emails: int = 0
unread_emails: int = 0
total_tasks: int = 0
pending_tasks: int = 0
overdue_tasks: int = 0
emails_today: int = 0
ai_analyses_today: int = 0
per_account: List[Dict[str, Any]] = []
class MailHealthCheck(BaseModel):
"""Health check for mail system."""
status: str # "healthy", "degraded", "unhealthy"
database_connected: bool = False
vault_connected: bool = False
accounts_checked: int = 0
accounts_healthy: int = 0
last_sync: Optional[datetime] = None
errors: List[str] = []
# =============================================================================
# Helper Functions
# =============================================================================
def generate_id() -> str:
"""Generate a new UUID."""
return str(uuid.uuid4())
def classify_sender_by_domain(email: str) -> Optional[SenderClassification]:
"""
Classify sender by known authority domains.
Returns None if domain is not recognized.
"""
email_lower = email.lower()
for domain, info in KNOWN_AUTHORITIES_NI.items():
if domain in email_lower:
return SenderClassification(
sender_type=info["type"],
authority_name=info["name"],
confidence=0.95,
domain_matched=True,
ai_classified=False,
)
return None
def get_priority_from_sender_type(sender_type: SenderType) -> TaskPriority:
"""Get suggested priority based on sender type."""
high_priority = {
SenderType.KULTUSMINISTERIUM,
SenderType.LANDESSCHULBEHOERDE,
SenderType.RLSB,
SenderType.SCHULAMT,
}
medium_priority = {
SenderType.NIBIS,
SenderType.SCHULTRAEGER,
SenderType.ELTERNVERTRETER,
}
if sender_type in high_priority:
return TaskPriority.HIGH
elif sender_type in medium_priority:
return TaskPriority.MEDIUM
return TaskPriority.LOW