""" 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