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) /// Relations auditLogs SecurityAuditLog[] @@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) /// Maximum number of users allowed for this company maxUsers Int @default(10) companyAiModels CompanyAIModel[] sessions Session[] imports SessionImport[] users User[] @relation("CompanyUsers") aiBatchRequests AIBatchRequest[] auditLogs SecurityAuditLog[] @@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) /// Display name for the user name String? @db.VarChar(255) /// When this user was invited invitedAt DateTime? @db.Timestamptz(6) /// Email of the user who invited this user (for audit trail) invitedBy String? @db.VarChar(255) company Company @relation("CompanyUsers", fields: [companyId], references: [id], onDelete: Cascade) auditLogs SecurityAuditLog[] @@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]) @@index([companyId, escalated]) @@index([companyId, forwardedHr]) @@index([companyId, language]) @@index([companyId, messagesSent]) @@index([companyId, avgResponseTime]) } /// * /// * 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]) @@index([sessionId, role]) } /// * /// * 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 BATCH REQUEST TRACKING (OpenAI Batch API) /// * Tracks batch jobs submitted to OpenAI for cost-efficient processing model AIBatchRequest { id String @id @default(cuid()) companyId String company Company @relation(fields: [companyId], references: [id]) /// OpenAI specific IDs openaiBatchId String @unique inputFileId String outputFileId String? errorFileId String? /// Our internal status tracking status AIBatchRequestStatus @default(PENDING) /// Timestamps createdAt DateTime @default(now()) @db.Timestamptz(6) completedAt DateTime? @db.Timestamptz(6) processedAt DateTime? @db.Timestamptz(6) // When we finished processing the results /// Relation to the individual requests included in this batch processingRequests AIProcessingRequest[] @@index([companyId, status]) } /// * /// * 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) /// === NEW BATCH API FIELDS === processingStatus AIRequestStatus @default(PENDING_BATCHING) batchId String? batch AIBatchRequest? @relation(fields: [batchId], references: [id]) session Session @relation(fields: [sessionId], references: [id], onDelete: Cascade) @@index([sessionId]) @@index([sessionId, requestedAt]) @@index([requestedAt]) @@index([model]) @@index([success, requestedAt]) @@index([processingStatus]) // Add this index for efficient querying @@index([processingStatus, requestedAt]) // Optimize time-based status queries @@index([batchId]) // Optimize batch-related queries @@index([processingStatus, batchId]) // Composite index for batch status filtering } /// * /// * 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 } /// OpenAI Batch Request Status enum AIBatchRequestStatus { /// Batch request created, waiting to upload file to OpenAI PENDING /// Currently uploading file to OpenAI UPLOADING /// OpenAI is validating the uploaded file VALIDATING /// Batch is queued or running on OpenAI's side IN_PROGRESS /// OpenAI is finalizing the batch results FINALIZING /// Batch completed on OpenAI, ready for processing COMPLETED /// We have processed the results from the completed batch PROCESSED /// Batch failed during processing FAILED /// Batch was cancelled CANCELLED } /// Status for individual AI processing requests within a batch enum AIRequestStatus { /// Request is waiting to be included in a batch PENDING_BATCHING /// Request is currently part of a batch being processed BATCHING_IN_PROGRESS /// Processing completed successfully PROCESSING_COMPLETE /// Processing failed PROCESSING_FAILED } /// * /// * SECURITY AUDIT LOG (comprehensive security event tracking) /// * Tracks all security-critical events for compliance and incident investigation /// * Immutable records with structured metadata for analysis model SecurityAuditLog { id String @id @default(uuid()) /// Event category for filtering and analysis eventType SecurityEventType /// High-level action description action String @db.VarChar(255) /// Detailed event outcome (success, failure, blocked) outcome AuditOutcome /// User who performed the action (if authenticated) userId String? /// Company context for multi-tenant filtering companyId String? /// Platform user who performed the action (for admin events) platformUserId String? /// Client IP address for geographic analysis ipAddress String? @db.Inet /// User agent string for device/browser analysis userAgent String? /// ISO 3166-1 alpha-3 country code derived from IP country String? @db.VarChar(3) /// Structured metadata with additional context metadata Json? /// Error message if action failed errorMessage String? /// Severity level for alerting and prioritization severity AuditSeverity @default(INFO) /// Session ID for correlation with user sessions sessionId String? @db.VarChar(255) /// Request ID for tracing across system boundaries requestId String? @db.VarChar(255) /// Immutable timestamp for chronological ordering timestamp DateTime @default(now()) @db.Timestamptz(6) /// Relations user User? @relation(fields: [userId], references: [id]) company Company? @relation(fields: [companyId], references: [id]) platformUser PlatformUser? @relation(fields: [platformUserId], references: [id]) @@index([eventType, timestamp]) @@index([companyId, eventType, timestamp]) @@index([userId, timestamp]) @@index([platformUserId, timestamp]) @@index([outcome, severity, timestamp]) @@index([ipAddress, timestamp]) @@index([timestamp]) @@index([sessionId]) @@index([requestId]) } /// Security event categories for audit logging enum SecurityEventType { /// Authentication events (login, logout, password changes) AUTHENTICATION /// Authorization events (permission checks, access denied) AUTHORIZATION /// User management events (create, update, delete, invite) USER_MANAGEMENT /// Company management events (create, suspend, settings changes) COMPANY_MANAGEMENT /// Rate limiting and abuse prevention RATE_LIMITING /// CSRF protection violations CSRF_PROTECTION /// Security header violations SECURITY_HEADERS /// Password reset flows PASSWORD_RESET /// Platform admin activities PLATFORM_ADMIN /// Data export and privacy events DATA_PRIVACY /// System configuration changes SYSTEM_CONFIG /// API security events API_SECURITY } /// Outcome classification for audit events enum AuditOutcome { /// Action completed successfully SUCCESS /// Action failed due to user error or invalid input FAILURE /// Action was blocked by security controls BLOCKED /// Action triggered rate limiting RATE_LIMITED /// Action was suspicious but not blocked SUSPICIOUS } /// Severity levels for audit events enum AuditSeverity { /// Informational events for compliance tracking INFO /// Low-impact security events LOW /// Medium-impact security events requiring attention MEDIUM /// High-impact security events requiring immediate attention HIGH /// Critical security events requiring urgent response CRITICAL }