2026-09-03 21:01:52 +03:30
|
|
|
|
// Novya backend schema.
|
|
|
|
|
|
// Identity/auth from Phase 3 is preserved. Domain tables match BACKEND_DATABASE_ARCHITECTURE_AUDIT.md.
|
|
|
|
|
|
|
|
|
|
|
|
generator client {
|
|
|
|
|
|
provider = "prisma-client-js"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
datasource db {
|
|
|
|
|
|
provider = "mysql"
|
|
|
|
|
|
url = env("DATABASE_URL")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
enum AuthProvider {
|
|
|
|
|
|
password
|
|
|
|
|
|
guest
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-21 08:00:24 +03:30
|
|
|
|
/// Application access role. Runtime admin checks use DB role, not ENV alone.
|
|
|
|
|
|
enum UserRole {
|
|
|
|
|
|
user
|
|
|
|
|
|
admin
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-03 21:01:52 +03:30
|
|
|
|
enum AttemptPhase {
|
|
|
|
|
|
pre
|
|
|
|
|
|
post
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
enum AttemptStatus {
|
|
|
|
|
|
/// Reserved for future draft/in-progress attempts; MVP always writes `scored`.
|
|
|
|
|
|
submitted
|
|
|
|
|
|
scored
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
enum SessionProgressStatus {
|
|
|
|
|
|
not_started
|
|
|
|
|
|
in_progress
|
|
|
|
|
|
completed
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
enum ChatRole {
|
|
|
|
|
|
/// Used in coach prompts at runtime; not persisted to chat_messages.
|
|
|
|
|
|
system
|
|
|
|
|
|
user
|
|
|
|
|
|
assistant
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
enum AiRunStatus {
|
|
|
|
|
|
queued
|
|
|
|
|
|
running
|
|
|
|
|
|
succeeded
|
|
|
|
|
|
failed
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-21 08:00:24 +03:30
|
|
|
|
enum AssignmentType {
|
|
|
|
|
|
ONE_TIME
|
|
|
|
|
|
DAILY
|
|
|
|
|
|
ONGOING
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
enum UserAssignmentStatus {
|
|
|
|
|
|
NOT_STARTED
|
|
|
|
|
|
ACTIVE
|
|
|
|
|
|
IN_PROGRESS
|
|
|
|
|
|
REPORTED
|
|
|
|
|
|
COMPLETED
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
enum AssignmentReportStatus {
|
|
|
|
|
|
completed
|
|
|
|
|
|
in_progress
|
|
|
|
|
|
difficult
|
|
|
|
|
|
not_started
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Per-day outcome for DAILY / ONGOING assignments (stored on the entry row).
|
|
|
|
|
|
enum AssignmentDayStatus {
|
|
|
|
|
|
done
|
|
|
|
|
|
skipped
|
|
|
|
|
|
difficult
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-03 21:01:52 +03:30
|
|
|
|
model User {
|
|
|
|
|
|
id BigInt @id @default(autoincrement()) @db.UnsignedBigInt
|
|
|
|
|
|
externalId String @unique @default(uuid()) @db.Char(36)
|
|
|
|
|
|
isGuest Boolean @default(false)
|
2026-09-21 08:00:24 +03:30
|
|
|
|
role UserRole @default(user)
|
2026-09-03 21:01:52 +03:30
|
|
|
|
createdAt DateTime @default(now())
|
|
|
|
|
|
updatedAt DateTime @updatedAt
|
|
|
|
|
|
|
|
|
|
|
|
profile UserProfile?
|
|
|
|
|
|
identities AuthIdentity[]
|
|
|
|
|
|
sessions AuthSession[]
|
|
|
|
|
|
journey LearningJourney?
|
|
|
|
|
|
assessmentAttempts AssessmentAttempt[]
|
|
|
|
|
|
sessionProgress SessionProgress[]
|
2026-09-21 08:00:24 +03:30
|
|
|
|
homeworkReports HomeworkReport[]
|
|
|
|
|
|
userAssignments UserAssignment[]
|
|
|
|
|
|
assignmentReports AssignmentReport[]
|
|
|
|
|
|
assignmentEntries AssignmentDailyEntry[]
|
2026-09-03 21:01:52 +03:30
|
|
|
|
chatSessions ChatSession[]
|
|
|
|
|
|
aiRuns AiAnalysisRun[]
|
2026-09-21 08:00:24 +03:30
|
|
|
|
auditLogsAsActor AuditLog[] @relation("AuditActor")
|
|
|
|
|
|
auditLogsAsTarget AuditLog[] @relation("AuditTarget")
|
2026-09-03 21:01:52 +03:30
|
|
|
|
|
|
|
|
|
|
@@index([createdAt], map: "idx_users_created_at")
|
|
|
|
|
|
@@index([isGuest], map: "idx_users_is_guest")
|
2026-09-21 08:00:24 +03:30
|
|
|
|
@@index([role], map: "idx_users_role")
|
2026-09-03 21:01:52 +03:30
|
|
|
|
@@map("users")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-21 08:00:24 +03:30
|
|
|
|
/// Sensitive admin/access events. Never store passwords, hashes, tokens, or full chat/assessment payloads.
|
|
|
|
|
|
model AuditLog {
|
|
|
|
|
|
id BigInt @id @default(autoincrement()) @db.UnsignedBigInt
|
|
|
|
|
|
actorUserId BigInt? @db.UnsignedBigInt
|
|
|
|
|
|
action String @db.VarChar(64)
|
|
|
|
|
|
targetUserId BigInt? @db.UnsignedBigInt
|
|
|
|
|
|
targetType String? @db.VarChar(64)
|
|
|
|
|
|
targetId String? @db.VarChar(191)
|
|
|
|
|
|
metadata Json?
|
|
|
|
|
|
ipAddress String? @db.VarChar(64)
|
|
|
|
|
|
userAgent String? @db.VarChar(512)
|
|
|
|
|
|
createdAt DateTime @default(now())
|
|
|
|
|
|
|
|
|
|
|
|
actor User? @relation("AuditActor", fields: [actorUserId], references: [id], onDelete: SetNull)
|
|
|
|
|
|
target User? @relation("AuditTarget", fields: [targetUserId], references: [id], onDelete: SetNull)
|
|
|
|
|
|
|
|
|
|
|
|
@@index([actorUserId, createdAt], map: "idx_audit_logs_actor_created")
|
|
|
|
|
|
@@index([action, createdAt], map: "idx_audit_logs_action_created")
|
|
|
|
|
|
@@index([targetUserId, createdAt], map: "idx_audit_logs_target_created")
|
|
|
|
|
|
@@map("audit_logs")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-03 21:01:52 +03:30
|
|
|
|
model UserProfile {
|
|
|
|
|
|
userId BigInt @id @db.UnsignedBigInt
|
|
|
|
|
|
name String @db.VarChar(100)
|
|
|
|
|
|
age Int
|
|
|
|
|
|
createdAt DateTime @default(now())
|
|
|
|
|
|
updatedAt DateTime @updatedAt
|
|
|
|
|
|
|
|
|
|
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
|
|
|
|
|
|
|
|
|
|
@@index([name], map: "idx_user_profiles_name")
|
|
|
|
|
|
@@map("user_profiles")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
model AuthIdentity {
|
|
|
|
|
|
id BigInt @id @default(autoincrement()) @db.UnsignedBigInt
|
|
|
|
|
|
userId BigInt @db.UnsignedBigInt
|
|
|
|
|
|
provider AuthProvider
|
|
|
|
|
|
providerSubject String @db.VarChar(191)
|
|
|
|
|
|
email String? @db.VarChar(191)
|
|
|
|
|
|
passwordHash String? @db.VarChar(255)
|
|
|
|
|
|
createdAt DateTime @default(now())
|
|
|
|
|
|
updatedAt DateTime @updatedAt
|
|
|
|
|
|
|
|
|
|
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
|
|
|
|
|
|
|
|
|
|
@@unique([provider, providerSubject], map: "uq_auth_identities_provider_subject")
|
|
|
|
|
|
@@index([userId], map: "idx_auth_identities_user_id")
|
|
|
|
|
|
@@index([email], map: "idx_auth_identities_email")
|
|
|
|
|
|
@@map("auth_identities")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
model AuthSession {
|
|
|
|
|
|
id BigInt @id @default(autoincrement()) @db.UnsignedBigInt
|
|
|
|
|
|
userId BigInt @db.UnsignedBigInt
|
|
|
|
|
|
tokenHash String @unique @db.Char(64)
|
|
|
|
|
|
expiresAt DateTime
|
|
|
|
|
|
revokedAt DateTime?
|
|
|
|
|
|
createdAt DateTime @default(now())
|
|
|
|
|
|
updatedAt DateTime @updatedAt
|
|
|
|
|
|
|
|
|
|
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
|
|
|
|
|
|
|
|
|
|
@@index([userId, expiresAt], map: "idx_auth_sessions_user_id_expires_at")
|
|
|
|
|
|
@@index([expiresAt], map: "idx_auth_sessions_expires_at")
|
|
|
|
|
|
@@map("auth_sessions")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Timestamps are authoritative. currentStep is a denormalized cache.
|
|
|
|
|
|
model LearningJourney {
|
|
|
|
|
|
userId BigInt @id @db.UnsignedBigInt
|
|
|
|
|
|
currentStep String @default("registered") @db.VarChar(32)
|
|
|
|
|
|
preTestCompletedAt DateTime?
|
|
|
|
|
|
trainingStartedAt DateTime?
|
|
|
|
|
|
trainingCompletedAt DateTime?
|
|
|
|
|
|
postTestCompletedAt DateTime?
|
|
|
|
|
|
lastActivityAt DateTime @default(now())
|
|
|
|
|
|
createdAt DateTime @default(now())
|
|
|
|
|
|
updatedAt DateTime @updatedAt
|
|
|
|
|
|
|
|
|
|
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
|
|
|
|
|
|
|
|
|
|
@@map("learning_journeys")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Catalog of the 11 educational sessions. Content blocks are JSON (same shape as PWA).
|
|
|
|
|
|
model CourseSession {
|
|
|
|
|
|
id BigInt @id @default(autoincrement()) @db.UnsignedBigInt
|
|
|
|
|
|
sessionKey String @unique @db.VarChar(64)
|
|
|
|
|
|
order Int @unique
|
|
|
|
|
|
title String @db.VarChar(191)
|
|
|
|
|
|
description String @db.Text
|
|
|
|
|
|
estimatedDurationMinutes Int
|
|
|
|
|
|
blocks Json
|
|
|
|
|
|
createdAt DateTime @default(now())
|
|
|
|
|
|
updatedAt DateTime @updatedAt
|
|
|
|
|
|
|
2026-09-21 08:00:24 +03:30
|
|
|
|
sessionProgress SessionProgress[]
|
|
|
|
|
|
homeworkReports HomeworkReport[]
|
|
|
|
|
|
assignmentDefinitions AssignmentDefinition[]
|
|
|
|
|
|
chatSessions ChatSession[]
|
2026-09-03 21:01:52 +03:30
|
|
|
|
|
|
|
|
|
|
@@index([order], map: "idx_course_sessions_order")
|
|
|
|
|
|
@@map("course_sessions")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-21 08:00:24 +03:30
|
|
|
|
/// Fixed catalog of homework/assignment definitions (seeded from code).
|
|
|
|
|
|
model AssignmentDefinition {
|
|
|
|
|
|
id BigInt @id @default(autoincrement()) @db.UnsignedBigInt
|
|
|
|
|
|
key String @unique @db.VarChar(64)
|
|
|
|
|
|
sessionOrder Int
|
|
|
|
|
|
title String @db.VarChar(191)
|
|
|
|
|
|
description String @db.Text
|
|
|
|
|
|
instructions String @db.Text
|
|
|
|
|
|
assignmentType AssignmentType
|
|
|
|
|
|
/// Structured form fields for drafts / daily entries (JSON). Null = free text.
|
|
|
|
|
|
formSchema Json?
|
|
|
|
|
|
/// Target day count for progress UI (not necessarily the completion threshold).
|
|
|
|
|
|
expectedDurationDays Int?
|
|
|
|
|
|
suggestedEndDay Int?
|
|
|
|
|
|
/// Min valid «done» days to auto-complete DAILY/ONGOING. Null = never auto-complete by count.
|
|
|
|
|
|
minValidDays Int?
|
|
|
|
|
|
/// When false, structured response is the primary record; AssignmentReport is optional/soft.
|
|
|
|
|
|
requiresReport Boolean @default(true)
|
|
|
|
|
|
/// Soft check-in only; must stay false unless a clinical gate is explicitly required.
|
|
|
|
|
|
blocksNextSession Boolean @default(false)
|
|
|
|
|
|
isFinalAssignment Boolean @default(false)
|
|
|
|
|
|
displayOrder Int @default(0)
|
|
|
|
|
|
createdAt DateTime @default(now())
|
|
|
|
|
|
updatedAt DateTime @updatedAt
|
|
|
|
|
|
|
|
|
|
|
|
courseSession CourseSession @relation(fields: [sessionOrder], references: [order], onDelete: Restrict, onUpdate: Cascade)
|
|
|
|
|
|
userAssignments UserAssignment[]
|
|
|
|
|
|
|
|
|
|
|
|
@@index([sessionOrder], map: "idx_assignment_definitions_session_order")
|
|
|
|
|
|
@@index([displayOrder], map: "idx_assignment_definitions_display_order")
|
|
|
|
|
|
@@map("assignment_definitions")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Per-user instance of an assignment, activated when the related session is completed.
|
|
|
|
|
|
model UserAssignment {
|
|
|
|
|
|
id BigInt @id @default(autoincrement()) @db.UnsignedBigInt
|
|
|
|
|
|
userId BigInt @db.UnsignedBigInt
|
|
|
|
|
|
definitionId BigInt @db.UnsignedBigInt
|
|
|
|
|
|
status UserAssignmentStatus @default(ACTIVE)
|
|
|
|
|
|
activatedAt DateTime @default(now())
|
|
|
|
|
|
startedAt DateTime?
|
|
|
|
|
|
lastActivityAt DateTime?
|
|
|
|
|
|
completedAt DateTime?
|
|
|
|
|
|
/// Draft answers for ONE_TIME writing assignments (JSON object keyed by field).
|
|
|
|
|
|
draftData Json?
|
|
|
|
|
|
completionNote String? @db.Text
|
|
|
|
|
|
createdAt DateTime @default(now())
|
|
|
|
|
|
updatedAt DateTime @updatedAt
|
|
|
|
|
|
|
|
|
|
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
|
|
|
|
definition AssignmentDefinition @relation(fields: [definitionId], references: [id], onDelete: Restrict)
|
|
|
|
|
|
reports AssignmentReport[]
|
|
|
|
|
|
dailyEntries AssignmentDailyEntry[]
|
|
|
|
|
|
|
|
|
|
|
|
@@unique([userId, definitionId], map: "uq_user_assignments_user_definition")
|
|
|
|
|
|
@@index([userId, status], map: "idx_user_assignments_user_status")
|
|
|
|
|
|
@@index([userId, activatedAt], map: "idx_user_assignments_user_activated")
|
|
|
|
|
|
@@map("user_assignments")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Check-in / progress reports — multiple allowed per user assignment.
|
|
|
|
|
|
model AssignmentReport {
|
|
|
|
|
|
id BigInt @id @default(autoincrement()) @db.UnsignedBigInt
|
|
|
|
|
|
userAssignmentId BigInt @db.UnsignedBigInt
|
|
|
|
|
|
userId BigInt @db.UnsignedBigInt
|
|
|
|
|
|
reportText String @db.Text
|
|
|
|
|
|
reportStatus AssignmentReportStatus
|
|
|
|
|
|
createdAt DateTime @default(now())
|
|
|
|
|
|
|
|
|
|
|
|
userAssignment UserAssignment @relation(fields: [userAssignmentId], references: [id], onDelete: Cascade)
|
|
|
|
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
|
|
|
|
|
|
|
|
|
|
@@index([userAssignmentId, createdAt], map: "idx_assignment_reports_ua_created")
|
|
|
|
|
|
@@index([userId, createdAt], map: "idx_assignment_reports_user_created")
|
|
|
|
|
|
@@map("assignment_reports")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Daily/ongoing entry — one row per calendar day per user assignment.
|
|
|
|
|
|
model AssignmentDailyEntry {
|
|
|
|
|
|
id BigInt @id @default(autoincrement()) @db.UnsignedBigInt
|
|
|
|
|
|
userAssignmentId BigInt @db.UnsignedBigInt
|
|
|
|
|
|
userId BigInt @db.UnsignedBigInt
|
|
|
|
|
|
entryDate DateTime @db.Date
|
|
|
|
|
|
/// done = practiced today; skipped / difficult = logged without full activity payload.
|
|
|
|
|
|
dayStatus AssignmentDayStatus @default(done)
|
|
|
|
|
|
responseData Json
|
|
|
|
|
|
note String? @db.Text
|
|
|
|
|
|
createdAt DateTime @default(now())
|
|
|
|
|
|
updatedAt DateTime @updatedAt
|
|
|
|
|
|
|
|
|
|
|
|
userAssignment UserAssignment @relation(fields: [userAssignmentId], references: [id], onDelete: Cascade)
|
|
|
|
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
|
|
|
|
|
|
|
|
|
|
@@unique([userAssignmentId, entryDate], map: "uq_assignment_daily_entries_ua_date")
|
|
|
|
|
|
@@index([userId, entryDate], map: "idx_assignment_daily_entries_user_date")
|
|
|
|
|
|
@@map("assignment_daily_entries")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-03 21:01:52 +03:30
|
|
|
|
model SessionProgress {
|
|
|
|
|
|
id BigInt @id @default(autoincrement()) @db.UnsignedBigInt
|
|
|
|
|
|
userId BigInt @db.UnsignedBigInt
|
|
|
|
|
|
sessionOrder Int
|
|
|
|
|
|
status SessionProgressStatus @default(not_started)
|
|
|
|
|
|
startedAt DateTime?
|
|
|
|
|
|
completedAt DateTime?
|
|
|
|
|
|
completionMetadata Json?
|
|
|
|
|
|
createdAt DateTime @default(now())
|
|
|
|
|
|
updatedAt DateTime @updatedAt
|
|
|
|
|
|
|
|
|
|
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
|
|
|
|
courseSession CourseSession @relation(fields: [sessionOrder], references: [order], onDelete: Restrict, onUpdate: Cascade)
|
|
|
|
|
|
|
|
|
|
|
|
@@unique([userId, sessionOrder], map: "uq_session_progress_user_order")
|
|
|
|
|
|
@@index([userId, status], map: "idx_session_progress_user_status")
|
|
|
|
|
|
@@map("session_progress")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-21 08:00:24 +03:30
|
|
|
|
/// Learner report of homework completed for a finished session (sessions 2–10).
|
|
|
|
|
|
model HomeworkReport {
|
|
|
|
|
|
id BigInt @id @default(autoincrement()) @db.UnsignedBigInt
|
|
|
|
|
|
userId BigInt @db.UnsignedBigInt
|
|
|
|
|
|
/// Session whose homework is being reported (the completed session, not the next one).
|
|
|
|
|
|
sessionOrder Int
|
|
|
|
|
|
reportText String @db.Text
|
|
|
|
|
|
submittedAt DateTime @default(now())
|
|
|
|
|
|
createdAt DateTime @default(now())
|
|
|
|
|
|
updatedAt DateTime @updatedAt
|
|
|
|
|
|
|
|
|
|
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
|
|
|
|
courseSession CourseSession @relation(fields: [sessionOrder], references: [order], onDelete: Restrict, onUpdate: Cascade)
|
|
|
|
|
|
|
|
|
|
|
|
@@unique([userId, sessionOrder], map: "uq_homework_reports_user_order")
|
|
|
|
|
|
@@index([userId], map: "idx_homework_reports_user")
|
|
|
|
|
|
@@map("homework_reports")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-03 21:01:52 +03:30
|
|
|
|
model AssessmentAttempt {
|
|
|
|
|
|
id BigInt @id @default(autoincrement()) @db.UnsignedBigInt
|
|
|
|
|
|
userId BigInt @db.UnsignedBigInt
|
|
|
|
|
|
phase AttemptPhase
|
|
|
|
|
|
status AttemptStatus @default(scored)
|
|
|
|
|
|
submittedAt DateTime
|
|
|
|
|
|
scoredAt DateTime
|
|
|
|
|
|
createdAt DateTime @default(now())
|
|
|
|
|
|
updatedAt DateTime @updatedAt
|
|
|
|
|
|
|
|
|
|
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
|
|
|
|
answers AssessmentAnswer[]
|
|
|
|
|
|
scores AssessmentScore[]
|
|
|
|
|
|
|
|
|
|
|
|
@@unique([userId, phase], map: "uq_assessment_attempts_user_phase")
|
|
|
|
|
|
@@index([userId, phase, submittedAt], map: "idx_assessment_attempts_user_phase_submitted")
|
|
|
|
|
|
@@map("assessment_attempts")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
model AssessmentAnswer {
|
|
|
|
|
|
id BigInt @id @default(autoincrement()) @db.UnsignedBigInt
|
|
|
|
|
|
attemptId BigInt @db.UnsignedBigInt
|
|
|
|
|
|
questionId String @db.VarChar(32)
|
|
|
|
|
|
rawValue Int
|
|
|
|
|
|
points Int
|
|
|
|
|
|
dimension String @db.VarChar(32)
|
|
|
|
|
|
isReverseScored Boolean
|
|
|
|
|
|
createdAt DateTime @default(now())
|
|
|
|
|
|
|
|
|
|
|
|
attempt AssessmentAttempt @relation(fields: [attemptId], references: [id], onDelete: Cascade)
|
|
|
|
|
|
|
|
|
|
|
|
@@unique([attemptId, questionId], map: "uq_assessment_answers_attempt_question")
|
|
|
|
|
|
@@map("assessment_answers")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
model AssessmentScore {
|
|
|
|
|
|
id BigInt @id @default(autoincrement()) @db.UnsignedBigInt
|
|
|
|
|
|
attemptId BigInt @db.UnsignedBigInt
|
|
|
|
|
|
metricKey String @db.VarChar(32)
|
|
|
|
|
|
rawScore Int
|
|
|
|
|
|
createdAt DateTime @default(now())
|
|
|
|
|
|
|
|
|
|
|
|
attempt AssessmentAttempt @relation(fields: [attemptId], references: [id], onDelete: Cascade)
|
|
|
|
|
|
|
|
|
|
|
|
@@unique([attemptId, metricKey], map: "uq_assessment_scores_attempt_metric")
|
|
|
|
|
|
@@map("assessment_scores")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
model ChatSession {
|
|
|
|
|
|
id BigInt @id @default(autoincrement()) @db.UnsignedBigInt
|
|
|
|
|
|
userId BigInt @db.UnsignedBigInt
|
|
|
|
|
|
sessionOrder Int
|
|
|
|
|
|
startedAt DateTime @default(now())
|
|
|
|
|
|
endedAt DateTime?
|
|
|
|
|
|
createdAt DateTime @default(now())
|
|
|
|
|
|
updatedAt DateTime @updatedAt
|
|
|
|
|
|
|
|
|
|
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
|
|
|
|
courseSession CourseSession @relation(fields: [sessionOrder], references: [order], onDelete: Restrict, onUpdate: Cascade)
|
|
|
|
|
|
messages ChatMessage[]
|
|
|
|
|
|
aiRuns AiAnalysisRun[]
|
|
|
|
|
|
|
|
|
|
|
|
@@index([userId, sessionOrder, startedAt], map: "idx_chat_sessions_user_order_started")
|
|
|
|
|
|
@@map("chat_sessions")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
model ChatMessage {
|
|
|
|
|
|
id BigInt @id @default(autoincrement()) @db.UnsignedBigInt
|
|
|
|
|
|
chatSessionId BigInt @db.UnsignedBigInt
|
|
|
|
|
|
role ChatRole
|
|
|
|
|
|
content String @db.Text
|
|
|
|
|
|
metadata Json?
|
|
|
|
|
|
createdAt DateTime @default(now())
|
|
|
|
|
|
|
|
|
|
|
|
chatSession ChatSession @relation(fields: [chatSessionId], references: [id], onDelete: Cascade)
|
|
|
|
|
|
|
|
|
|
|
|
@@index([chatSessionId, createdAt], map: "idx_chat_messages_session_created")
|
|
|
|
|
|
@@map("chat_messages")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
model AiAnalysisRun {
|
|
|
|
|
|
id BigInt @id @default(autoincrement()) @db.UnsignedBigInt
|
|
|
|
|
|
userId BigInt @db.UnsignedBigInt
|
|
|
|
|
|
chatSessionId BigInt?
|
|
|
|
|
|
provider String @db.VarChar(64)
|
|
|
|
|
|
model String @db.VarChar(128)
|
|
|
|
|
|
status AiRunStatus @default(queued)
|
|
|
|
|
|
latencyMs Int?
|
|
|
|
|
|
totalTokens Int?
|
|
|
|
|
|
errorMessage String? @db.Text
|
|
|
|
|
|
createdAt DateTime @default(now())
|
|
|
|
|
|
updatedAt DateTime @updatedAt
|
|
|
|
|
|
|
|
|
|
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
|
|
|
|
chatSession ChatSession? @relation(fields: [chatSessionId], references: [id], onDelete: SetNull)
|
|
|
|
|
|
|
|
|
|
|
|
@@index([userId, createdAt], map: "idx_ai_analysis_runs_user_created")
|
|
|
|
|
|
@@map("ai_analysis_runs")
|
|
|
|
|
|
}
|