# Copyright (c) 2025 Patrick Motsch # All rights reserved. """ CommCoach Feature - Data Models. Pydantic models for coaching contexts, sessions, messages, tasks, scores, and user profiles. """ from typing import Optional, List, Dict, Any from pydantic import BaseModel, Field from enum import Enum from modules.datamodels.datamodelBase import PowerOnModel import uuid # ============================================================================ # Enums # ============================================================================ class CoachingContextStatus(str, Enum): ACTIVE = "active" PAUSED = "paused" ARCHIVED = "archived" COMPLETED = "completed" class CoachingContextCategory(str, Enum): LEADERSHIP = "leadership" CONFLICT = "conflict" NEGOTIATION = "negotiation" PRESENTATION = "presentation" FEEDBACK = "feedback" DELEGATION = "delegation" CHANGE_MANAGEMENT = "changeManagement" CUSTOM = "custom" class CoachingSessionStatus(str, Enum): ACTIVE = "active" COMPLETED = "completed" CANCELLED = "cancelled" class CoachingMessageRole(str, Enum): USER = "user" ASSISTANT = "assistant" SYSTEM = "system" class CoachingMessageContentType(str, Enum): TEXT = "text" AUDIO_TRANSCRIPT = "audioTranscript" SYSTEM_NOTE = "systemNote" class CoachingTaskStatus(str, Enum): OPEN = "open" IN_PROGRESS = "inProgress" DONE = "done" SKIPPED = "skipped" class CoachingTaskPriority(str, Enum): LOW = "low" MEDIUM = "medium" HIGH = "high" class CoachingScoreTrend(str, Enum): IMPROVING = "improving" STABLE = "stable" DECLINING = "declining" # ============================================================================ # Database Models # ============================================================================ class CoachingContext(PowerOnModel): """A coaching context/dossier representing a topic the user is working on.""" id: str = Field(default_factory=lambda: str(uuid.uuid4())) userId: str = Field(description="Owner user ID (strict ownership)") mandateId: str = Field(description="Mandate ID") instanceId: str = Field(description="Feature instance ID") title: str = Field(description="Context title, e.g. 'Conflict with team lead'") description: Optional[str] = Field(default=None, description="Short description") category: CoachingContextCategory = Field(default=CoachingContextCategory.CUSTOM) status: CoachingContextStatus = Field(default=CoachingContextStatus.ACTIVE) goals: Optional[str] = Field(default=None, description="JSON array of goals [{id, text, status, createdAt}]") insights: Optional[str] = Field(default=None, description="JSON array of AI insights [{id, text, sessionId, createdAt}]") metadata: Optional[str] = Field(default=None, description="JSON object with flexible metadata") sessionCount: int = Field(default=0) taskCount: int = Field(default=0) lastSessionAt: Optional[str] = Field(default=None) rollingOverview: Optional[str] = Field(default=None, description="AI summary of older sessions for long context history") rollingOverviewUpToSessionCount: Optional[int] = Field(default=None, description="Session count covered by rollingOverview") class CoachingSession(PowerOnModel): """A single coaching conversation session within a context.""" id: str = Field(default_factory=lambda: str(uuid.uuid4())) contextId: str = Field(description="FK to CoachingContext") userId: str = Field(description="Owner user ID") mandateId: str = Field(description="Mandate ID") instanceId: str = Field(description="Feature instance ID") status: CoachingSessionStatus = Field(default=CoachingSessionStatus.ACTIVE) personaId: Optional[str] = Field(default=None, description="FK to CoachingPersona (Iteration 2)") summary: Optional[str] = Field(default=None, description="AI-generated session summary") coachNotes: Optional[str] = Field(default=None, description="JSON: AI internal notes for continuity") compressedHistorySummary: Optional[str] = Field(default=None, description="AI summary of older messages for long sessions") compressedHistoryUpToMessageCount: Optional[int] = Field(default=None, description="Message count covered by compressedHistorySummary") keyTopics: Optional[str] = Field(default=None, description="JSON array of key topics extracted at session complete") durationSeconds: int = Field(default=0) messageCount: int = Field(default=0) competenceScore: Optional[float] = Field(default=None, ge=0.0, le=100.0) emailSent: bool = Field(default=False) startedAt: Optional[str] = Field(default=None) endedAt: Optional[str] = Field(default=None) class CoachingMessage(PowerOnModel): """A single message in a coaching session.""" id: str = Field(default_factory=lambda: str(uuid.uuid4())) sessionId: str = Field(description="FK to CoachingSession") contextId: str = Field(description="FK to CoachingContext") userId: str = Field(description="Owner user ID") role: CoachingMessageRole = Field(description="Message author role") content: str = Field(description="Message content (Markdown)") contentType: CoachingMessageContentType = Field(default=CoachingMessageContentType.TEXT) audioRef: Optional[str] = Field(default=None, description="Reference to audio file") metadata: Optional[str] = Field(default=None, description="JSON: token count, voice info, etc.") class CoachingTask(PowerOnModel): """A task/checklist item assigned within a coaching context.""" id: str = Field(default_factory=lambda: str(uuid.uuid4())) contextId: str = Field(description="FK to CoachingContext") sessionId: Optional[str] = Field(default=None, description="FK to originating session") userId: str = Field(description="Owner user ID") mandateId: str = Field(description="Mandate ID") title: str = Field(description="Task title") description: Optional[str] = Field(default=None) status: CoachingTaskStatus = Field(default=CoachingTaskStatus.OPEN) priority: CoachingTaskPriority = Field(default=CoachingTaskPriority.MEDIUM) dueDate: Optional[str] = Field(default=None) completedAt: Optional[str] = Field(default=None) class CoachingScore(PowerOnModel): """A competence score for a dimension, recorded after a session.""" id: str = Field(default_factory=lambda: str(uuid.uuid4())) contextId: str = Field(description="FK to CoachingContext") sessionId: str = Field(description="FK to CoachingSession") userId: str = Field(description="Owner user ID") mandateId: str = Field(description="Mandate ID") dimension: str = Field(description="e.g. empathy, clarity, assertiveness, listening") score: float = Field(ge=0.0, le=100.0) trend: CoachingScoreTrend = Field(default=CoachingScoreTrend.STABLE) evidence: Optional[str] = Field(default=None, description="AI reasoning for the score") class CoachingUserProfile(PowerOnModel): """Per-user coaching profile and preferences.""" id: str = Field(default_factory=lambda: str(uuid.uuid4())) userId: str = Field(description="Owner user ID") mandateId: str = Field(description="Mandate ID") instanceId: str = Field(description="Feature instance ID") dailyReminderTime: Optional[str] = Field(default=None, description="HH:MM format") dailyReminderEnabled: bool = Field(default=False) emailSummaryEnabled: bool = Field(default=True) streakDays: int = Field(default=0) longestStreak: int = Field(default=0) totalSessions: int = Field(default=0) totalMinutes: int = Field(default=0) lastSessionAt: Optional[str] = Field(default=None) # ============================================================================ # Iteration 2: Personas # ============================================================================ class CoachingPersona(PowerOnModel): """A roleplay persona for coaching sessions.""" id: str = Field(default_factory=lambda: str(uuid.uuid4())) userId: str = Field(description="Owner user ID ('system' for builtins)") mandateId: Optional[str] = Field(default=None) instanceId: Optional[str] = Field(default=None) key: str = Field(description="Unique key, e.g. 'critical_cfo_f'") label: str = Field(description="Display label, e.g. 'Kritische CFO'") description: str = Field(description="Detailed role description for the AI") systemPromptOverride: Optional[str] = Field(default=None, description="Full system prompt override for this persona") gender: Optional[str] = Field(default=None, description="m or f") category: str = Field(default="builtin", description="'builtin' or 'custom'") isActive: bool = Field(default=True) # ============================================================================ # Iteration 2: Badges / Gamification # ============================================================================ class CoachingBadge(PowerOnModel): """An achievement badge awarded to a user.""" id: str = Field(default_factory=lambda: str(uuid.uuid4())) userId: str = Field(description="Owner user ID") mandateId: str = Field(description="Mandate ID") instanceId: str = Field(description="Feature instance ID") badgeKey: str = Field(description="Badge identifier, e.g. 'streak_7'") awardedAt: Optional[str] = Field(default=None) # ============================================================================ # API Request/Response Models # ============================================================================ class CreateContextRequest(BaseModel): title: str = Field(description="Context title") description: Optional[str] = None category: Optional[CoachingContextCategory] = CoachingContextCategory.CUSTOM goals: Optional[List[str]] = None class UpdateContextRequest(BaseModel): title: Optional[str] = None description: Optional[str] = None category: Optional[CoachingContextCategory] = None goals: Optional[str] = None class SendMessageRequest(BaseModel): content: str = Field(description="User message text") contentType: Optional[CoachingMessageContentType] = CoachingMessageContentType.TEXT fileIds: Optional[List[str]] = Field(default=None, description="Attached file IDs for agent context") dataSourceIds: Optional[List[str]] = Field(default=None, description="Personal data source IDs") featureDataSourceIds: Optional[List[str]] = Field(default=None, description="Feature data source IDs") allowedProviders: Optional[List[str]] = Field(default=None, description="Allowed AI providers") class CreateTaskRequest(BaseModel): title: str description: Optional[str] = None priority: Optional[CoachingTaskPriority] = CoachingTaskPriority.MEDIUM dueDate: Optional[str] = None class UpdateTaskRequest(BaseModel): title: Optional[str] = None description: Optional[str] = None priority: Optional[CoachingTaskPriority] = None dueDate: Optional[str] = None class UpdateTaskStatusRequest(BaseModel): status: CoachingTaskStatus class UpdateProfileRequest(BaseModel): dailyReminderTime: Optional[str] = None dailyReminderEnabled: Optional[bool] = None emailSummaryEnabled: Optional[bool] = None class StartSessionRequest(BaseModel): personaId: Optional[str] = None class CreatePersonaRequest(BaseModel): label: str description: str gender: Optional[str] = None systemPromptOverride: Optional[str] = None class UpdatePersonaRequest(BaseModel): label: Optional[str] = None description: Optional[str] = None gender: Optional[str] = None systemPromptOverride: Optional[str] = None isActive: Optional[bool] = None class DashboardData(BaseModel): """Aggregated dashboard data for the user.""" totalContexts: int = 0 activeContexts: int = 0 totalSessions: int = 0 totalMinutes: int = 0 streakDays: int = 0 longestStreak: int = 0 averageScore: Optional[float] = None recentScores: List[Dict[str, Any]] = Field(default_factory=list) openTasks: int = 0 completedTasks: int = 0 contexts: List[Dict[str, Any]] = Field(default_factory=list)