mirror of
https://github.com/kjanat/livedash-node.git
synced 2026-01-16 06:12:09 +01:00
- Updated environment configuration to include Postgres database settings. - Enhanced import processing to minimize field copying and rely on AI for analysis. - Implemented detailed AI processing request tracking, including token usage and costs. - Added new models for Question and SessionQuestion to manage user inquiries separately. - Improved session processing scheduler with AI cost reporting functionality. - Created a test script to validate the refactored pipeline and display processing statistics. - Updated Prisma schema and migration files to reflect new database structure and relationships.
284 lines
8.0 KiB
Plaintext
284 lines
8.0 KiB
Plaintext
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
|
||
}
|
||
|
||
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
|
||
}
|
||
|
||
/**
|
||
* 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
|
||
|
||
// Direct copies from SessionImport (minimal processing)
|
||
ipAddress String?
|
||
country String? // from countryCode
|
||
fullTranscriptUrl String?
|
||
avgResponseTime Float? // from avgResponseTimeSeconds
|
||
initialMsg String? // from initialMessage
|
||
|
||
// AI-processed fields (calculated from Messages or AI analysis)
|
||
language String? // AI-detected from Messages
|
||
messagesSent Int? // Calculated from Message count
|
||
sentiment SentimentCategory? // AI-analyzed (changed from Float to enum)
|
||
escalated Boolean? // AI-detected
|
||
forwardedHr Boolean? // AI-detected
|
||
category SessionCategory? // AI-categorized (changed to enum)
|
||
|
||
// AI-generated fields
|
||
summary String? // AI-generated summary
|
||
|
||
// Processing metadata
|
||
processed Boolean @default(false)
|
||
|
||
/**
|
||
* Relationships
|
||
*/
|
||
messages Message[] // Individual conversation messages
|
||
sessionQuestions SessionQuestion[] // Questions asked in this session
|
||
aiProcessingRequests AIProcessingRequest[] // AI processing cost tracking
|
||
|
||
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])
|
||
}
|
||
|
||
/**
|
||
* QUESTION MANAGEMENT (separate from Session for better analytics)
|
||
*/
|
||
model Question {
|
||
id String @id @default(uuid())
|
||
content String @unique // The actual question text
|
||
createdAt DateTime @default(now())
|
||
|
||
// Relationships
|
||
sessionQuestions SessionQuestion[]
|
||
}
|
||
|
||
model SessionQuestion {
|
||
id String @id @default(uuid())
|
||
sessionId String
|
||
questionId String
|
||
order Int // Order within the session
|
||
createdAt DateTime @default(now())
|
||
|
||
// Relationships
|
||
session Session @relation(fields: [sessionId], references: [id], onDelete: Cascade)
|
||
question Question @relation(fields: [questionId], references: [id])
|
||
|
||
@@unique([sessionId, questionId]) // Prevent duplicate questions per session
|
||
@@unique([sessionId, order]) // Ensure unique ordering
|
||
@@index([sessionId])
|
||
}
|
||
|
||
/**
|
||
* AI PROCESSING COST TRACKING
|
||
*/
|
||
model AIProcessingRequest {
|
||
id String @id @default(uuid())
|
||
sessionId String
|
||
|
||
// OpenAI Request Details
|
||
openaiRequestId String? // "chatcmpl-Bn8IH9UM8t7luZVWnwZG7CVJ0kjPo"
|
||
model String // "gpt-4o-2024-08-06"
|
||
serviceTier String? // "default"
|
||
systemFingerprint String? // "fp_07871e2ad8"
|
||
|
||
// Token Usage (from usage object)
|
||
promptTokens Int // 11
|
||
completionTokens Int // 9
|
||
totalTokens Int // 20
|
||
|
||
// Detailed Token Breakdown
|
||
cachedTokens Int? // prompt_tokens_details.cached_tokens
|
||
audioTokensPrompt Int? // prompt_tokens_details.audio_tokens
|
||
reasoningTokens Int? // completion_tokens_details.reasoning_tokens
|
||
audioTokensCompletion Int? // completion_tokens_details.audio_tokens
|
||
acceptedPredictionTokens Int? // completion_tokens_details.accepted_prediction_tokens
|
||
rejectedPredictionTokens Int? // completion_tokens_details.rejected_prediction_tokens
|
||
|
||
// Cost Calculation
|
||
promptTokenCost Float // Cost per prompt token (varies by model)
|
||
completionTokenCost Float // Cost per completion token (varies by model)
|
||
totalCostEur Float // Calculated total cost in EUR
|
||
|
||
// Processing Context
|
||
processingType String // "session_analysis", "reprocessing", etc.
|
||
success Boolean // Whether the request succeeded
|
||
errorMessage String? // If failed, what went wrong
|
||
|
||
// Timestamps
|
||
requestedAt DateTime @default(now())
|
||
completedAt DateTime?
|
||
|
||
// Relationships
|
||
session Session @relation(fields: [sessionId], references: [id], onDelete: Cascade)
|
||
|
||
@@index([sessionId])
|
||
@@index([requestedAt])
|
||
@@index([model])
|
||
}
|