gateway/modules/features/commcoach/datamodelCommcoach.py
2026-04-26 18:11:42 +02:00

292 lines
12 KiB
Python

# 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[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 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[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")
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[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()))
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[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)
# ============================================================================
# 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 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[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."""
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)