generator client { provider = "prisma-client-js" } datasource db { provider = "sqlite" // still OK for local/dev; use Postgres in prod url = "file:./dev.db" } /** * ENUMS – fewer magic strings */ enum UserRole { ADMIN USER AUDITOR } enum SentimentCategory { POSITIVE NEUTRAL NEGATIVE } /** * COMPANY (multi-tenant root) */ model Company { id String @id @default(uuid()) name String csvUrl String csvUsername String? csvPassword String? sentimentAlert Float? dashboardOpts Json? // JSON column instead of opaque string users User[] @relation("CompanyUsers") sessions Session[] imports SessionImport[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } /** * USER (auth accounts) */ model User { id String @id @default(uuid()) email String @unique password String role UserRole @default(USER) company Company @relation("CompanyUsers", fields: [companyId], references: [id], onDelete: Cascade) companyId String resetToken String? resetTokenExpiry DateTime? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } /** * SESSION ↔ SESSIONIMPORT (1-to-1) */ /** * 1. Normalised session --------------------------- */ model Session { id String @id @default(uuid()) company Company @relation(fields: [companyId], references: [id], onDelete: Cascade) companyId String /** * 1-to-1 link back to the import row */ import SessionImport? @relation("ImportToSession", fields: [importId], references: [id]) importId String? @unique /** * session-level data (processed from SessionImport) */ startTime DateTime endTime DateTime // Processed fields from SessionImport data ipAddress String? country String? // processed from countryCode language String? // processed from language messagesSent Int? sentiment Float? // processed from sentimentRaw sentimentCategory SentimentCategory? escalated Boolean? forwardedHr Boolean? fullTranscriptUrl String? avgResponseTime Float? // processed from avgResponseTimeSeconds tokens Int? tokensEur Float? category String? initialMsg String? // processed from initialMessage // Processing metadata processed Boolean @default(false) questions String? // JSON array of extracted questions summary String? // AI-generated summary /** * ---------- the missing opposite side ---------- */ messages Message[] // <-- satisfies Message.session createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([companyId, startTime]) } /** * 2. Raw CSV row waiting to be processed ---------- */ enum ImportStatus { QUEUED PROCESSING DONE ERROR } model SessionImport { id String @id @default(uuid()) company Company @relation(fields: [companyId], references: [id], onDelete: Cascade) companyId String /** * 1-to-1 back-relation; NO fields/references here */ session Session? @relation("ImportToSession") // ─── 16 CSV columns 1-to-1 ──────────────────────── externalSessionId String @unique // value from CSV column 1 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? // ─── Raw transcript content ───────────────────────── rawTranscriptContent String? // Fetched content from fullTranscriptUrl // ─── bookkeeping ───────────────────────────────── status ImportStatus @default(QUEUED) errorMsg String? processedAt DateTime? createdAt DateTime @default(now()) @@unique([companyId, externalSessionId]) // idempotent re-imports @@index([status]) } /** * MESSAGE (individual lines) */ model Message { id String @id @default(uuid()) session Session @relation(fields: [sessionId], references: [id], onDelete: Cascade) sessionId String timestamp DateTime? role String // "user" | "assistant" | "system" – free-form keeps migration easy content String order Int createdAt DateTime @default(now()) @@unique([sessionId, order]) // guards against duplicate order values @@index([sessionId, order]) }