generator client { provider = "prisma-client-js" previewFeatures = ["driverAdapters"] } datasource db { provider = "postgresql" url = env("DATABASE_URL") directUrl = env("DATABASE_URL_DIRECT") } /// * /// * PLATFORM USER (super-admin for Notso AI) /// * Platform-level users who can manage companies and platform-wide settings /// * Separate from Company users for platform management isolation model PlatformUser { id String @id @default(uuid()) /// Platform user email address email String @unique @db.VarChar(255) /// Hashed password for platform authentication password String @db.VarChar(255) /// Platform permission level role PlatformUserRole @default(ADMIN) /// Display name for platform user name String @db.VarChar(255) createdAt DateTime @default(now()) @db.Timestamptz(6) updatedAt DateTime @updatedAt @db.Timestamptz(6) @@index([email]) } /// * /// * COMPANY (multi-tenant root) /// * Root entity for multi-tenant architecture /// * Each company has isolated data with own users, sessions, and AI model configurations model Company { id String @id @default(uuid()) /// Company name for display and filtering name String @db.VarChar(255) /// Company status for suspension/activation status CompanyStatus @default(ACTIVE) /// URL endpoint for CSV data import csvUrl String /// Optional HTTP auth username for CSV endpoint csvUsername String? @db.VarChar(255) /// Optional HTTP auth password for CSV endpoint csvPassword String? @db.VarChar(255) /// Company-specific dashboard configuration (theme, layout, etc.) dashboardOpts Json? createdAt DateTime @default(now()) @db.Timestamptz(6) updatedAt DateTime @updatedAt @db.Timestamptz(6) companyAiModels CompanyAIModel[] sessions Session[] imports SessionImport[] users User[] @relation("CompanyUsers") @@index([name]) @@index([status]) } /// * /// * USER (authentication accounts) /// * Application users with role-based access control /// * Each user belongs to exactly one company for data isolation model User { id String @id @default(uuid()) /// User email address, must be unique across all companies email String @unique @db.VarChar(255) /// Hashed password for authentication password String @db.VarChar(255) /// User permission level within their company role UserRole @default(USER) /// Foreign key to Company - enforces data isolation companyId String /// Temporary token for password reset functionality resetToken String? @db.VarChar(255) /// Expiration time for reset token resetTokenExpiry DateTime? @db.Timestamptz(6) createdAt DateTime @default(now()) @db.Timestamptz(6) updatedAt DateTime @updatedAt @db.Timestamptz(6) company Company @relation("CompanyUsers", fields: [companyId], references: [id], onDelete: Cascade) @@index([companyId]) @@index([email]) } /// * /// * SESSION (processed conversation data) /// * Normalized session data derived from raw CSV imports /// * Contains AI-enhanced data like sentiment analysis and categorization /// * 1:1 relationship with SessionImport via importId model Session { id String @id @default(uuid()) /// Foreign key to Company for data isolation companyId String /// Optional 1:1 link to source SessionImport record importId String? @unique /// Session timing and basic data /// When the conversation started startTime DateTime @db.Timestamptz(6) /// When the conversation ended endTime DateTime @db.Timestamptz(6) /// Client IP address (IPv4/IPv6) ipAddress String? @db.Inet /// ISO 3166-1 alpha-3 country code country String? @db.VarChar(3) /// URL to external transcript source fullTranscriptUrl String? /// Average response time in seconds avgResponseTime Float? @db.Real /// First message in the conversation initialMsg String? /// ISO 639 language code language String? @db.VarChar(10) /// Total number of messages in session messagesSent Int? /// AI-enhanced analysis fields /// AI-determined overall sentiment sentiment SentimentCategory? /// Whether session was escalated to human escalated Boolean? /// Whether session was forwarded to HR forwardedHr Boolean? /// AI-determined conversation category category SessionCategory? /// AI-generated session summary summary String? createdAt DateTime @default(now()) @db.Timestamptz(6) updatedAt DateTime @updatedAt @db.Timestamptz(6) aiProcessingRequests AIProcessingRequest[] messages Message[] company Company @relation(fields: [companyId], references: [id], onDelete: Cascade) import SessionImport? @relation("ImportToSession", fields: [importId], references: [id]) processingStatus SessionProcessingStatus[] sessionQuestions SessionQuestion[] @@index([companyId, startTime]) @@index([companyId, sentiment]) @@index([companyId, category]) } /// * /// * 2. Raw CSV row (pure data storage) ---------- model SessionImport { id String @id @default(uuid()) companyId String externalSessionId String startTimeRaw String @db.VarChar(255) endTimeRaw String @db.VarChar(255) ipAddress String? @db.VarChar(45) countryCode String? @db.VarChar(3) language String? @db.VarChar(10) messagesSent Int? sentimentRaw String? @db.VarChar(50) escalatedRaw String? @db.VarChar(50) forwardedHrRaw String? @db.VarChar(50) fullTranscriptUrl String? avgResponseTimeSeconds Float? @db.Real tokens Int? tokensEur Float? @db.Real category String? @db.VarChar(255) initialMessage String? rawTranscriptContent String? createdAt DateTime @default(now()) @db.Timestamptz(6) session Session? @relation("ImportToSession") company Company @relation(fields: [companyId], references: [id], onDelete: Cascade) @@unique([companyId, externalSessionId]) @@index([companyId]) @@index([companyId, createdAt]) } /// * /// * MESSAGE (individual lines) model Message { id String @id @default(uuid()) sessionId String timestamp DateTime? @db.Timestamptz(6) role String @db.VarChar(50) content String order Int createdAt DateTime @default(now()) @db.Timestamptz(6) session Session @relation(fields: [sessionId], references: [id], onDelete: Cascade) @@unique([sessionId, order]) @@index([sessionId, order]) @@index([sessionId, timestamp]) } /// * /// * UNIFIED PROCESSING STATUS TRACKING model SessionProcessingStatus { id String @id @default(uuid()) sessionId String stage ProcessingStage status ProcessingStatus @default(PENDING) startedAt DateTime? @db.Timestamptz(6) completedAt DateTime? @db.Timestamptz(6) errorMessage String? retryCount Int @default(0) metadata Json? session Session @relation(fields: [sessionId], references: [id], onDelete: Cascade) @@unique([sessionId, stage]) @@index([stage, status]) @@index([sessionId]) @@index([status, startedAt]) } /// * /// * QUESTION MANAGEMENT (separate from Session for better analytics) model Question { id String @id @default(uuid()) content String @unique createdAt DateTime @default(now()) @db.Timestamptz(6) sessionQuestions SessionQuestion[] } model SessionQuestion { id String @id @default(uuid()) sessionId String questionId String order Int createdAt DateTime @default(now()) @db.Timestamptz(6) question Question @relation(fields: [questionId], references: [id]) session Session @relation(fields: [sessionId], references: [id], onDelete: Cascade) @@unique([sessionId, questionId]) @@unique([sessionId, order]) @@index([sessionId]) @@index([questionId]) } /// * /// * AI PROCESSING COST TRACKING model AIProcessingRequest { id String @id @default(uuid()) sessionId String openaiRequestId String? @db.VarChar(255) model String @db.VarChar(100) serviceTier String? @db.VarChar(50) systemFingerprint String? @db.VarChar(255) promptTokens Int completionTokens Int totalTokens Int cachedTokens Int? audioTokensPrompt Int? reasoningTokens Int? audioTokensCompletion Int? acceptedPredictionTokens Int? rejectedPredictionTokens Int? promptTokenCost Float @db.Real completionTokenCost Float @db.Real totalCostEur Float @db.Real processingType String @db.VarChar(100) success Boolean errorMessage String? requestedAt DateTime @default(now()) @db.Timestamptz(6) completedAt DateTime? @db.Timestamptz(6) session Session @relation(fields: [sessionId], references: [id], onDelete: Cascade) @@index([sessionId]) @@index([sessionId, requestedAt]) @@index([requestedAt]) @@index([model]) @@index([success, requestedAt]) } /// * /// * AI Model definitions (without pricing) model AIModel { id String @id @default(uuid()) name String @unique @db.VarChar(100) provider String @db.VarChar(50) maxTokens Int? isActive Boolean @default(true) createdAt DateTime @default(now()) @db.Timestamptz(6) updatedAt DateTime @updatedAt @db.Timestamptz(6) pricing AIModelPricing[] companyModels CompanyAIModel[] @@index([provider, isActive]) @@index([name]) } /// * /// * Time-based pricing for AI models model AIModelPricing { id String @id @default(uuid()) aiModelId String promptTokenCost Float @db.Real completionTokenCost Float @db.Real effectiveFrom DateTime @db.Timestamptz(6) effectiveUntil DateTime? @db.Timestamptz(6) createdAt DateTime @default(now()) @db.Timestamptz(6) aiModel AIModel @relation(fields: [aiModelId], references: [id], onDelete: Cascade) @@index([aiModelId, effectiveFrom]) @@index([effectiveFrom, effectiveUntil]) } /// * /// * Company-specific AI model assignments model CompanyAIModel { id String @id @default(uuid()) companyId String aiModelId String isDefault Boolean @default(false) createdAt DateTime @default(now()) @db.Timestamptz(6) aiModel AIModel @relation(fields: [aiModelId], references: [id], onDelete: Cascade) company Company @relation(fields: [companyId], references: [id], onDelete: Cascade) @@unique([companyId, aiModelId]) @@index([companyId, isDefault]) } /// Platform-level user roles for Notso AI team enum PlatformUserRole { /// Full platform access, can create/suspend companies SUPER_ADMIN /// Platform administration, company management ADMIN /// Customer support access, read-only company access SUPPORT } /// User permission levels within a company enum UserRole { /// Full access to company data and settings ADMIN /// Standard access to view and interact with data USER /// Read-only access for compliance and auditing AUDITOR } /// Company operational status enum CompanyStatus { /// Company is operational and can access all features ACTIVE /// Company access is temporarily disabled SUSPENDED /// Company is in trial period with potential limitations TRIAL /// Company is archived and data is read-only ARCHIVED } /// AI-determined sentiment categories for sessions enum SentimentCategory { /// Customer expressed satisfaction or positive emotions POSITIVE /// Neutral tone or mixed emotions NEUTRAL /// Customer expressed frustration or negative emotions NEGATIVE } /// AI-determined conversation categories based on content analysis enum SessionCategory { /// Questions about work schedules and hours SCHEDULE_HOURS /// Vacation requests and leave policies LEAVE_VACATION /// Sick leave and recovery-related discussions SICK_LEAVE_RECOVERY /// Salary, benefits, and compensation questions SALARY_COMPENSATION /// Contract terms and working hours CONTRACT_HOURS /// New employee onboarding processes ONBOARDING /// Employee departure and offboarding OFFBOARDING /// Equipment, uniforms, and access cards WORKWEAR_STAFF_PASS /// Team directory and contact information TEAM_CONTACTS /// Personal HR matters and private concerns PERSONAL_QUESTIONS /// System access and login issues ACCESS_LOGIN /// Social events and company culture SOCIAL_QUESTIONS /// Conversations that don't fit other categories UNRECOGNIZED_OTHER } /// Processing pipeline stages for session data transformation enum ProcessingStage { /// Initial import of raw CSV data into SessionImport CSV_IMPORT /// Fetching transcript content from external URLs TRANSCRIPT_FETCH /// Converting SessionImport to normalized Session SESSION_CREATION /// AI processing for sentiment, categorization, summaries AI_ANALYSIS /// Extracting questions from conversation content QUESTION_EXTRACTION } /// Status of each processing stage enum ProcessingStatus { /// Stage is queued for processing PENDING /// Stage is currently being processed IN_PROGRESS /// Stage completed successfully COMPLETED /// Stage failed with errors FAILED /// Stage was intentionally skipped SKIPPED }