mirror of
https://github.com/kjanat/livedash-node.git
synced 2026-01-16 15:12:09 +01:00
- Add compound index on (companyId, language) for language filtering - Add compound index on (companyId, messagesSent) for message count sorting - Add compound index on (companyId, avgResponseTime) for response time sorting These indexes optimize the session dashboard queries that filter by language or sort by messagesSent/avgResponseTime, preventing full table scans.
430 lines
15 KiB
Plaintext
430 lines
15 KiB
Plaintext
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)
|
|
/// Maximum number of users allowed for this company
|
|
maxUsers Int @default(10)
|
|
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)
|
|
/// 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)
|
|
|
|
@@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 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
|
|
}
|