generator client { provider = "prisma-client-js" previewFeatures = ["driverAdapters"] } datasource db { provider = "postgresql" url = env("DATABASE_URL") directUrl = env("DATABASE_URL_DIRECT") } /// * /// * 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()) name String @db.VarChar(255) /// Company name for display and filtering csvUrl String @db.Text /// URL endpoint for CSV data import csvUsername String? @db.VarChar(255) /// Optional HTTP auth username for CSV endpoint csvPassword String? @db.VarChar(255) /// Optional HTTP auth password for CSV endpoint dashboardOpts Json? @db.JsonB /// Company-specific dashboard configuration (theme, layout, etc.) createdAt DateTime @default(now()) @db.Timestamptz(6) updatedAt DateTime @updatedAt @db.Timestamptz(6) companyAiModels CompanyAIModel[] /// AI models assigned to this company sessions Session[] /// All processed sessions for this company imports SessionImport[] /// Raw CSV import data for this company users User[] @relation("CompanyUsers") /// Users belonging to this company @@index([name]) } /// * /// * 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()) email String @unique @db.VarChar(255) /// User email address, must be unique across all companies password String @db.VarChar(255) /// Hashed password for authentication role UserRole @default(USER) /// User permission level within their company companyId String /// Foreign key to Company - enforces data isolation resetToken String? @db.VarChar(255) /// Temporary token for password reset functionality resetTokenExpiry DateTime? @db.Timestamptz(6) /// Expiration time for reset token 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()) companyId String /// Foreign key to Company for data isolation importId String? @unique /// Optional 1:1 link to source SessionImport record /// Session timing and basic data startTime DateTime @db.Timestamptz(6) /// When the conversation started endTime DateTime @db.Timestamptz(6) /// When the conversation ended ipAddress String? @db.Inet /// Client IP address (IPv4/IPv6) country String? @db.VarChar(3) /// ISO 3166-1 alpha-3 country code fullTranscriptUrl String? @db.Text /// URL to external transcript source avgResponseTime Float? @db.Real /// Average response time in seconds initialMsg String? @db.Text /// First message in the conversation language String? @db.VarChar(10) /// ISO 639 language code messagesSent Int? /// Total number of messages in session /// AI-enhanced analysis fields sentiment SentimentCategory? /// AI-determined overall sentiment escalated Boolean? /// Whether session was escalated to human forwardedHr Boolean? /// Whether session was forwarded to HR category SessionCategory? /// AI-determined conversation category summary String? @db.Text /// AI-generated session summary createdAt DateTime @default(now()) @db.Timestamptz(6) updatedAt DateTime @updatedAt @db.Timestamptz(6) /// Related data aiProcessingRequests AIProcessingRequest[] /// All AI API calls made for this session messages Message[] /// Individual messages in conversation order company Company @relation(fields: [companyId], references: [id], onDelete: Cascade) import SessionImport? @relation("ImportToSession", fields: [importId], references: [id]) processingStatus SessionProcessingStatus[] /// Pipeline stage tracking sessionQuestions SessionQuestion[] /// Questions extracted from conversation @@index([companyId, startTime]) /// Primary query pattern: company sessions by time @@index([companyId, sentiment]) /// Filter sessions by sentiment within company @@index([companyId, category]) /// Filter sessions by category within company } /// * /// * 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? @db.Text avgResponseTimeSeconds Float? @db.Real tokens Int? tokensEur Float? @db.Real category String? @db.VarChar(255) initialMessage String? @db.Text rawTranscriptContent String? @db.Text 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 @db.Text 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? @db.Text retryCount Int @default(0) metadata Json? @db.JsonB 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 @db.Text 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? @db.Text 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]) } /// * /// * ENUMS – typed constants for better data integrity /// /// User permission levels within a company enum UserRole { ADMIN /// Full access to company data and settings USER /// Standard access to view and interact with data AUDITOR /// Read-only access for compliance and auditing } /// AI-determined sentiment categories for sessions enum SentimentCategory { POSITIVE /// Customer expressed satisfaction or positive emotions NEUTRAL /// Neutral tone or mixed emotions NEGATIVE /// Customer expressed frustration or negative emotions } /// AI-determined conversation categories based on content analysis enum SessionCategory { SCHEDULE_HOURS /// Questions about work schedules and hours LEAVE_VACATION /// Vacation requests and leave policies SICK_LEAVE_RECOVERY /// Sick leave and recovery-related discussions SALARY_COMPENSATION /// Salary, benefits, and compensation questions CONTRACT_HOURS /// Contract terms and working hours ONBOARDING /// New employee onboarding processes OFFBOARDING /// Employee departure and offboarding WORKWEAR_STAFF_PASS /// Equipment, uniforms, and access cards TEAM_CONTACTS /// Team directory and contact information PERSONAL_QUESTIONS /// Personal HR matters and private concerns ACCESS_LOGIN /// System access and login issues SOCIAL_QUESTIONS /// Social events and company culture UNRECOGNIZED_OTHER /// Conversations that don't fit other categories } /// Processing pipeline stages for session data transformation enum ProcessingStage { CSV_IMPORT /// Initial import of raw CSV data into SessionImport TRANSCRIPT_FETCH /// Fetching transcript content from external URLs SESSION_CREATION /// Converting SessionImport to normalized Session AI_ANALYSIS /// AI processing for sentiment, categorization, summaries QUESTION_EXTRACTION /// Extracting questions from conversation content } /// Status of each processing stage enum ProcessingStatus { PENDING /// Stage is queued for processing IN_PROGRESS /// Stage is currently being processed COMPLETED /// Stage completed successfully FAILED /// Stage failed with errors SKIPPED /// Stage was intentionally skipped }