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) model Company { id String @id @default(uuid()) name String csvUrl String csvUsername String? csvPassword String? sentimentAlert Float? dashboardOpts Json? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt companyAiModels CompanyAIModel[] sessions Session[] imports SessionImport[] users User[] @relation("CompanyUsers") } /// * /// * USER (auth accounts) model User { id String @id @default(uuid()) email String @unique password String role UserRole @default(USER) companyId String resetToken String? resetTokenExpiry DateTime? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt company Company @relation("CompanyUsers", fields: [companyId], references: [id], onDelete: Cascade) } /// * /// * 1. Normalised session --------------------------- model Session { id String @id @default(uuid()) companyId String importId String? @unique /// * /// * session-level data (processed from SessionImport) startTime DateTime endTime DateTime ipAddress String? country String? fullTranscriptUrl String? avgResponseTime Float? initialMsg String? language String? messagesSent Int? sentiment SentimentCategory? escalated Boolean? forwardedHr Boolean? category SessionCategory? summary String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt 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]) } /// * /// * 2. Raw CSV row (pure data storage) ---------- model SessionImport { id String @id @default(uuid()) companyId String externalSessionId String @unique startTimeRaw String endTimeRaw String ipAddress String? countryCode String? language String? messagesSent Int? sentimentRaw String? escalatedRaw String? forwardedHrRaw String? fullTranscriptUrl String? avgResponseTimeSeconds Float? tokens Int? tokensEur Float? category String? initialMessage String? rawTranscriptContent String? createdAt DateTime @default(now()) session Session? @relation("ImportToSession") company Company @relation(fields: [companyId], references: [id], onDelete: Cascade) @@unique([companyId, externalSessionId]) } /// * /// * MESSAGE (individual lines) model Message { id String @id @default(uuid()) sessionId String timestamp DateTime? role String content String order Int createdAt DateTime @default(now()) session Session @relation(fields: [sessionId], references: [id], onDelete: Cascade) @@unique([sessionId, order]) @@index([sessionId, order]) } /// * /// * UNIFIED PROCESSING STATUS TRACKING model SessionProcessingStatus { id String @id @default(uuid()) sessionId String stage ProcessingStage status ProcessingStatus @default(PENDING) startedAt DateTime? completedAt DateTime? 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]) } /// * /// * QUESTION MANAGEMENT (separate from Session for better analytics) model Question { id String @id @default(uuid()) content String @unique createdAt DateTime @default(now()) sessionQuestions SessionQuestion[] } model SessionQuestion { id String @id @default(uuid()) sessionId String questionId String order Int createdAt DateTime @default(now()) question Question @relation(fields: [questionId], references: [id]) session Session @relation(fields: [sessionId], references: [id], onDelete: Cascade) @@unique([sessionId, questionId]) @@unique([sessionId, order]) @@index([sessionId]) } /// * /// * AI PROCESSING COST TRACKING model AIProcessingRequest { id String @id @default(uuid()) sessionId String openaiRequestId String? model String serviceTier String? systemFingerprint String? promptTokens Int completionTokens Int totalTokens Int cachedTokens Int? audioTokensPrompt Int? reasoningTokens Int? audioTokensCompletion Int? acceptedPredictionTokens Int? rejectedPredictionTokens Int? promptTokenCost Float completionTokenCost Float totalCostEur Float processingType String success Boolean errorMessage String? requestedAt DateTime @default(now()) completedAt DateTime? session Session @relation(fields: [sessionId], references: [id], onDelete: Cascade) @@index([sessionId]) @@index([requestedAt]) @@index([model]) } /// * /// * AI Model definitions (without pricing) model AIModel { id String @id @default(uuid()) name String @unique provider String maxTokens Int? isActive Boolean @default(true) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt pricing AIModelPricing[] companyModels CompanyAIModel[] @@index([provider, isActive]) } /// * /// * Time-based pricing for AI models model AIModelPricing { id String @id @default(uuid()) aiModelId String promptTokenCost Float completionTokenCost Float effectiveFrom DateTime effectiveUntil DateTime? createdAt DateTime @default(now()) 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()) 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 – fewer magic strings enum UserRole { ADMIN USER AUDITOR } enum SentimentCategory { POSITIVE NEUTRAL NEGATIVE } enum SessionCategory { SCHEDULE_HOURS LEAVE_VACATION SICK_LEAVE_RECOVERY SALARY_COMPENSATION CONTRACT_HOURS ONBOARDING OFFBOARDING WORKWEAR_STAFF_PASS TEAM_CONTACTS PERSONAL_QUESTIONS ACCESS_LOGIN SOCIAL_QUESTIONS UNRECOGNIZED_OTHER } enum ProcessingStage { CSV_IMPORT TRANSCRIPT_FETCH SESSION_CREATION AI_ANALYSIS QUESTION_EXTRACTION } enum ProcessingStatus { PENDING IN_PROGRESS COMPLETED FAILED SKIPPED }