gateway/modules/features/commcoach/datamodelCommcoach.py
2026-05-06 23:28:22 +02:00

310 lines
13 KiB
Python

# Copyright (c) 2025 Patrick Motsch
# All rights reserved.
"""
CommCoach Feature - Data Models.
Pydantic models for training modules, 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 TrainingModuleStatus(str, Enum):
ACTIVE = "active"
PAUSED = "paused"
ARCHIVED = "archived"
COMPLETED = "completed"
class TrainingModuleType(str, Enum):
COACHING = "coaching"
TRAINING = "training"
EXAM = "exam"
ELEARNING = "elearning"
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 TrainingModule(PowerOnModel):
"""A training module 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="Module title, e.g. 'Conflict with team lead'")
description: Optional[str] = Field(default=None, description="Short description")
moduleType: TrainingModuleType = Field(default=TrainingModuleType.COACHING)
status: TrainingModuleStatus = Field(default=TrainingModuleStatus.ACTIVE)
goals: Optional[str] = Field(default=None, description="Free-text goal description")
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")
personaId: Optional[str] = Field(default=None, description="Default persona for sessions")
kpiTargets: Optional[str] = Field(default=None, description="JSON object with structured KPI targets")
sessionCount: int = Field(default=0)
taskCount: int = Field(default=0)
lastSessionAt: Optional[float] = Field(default=None, json_schema_extra={"frontend_type": "timestamp"})
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 module."""
id: str = Field(default_factory=lambda: str(uuid.uuid4()))
moduleId: str = Field(description="FK to TrainingModule")
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[float] = Field(default=None, json_schema_extra={"frontend_type": "timestamp"})
endedAt: Optional[float] = Field(default=None, json_schema_extra={"frontend_type": "timestamp"})
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")
moduleId: str = Field(description="FK to TrainingModule")
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 training module."""
id: str = Field(default_factory=lambda: str(uuid.uuid4()))
moduleId: str = Field(description="FK to TrainingModule")
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[float] = Field(default=None, json_schema_extra={"frontend_type": "date"})
completedAt: Optional[float] = Field(default=None, json_schema_extra={"frontend_type": "timestamp"})
class CoachingScore(PowerOnModel):
"""A competence score for a dimension, recorded after a session."""
id: str = Field(default_factory=lambda: str(uuid.uuid4()))
moduleId: str = Field(description="FK to TrainingModule")
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[float] = Field(default=None, json_schema_extra={"frontend_type": "timestamp"})
# ============================================================================
# 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)
# ============================================================================
# Module-Persona Mapping (M:N)
# ============================================================================
class ModulePersonaMapping(PowerOnModel):
"""Maps which personas are available for a specific training module."""
id: str = Field(default_factory=lambda: str(uuid.uuid4()))
moduleId: str = Field(description="FK to TrainingModule")
personaId: str = Field(description="FK to CoachingPersona")
instanceId: str = Field(description="Feature instance ID")
class SetModulePersonasRequest(BaseModel):
personaIds: List[str] = Field(description="List of persona IDs to assign to this module")
# ============================================================================
# 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[float] = Field(default=None, json_schema_extra={"frontend_type": "timestamp"})
# ============================================================================
# API Request/Response Models
# ============================================================================
class CreateModuleRequest(BaseModel):
title: str = Field(description="Module title")
description: Optional[str] = None
moduleType: Optional[TrainingModuleType] = TrainingModuleType.COACHING
goals: Optional[str] = None
personaId: Optional[str] = None
kpiTargets: Optional[str] = None
class UpdateModuleRequest(BaseModel):
title: Optional[str] = None
description: Optional[str] = None
moduleType: Optional[TrainingModuleType] = None
goals: Optional[str] = None
personaId: Optional[str] = None
kpiTargets: 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[float] = None
class UpdateTaskRequest(BaseModel):
title: Optional[str] = None
description: Optional[str] = None
priority: Optional[CoachingTaskPriority] = None
dueDate: Optional[float] = 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."""
totalModules: int = 0
activeModules: 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
modules: List[Dict[str, Any]] = Field(default_factory=list)