// DESIGN ARTIFACT — not the runtime backend schema. // Active backend schema: backend/prisma/schema.prisma // Do not run migrations from this file against the backend MySQL database. generator client { provider = "prisma-client-js" } datasource db { provider = "mysql" url = env("DATABASE_URL") } enum UserStatus { active pending_verification suspended deleted } enum AuthProvider { password google phone_otp } enum SessionStatus { active revoked expired } enum EnrollmentStatus { active paused completed dropped } enum UnlockReason { schedule manual prerequisite_completed retry } enum LessonProgressStatus { not_started in_progress completed skipped } enum SubmissionStatus { draft submitted reviewed } enum EventSource { user system ai admin } enum AttemptPhase { pre post } enum AttemptStatus { in_progress submitted scored canceled } enum AiRunStatus { queued running succeeded failed canceled } enum AiRunType { assessment_analysis final_report exercise_feedback lesson_chat } enum ChatRole { system user assistant tool } enum RoleCode { admin researcher user } enum JourneyStep { registered pre_assessment_assigned pre_assessment_completed in_program lesson_in_progress exercise_pending post_assessment_assigned post_assessment_completed completed } enum DailyCheckinSource { manual ai system } enum AssessmentAssignmentStatus { assigned available started completed expired canceled } enum NotificationQueueStatus { pending sent failed canceled } model Organization { id BigInt @id @default(autoincrement()) @db.UnsignedBigInt externalId String @unique @default(uuid()) @db.Char(36) name String @db.VarChar(191) slug String @unique @db.VarChar(191) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt users User[] programs Program[] providers AiProvider[] } model User { id BigInt @id @default(autoincrement()) @db.UnsignedBigInt externalId String @unique @default(uuid()) @db.Char(36) organizationId BigInt? @db.UnsignedBigInt email String? @unique(map: "uq_users_email") @db.VarChar(191) phone String? @unique(map: "uq_users_phone") @db.VarChar(32) status UserStatus @default(active) locale String @default("fa-IR") @db.VarChar(16) timezone String @default("Asia/Tehran") @db.VarChar(64) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt deletedAt DateTime? organization Organization? @relation(fields: [organizationId], references: [id], onDelete: SetNull) profile UserProfile? authIdentities AuthIdentity[] authSessions AuthSession[] userRoles UserRole[] userConsents UserConsent[] assessmentAssignments AssessmentAssignment[] attempts AssessmentAttempt[] enrollments ProgramEnrollment[] journeyState UserJourneyState? dailyCheckins DailyCheckin[] journeyEvents UserJourneyEvent[] dailySummaries DailyActivitySummary[] aiRuns AiAnalysisRun[] aiMemories AiMemory[] chatSessions ChatSession[] chatMessages ChatMessage[] sessionSummaries SessionSummary[] feedbacks AiFeedback[] notificationQueue NotificationQueue[] behaviorLogs BehaviorLog[] auditLogs AuditLog[] assessmentComparisons AssessmentComparison[] @@index([organizationId], map: "idx_users_organization_id") @@index([createdAt], map: "idx_users_created_at") } model AuthIdentity { id BigInt @id @default(autoincrement()) @db.UnsignedBigInt userId BigInt @db.UnsignedBigInt provider AuthProvider providerUserId String @db.VarChar(191) providerEmail String? @db.VarChar(191) metadata Json? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@unique([provider, providerUserId], map: "uq_auth_identities_provider_provider_user_id") @@index([userId], map: "idx_auth_identities_user_id") } model AuthSession { id BigInt @id @default(autoincrement()) @db.UnsignedBigInt userId BigInt @db.UnsignedBigInt refreshTokenHash String @unique @db.VarChar(255) status SessionStatus @default(active) ipAddress String? @db.VarChar(45) userAgent String? @db.VarChar(512) expiresAt DateTime revokedAt DateTime? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@index([userId, status], map: "idx_auth_sessions_user_id_status") @@index([expiresAt], map: "idx_auth_sessions_expires_at") } model Role { id BigInt @id @default(autoincrement()) @db.UnsignedBigInt code RoleCode @unique displayName String @db.VarChar(128) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt userRoles UserRole[] } model UserRole { userId BigInt @db.UnsignedBigInt roleId BigInt @db.UnsignedBigInt assignedAt DateTime @default(now()) assignedById BigInt? @db.UnsignedBigInt user User @relation(fields: [userId], references: [id], onDelete: Cascade) role Role @relation(fields: [roleId], references: [id], onDelete: Cascade) @@id([userId, roleId]) @@index([roleId], map: "idx_user_roles_role_id") } model UserProfile { userId BigInt @id @db.UnsignedBigInt firstName String? @db.VarChar(100) lastName String? @db.VarChar(100) displayName String? @db.VarChar(191) avatarUrl String? @db.VarChar(512) birthDate DateTime? gender String? @db.VarChar(32) countryCode String? @db.VarChar(8) city String? @db.VarChar(100) educationLevel String? @db.VarChar(100) occupation String? @db.VarChar(100) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt user User @relation(fields: [userId], references: [id], onDelete: Cascade) } model Consent { id BigInt @id @default(autoincrement()) @db.UnsignedBigInt key String @db.VarChar(100) policyVersion String @db.VarChar(32) title String @db.VarChar(191) body String @db.Text required Boolean @default(true) active Boolean @default(true) publishedAt DateTime? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt userConsents UserConsent[] @@unique([key, policyVersion], map: "uq_consents_key_policy_version") } model UserConsent { id BigInt @id @default(autoincrement()) @db.UnsignedBigInt userId BigInt @db.UnsignedBigInt consentId BigInt @db.UnsignedBigInt accepted Boolean @default(true) acceptedAt DateTime @default(now()) ipAddress String? @db.VarChar(45) userAgent String? @db.VarChar(512) user User @relation(fields: [userId], references: [id], onDelete: Cascade) consent Consent @relation(fields: [consentId], references: [id], onDelete: Cascade) @@unique([userId, consentId], map: "uq_user_consents_user_id_consent_id") @@index([consentId], map: "idx_user_consents_consent_id") } model AssessmentScale { id BigInt @id @default(autoincrement()) @db.UnsignedBigInt key String @unique @db.VarChar(64) title String @db.VarChar(191) description String? @db.Text createdAt DateTime @default(now()) updatedAt DateTime @updatedAt versions AssessmentScaleVersion[] } model AssessmentScaleVersion { id BigInt @id @default(autoincrement()) @db.UnsignedBigInt scaleId BigInt @db.UnsignedBigInt version String @db.VarChar(32) isActive Boolean @default(false) scoringMethod String @db.VarChar(64) metadata Json? effectiveFrom DateTime? effectiveTo DateTime? createdAt DateTime @default(now()) scale AssessmentScale @relation(fields: [scaleId], references: [id], onDelete: Cascade) questions AssessmentQuestion[] attempts AssessmentAttempt[] assignments AssessmentAssignment[] @@unique([scaleId, version], map: "uq_assessment_scale_versions_scale_id_version") @@index([scaleId, isActive], map: "idx_assessment_scale_versions_scale_id_is_active") } model AssessmentQuestion { id BigInt @id @default(autoincrement()) @db.UnsignedBigInt scaleVersionId BigInt @db.UnsignedBigInt code String @db.VarChar(64) orderNo Int prompt String @db.Text reverseScored Boolean @default(false) createdAt DateTime @default(now()) scaleVersion AssessmentScaleVersion @relation(fields: [scaleVersionId], references: [id], onDelete: Cascade) options AssessmentOption[] answers AssessmentAnswer[] @@unique([scaleVersionId, code], map: "uq_assessment_questions_scale_version_id_code") @@unique([scaleVersionId, orderNo], map: "uq_assessment_questions_scale_version_id_order_no") } model AssessmentOption { id BigInt @id @default(autoincrement()) @db.UnsignedBigInt questionId BigInt @db.UnsignedBigInt value Int label String @db.VarChar(191) points Int orderNo Int question AssessmentQuestion @relation(fields: [questionId], references: [id], onDelete: Cascade) @@unique([questionId, value], map: "uq_assessment_options_question_id_value") @@unique([questionId, orderNo], map: "uq_assessment_options_question_id_order_no") } model AssessmentAttempt { id BigInt @id @default(autoincrement()) @db.UnsignedBigInt userId BigInt @db.UnsignedBigInt scaleVersionId BigInt @db.UnsignedBigInt enrollmentId BigInt? @db.UnsignedBigInt assignmentId BigInt? @db.UnsignedBigInt phase AttemptPhase status AttemptStatus @default(in_progress) startedAt DateTime @default(now()) submittedAt DateTime? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt user User @relation(fields: [userId], references: [id], onDelete: Cascade) scaleVersion AssessmentScaleVersion @relation(fields: [scaleVersionId], references: [id], onDelete: Restrict) enrollment ProgramEnrollment? @relation(fields: [enrollmentId], references: [id], onDelete: SetNull) assignment AssessmentAssignment? @relation(fields: [assignmentId], references: [id], onDelete: SetNull) answers AssessmentAnswer[] scores AssessmentScore[] comparisonsAsPre AssessmentComparison[] @relation("ComparisonPreAttempt") comparisonsAsPost AssessmentComparison[] @relation("ComparisonPostAttempt") @@index([userId, phase, createdAt], map: "idx_assessment_attempts_user_id_phase_created_at") @@index([enrollmentId], map: "idx_assessment_attempts_enrollment_id") @@index([assignmentId], map: "idx_assessment_attempts_assignment_id") } model AssessmentAssignment { id BigInt @id @default(autoincrement()) @db.UnsignedBigInt userId BigInt @db.UnsignedBigInt enrollmentId BigInt? @db.UnsignedBigInt scaleVersionId BigInt @db.UnsignedBigInt phase AttemptPhase status AssessmentAssignmentStatus @default(assigned) eligibleFrom DateTime? dueAt DateTime? attemptLimit Int? attemptsUsed Int @default(0) completedAt DateTime? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt user User @relation(fields: [userId], references: [id], onDelete: Cascade) enrollment ProgramEnrollment? @relation(fields: [enrollmentId], references: [id], onDelete: SetNull) scaleVersion AssessmentScaleVersion @relation(fields: [scaleVersionId], references: [id], onDelete: Restrict) attempts AssessmentAttempt[] @@index([userId, phase, status], map: "idx_assessment_assignments_user_phase_status") @@index([enrollmentId], map: "idx_assessment_assignments_enrollment_id") @@index([dueAt], map: "idx_assessment_assignments_due_at") @@map("assessment_assignments") } model AssessmentAnswer { id BigInt @id @default(autoincrement()) @db.UnsignedBigInt attemptId BigInt @db.UnsignedBigInt questionId BigInt @db.UnsignedBigInt selectedValue Int? answerText String? @db.Text points Int @default(0) createdAt DateTime @default(now()) attempt AssessmentAttempt @relation(fields: [attemptId], references: [id], onDelete: Cascade) question AssessmentQuestion @relation(fields: [questionId], references: [id], onDelete: Restrict) @@unique([attemptId, questionId], map: "uq_assessment_answers_attempt_id_question_id") } model AssessmentScore { id BigInt @id @default(autoincrement()) @db.UnsignedBigInt attemptId BigInt @db.UnsignedBigInt metricKey String @db.VarChar(100) rawScore Decimal @db.Decimal(10, 2) normalizedScore Decimal? @db.Decimal(10, 2) interpretation String? @db.Text createdAt DateTime @default(now()) attempt AssessmentAttempt @relation(fields: [attemptId], references: [id], onDelete: Cascade) @@unique([attemptId, metricKey], map: "uq_assessment_scores_attempt_id_metric_key") } model AssessmentComparison { id BigInt @id @default(autoincrement()) @db.UnsignedBigInt userId BigInt @db.UnsignedBigInt enrollmentId BigInt? @db.UnsignedBigInt preAttemptId BigInt @unique postAttemptId BigInt @unique summaryText String? @db.Text deltaJson Json? createdAt DateTime @default(now()) user User @relation(fields: [userId], references: [id], onDelete: Cascade) enrollment ProgramEnrollment? @relation(fields: [enrollmentId], references: [id], onDelete: SetNull) preAttempt AssessmentAttempt @relation("ComparisonPreAttempt", fields: [preAttemptId], references: [id], onDelete: Restrict) postAttempt AssessmentAttempt @relation("ComparisonPostAttempt", fields: [postAttemptId], references: [id], onDelete: Restrict) @@index([userId, createdAt], map: "idx_assessment_comparisons_user_id_created_at") } model Program { id BigInt @id @default(autoincrement()) @db.UnsignedBigInt organizationId BigInt? @db.UnsignedBigInt key String @unique @db.VarChar(64) title String @db.VarChar(191) description String? @db.Text createdAt DateTime @default(now()) updatedAt DateTime @updatedAt organization Organization? @relation(fields: [organizationId], references: [id], onDelete: SetNull) versions ProgramVersion[] } model ProgramVersion { id BigInt @id @default(autoincrement()) @db.UnsignedBigInt programId BigInt @db.UnsignedBigInt version String @db.VarChar(32) isActive Boolean @default(false) totalDays Int @default(0) metadata Json? createdAt DateTime @default(now()) program Program @relation(fields: [programId], references: [id], onDelete: Cascade) modules Module[] enrollments ProgramEnrollment[] chatSessions ChatSession[] journeyStates UserJourneyState[] @@unique([programId, version], map: "uq_program_versions_program_id_version") } model Module { id BigInt @id @default(autoincrement()) @db.UnsignedBigInt programVersionId BigInt @db.UnsignedBigInt code String @db.VarChar(64) title String @db.VarChar(191) summary String? @db.Text sequenceNo Int createdAt DateTime @default(now()) updatedAt DateTime @updatedAt programVersion ProgramVersion @relation(fields: [programVersionId], references: [id], onDelete: Cascade) lessons Lesson[] journeyStates UserJourneyState[] chatSessions ChatSession[] @@unique([programVersionId, code], map: "uq_modules_program_version_id_code") @@unique([programVersionId, sequenceNo], map: "uq_modules_program_version_id_sequence_no") @@unique([id, programVersionId], map: "uq_modules_id_program_version_id") @@map("modules") } model Lesson { id BigInt @id @default(autoincrement()) @db.UnsignedBigInt moduleId BigInt @db.UnsignedBigInt code String @db.VarChar(64) title String @db.VarChar(191) summary String? @db.Text dayNumber Int sequenceNo Int unlockRuleType String @db.VarChar(64) estimatedMinutes Int? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt module Module @relation(fields: [moduleId], references: [id], onDelete: Cascade) contents LessonContent[] exercises Exercise[] unlocks UserLessonUnlock[] progresses UserLessonProgress[] chatSessions ChatSession[] aiMemories AiMemory[] journeyStates UserJourneyState[] sessionSummaries SessionSummary[] @@unique([moduleId, code], map: "uq_lessons_module_id_code") @@unique([moduleId, sequenceNo], map: "uq_lessons_module_id_sequence_no") @@unique([id, moduleId], map: "uq_lessons_id_module_id") } model LessonContent { id BigInt @id @default(autoincrement()) @db.UnsignedBigInt lessonId BigInt @db.UnsignedBigInt contentType String @db.VarChar(32) title String? @db.VarChar(191) body String? @db.LongText mediaUrl String? @db.VarChar(512) orderNo Int metadata Json? createdAt DateTime @default(now()) lesson Lesson @relation(fields: [lessonId], references: [id], onDelete: Cascade) @@unique([lessonId, orderNo], map: "uq_lesson_contents_lesson_id_order_no") } model Exercise { id BigInt @id @default(autoincrement()) @db.UnsignedBigInt lessonId BigInt @db.UnsignedBigInt code String @db.VarChar(64) title String @db.VarChar(191) instructions String? @db.Text required Boolean @default(true) orderNo Int createdAt DateTime @default(now()) updatedAt DateTime @updatedAt lesson Lesson @relation(fields: [lessonId], references: [id], onDelete: Cascade) prompts ExercisePrompt[] submissions UserExerciseSubmission[] @@unique([lessonId, code], map: "uq_exercises_lesson_id_code") @@unique([lessonId, orderNo], map: "uq_exercises_lesson_id_order_no") } model ExercisePrompt { id BigInt @id @default(autoincrement()) @db.UnsignedBigInt exerciseId BigInt @db.UnsignedBigInt code String @db.VarChar(64) promptText String @db.Text inputType String @db.VarChar(32) orderNo Int required Boolean @default(true) exercise Exercise @relation(fields: [exerciseId], references: [id], onDelete: Cascade) answers UserExerciseAnswer[] @@unique([exerciseId, code], map: "uq_exercise_prompts_exercise_id_code") @@unique([exerciseId, orderNo], map: "uq_exercise_prompts_exercise_id_order_no") } model ProgramEnrollment { id BigInt @id @default(autoincrement()) @db.UnsignedBigInt userId BigInt @db.UnsignedBigInt programVersionId BigInt @db.UnsignedBigInt status EnrollmentStatus @default(active) startedAt DateTime @default(now()) endedAt DateTime? currentDay Int @default(0) metadata Json? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt user User @relation(fields: [userId], references: [id], onDelete: Cascade) programVersion ProgramVersion @relation(fields: [programVersionId], references: [id], onDelete: Restrict) attempts AssessmentAttempt[] assessmentAssignments AssessmentAssignment[] comparisons AssessmentComparison[] lessonUnlocks UserLessonUnlock[] lessonProgresses UserLessonProgress[] exerciseSubmissions UserExerciseSubmission[] chatSessions ChatSession[] aiRuns AiAnalysisRun[] journeyEvents UserJourneyEvent[] journeyStates UserJourneyState[] dailyCheckins DailyCheckin[] aiMemories AiMemory[] sessionSummaries SessionSummary[] notificationQueue NotificationQueue[] @@unique([userId, programVersionId, startedAt], map: "uq_user_enrollments_user_program_started") @@unique([id, userId, programVersionId], map: "uq_program_enrollments_id_user_program_version_id") @@index([userId, status], map: "idx_user_program_enrollments_user_id_status") @@map("program_enrollments") } model UserJourneyState { userId BigInt @id @db.UnsignedBigInt enrollmentId BigInt? @db.UnsignedBigInt currentStep JourneyStep @default(registered) currentProgramVersionId BigInt? @db.UnsignedBigInt currentModuleId BigInt? @db.UnsignedBigInt currentLessonId BigInt? @db.UnsignedBigInt nextUnlockAt DateTime? finishedAt DateTime? lastActivityAt DateTime @default(now()) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt user User @relation(fields: [userId], references: [id], onDelete: Cascade) enrollment ProgramEnrollment? @relation(fields: [enrollmentId], references: [id], onDelete: SetNull) currentProgramVersion ProgramVersion? @relation(fields: [currentProgramVersionId], references: [id], onDelete: SetNull) currentModule Module? @relation(fields: [currentModuleId], references: [id], onDelete: SetNull) currentLesson Lesson? @relation(fields: [currentLessonId], references: [id], onDelete: SetNull) @@index([enrollmentId], map: "idx_user_journey_state_enrollment_id") @@index([currentStep, nextUnlockAt], map: "idx_user_journey_state_step_next_unlock") @@map("user_journey_state") } model DailyCheckin { id BigInt @id @default(autoincrement()) @db.UnsignedBigInt userId BigInt @db.UnsignedBigInt enrollmentId BigInt? @db.UnsignedBigInt checkinDate DateTime @db.Date mood Int energy Int stress Int sleep Int note String? @db.Text source DailyCheckinSource @default(manual) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt user User @relation(fields: [userId], references: [id], onDelete: Cascade) enrollment ProgramEnrollment? @relation(fields: [enrollmentId], references: [id], onDelete: SetNull) @@unique([userId, checkinDate], map: "uq_daily_checkins_user_id_checkin_date") @@index([enrollmentId], map: "idx_daily_checkins_enrollment_id") @@map("daily_checkins") } model UserLessonUnlock { id BigInt @id @default(autoincrement()) @db.UnsignedBigInt enrollmentId BigInt @db.UnsignedBigInt lessonId BigInt @db.UnsignedBigInt reason UnlockReason scheduledAt DateTime? unlockedAt DateTime @default(now()) createdAt DateTime @default(now()) enrollment ProgramEnrollment @relation(fields: [enrollmentId], references: [id], onDelete: Cascade) lesson Lesson @relation(fields: [lessonId], references: [id], onDelete: Cascade) @@unique([enrollmentId, lessonId], map: "uq_user_lesson_unlocks_enrollment_id_lesson_id") @@index([lessonId], map: "idx_user_lesson_unlocks_lesson_id") } model UserLessonProgress { id BigInt @id @default(autoincrement()) @db.UnsignedBigInt enrollmentId BigInt @db.UnsignedBigInt lessonId BigInt @db.UnsignedBigInt status LessonProgressStatus @default(not_started) startedAt DateTime? completedAt DateTime? progressPercent Decimal @default(0) @db.Decimal(5, 2) updatedAt DateTime @updatedAt createdAt DateTime @default(now()) enrollment ProgramEnrollment @relation(fields: [enrollmentId], references: [id], onDelete: Cascade) lesson Lesson @relation(fields: [lessonId], references: [id], onDelete: Cascade) chatSessions ChatSession[] feedbacks AiFeedback[] @@unique([enrollmentId, lessonId], map: "uq_user_lesson_progress_enrollment_id_lesson_id") @@index([status, updatedAt], map: "idx_user_lesson_progress_status_updated_at") } model UserExerciseSubmission { id BigInt @id @default(autoincrement()) @db.UnsignedBigInt enrollmentId BigInt @db.UnsignedBigInt exerciseId BigInt @db.UnsignedBigInt attemptNo Int @default(1) status SubmissionStatus @default(draft) submittedAt DateTime? score Decimal? @db.Decimal(10, 2) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt enrollment ProgramEnrollment @relation(fields: [enrollmentId], references: [id], onDelete: Cascade) exercise Exercise @relation(fields: [exerciseId], references: [id], onDelete: Restrict) answers UserExerciseAnswer[] feedbacks AiFeedback[] @@unique([enrollmentId, exerciseId, attemptNo], map: "uq_exercise_submissions_enrollment_exercise_attempt") @@index([status, submittedAt], map: "idx_user_exercise_submissions_status_submitted_at") } model UserExerciseAnswer { id BigInt @id @default(autoincrement()) @db.UnsignedBigInt submissionId BigInt @db.UnsignedBigInt promptId BigInt @db.UnsignedBigInt answerText String? @db.LongText answerJson Json? createdAt DateTime @default(now()) submission UserExerciseSubmission @relation(fields: [submissionId], references: [id], onDelete: Cascade) prompt ExercisePrompt @relation(fields: [promptId], references: [id], onDelete: Restrict) @@unique([submissionId, promptId], map: "uq_user_exercise_answers_submission_id_prompt_id") } model UserJourneyEvent { id BigInt @id @default(autoincrement()) @db.UnsignedBigInt userId BigInt @db.UnsignedBigInt enrollmentId BigInt? @db.UnsignedBigInt eventName String @db.VarChar(100) source EventSource @default(system) eventAt DateTime @default(now()) payload Json? user User @relation(fields: [userId], references: [id], onDelete: Cascade) enrollment ProgramEnrollment? @relation(fields: [enrollmentId], references: [id], onDelete: SetNull) @@index([userId, eventAt], map: "idx_user_journey_events_user_id_event_at") @@index([eventName, eventAt], map: "idx_user_journey_events_event_name_event_at") } model DailyActivitySummary { id BigInt @id @default(autoincrement()) @db.UnsignedBigInt userId BigInt @db.UnsignedBigInt activityDate DateTime @db.Date lessonsCompleted Int @default(0) exercisesSubmitted Int @default(0) chatMessagesCount Int @default(0) totalStudyMinutes Int @default(0) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@unique([userId, activityDate], map: "uq_daily_activity_summaries_user_id_activity_date") } model AiProvider { id BigInt @id @default(autoincrement()) @db.UnsignedBigInt organizationId BigInt? @db.UnsignedBigInt key String @unique @db.VarChar(64) displayName String @db.VarChar(191) endpointBaseUrl String @db.VarChar(512) active Boolean @default(true) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt organization Organization? @relation(fields: [organizationId], references: [id], onDelete: SetNull) models AiModel[] runs AiAnalysisRun[] } model AiModel { id BigInt @id @default(autoincrement()) @db.UnsignedBigInt providerId BigInt @db.UnsignedBigInt modelKey String @db.VarChar(128) displayName String @db.VarChar(191) isDefault Boolean @default(false) maxInputTokens Int? maxOutputTokens Int? active Boolean @default(true) createdAt DateTime @default(now()) provider AiProvider @relation(fields: [providerId], references: [id], onDelete: Cascade) runs AiAnalysisRun[] @@unique([providerId, modelKey], map: "uq_ai_models_provider_id_model_key") } model AiPrompt { id BigInt @id @default(autoincrement()) @db.UnsignedBigInt key String @db.VarChar(100) version String @db.VarChar(32) promptText String @db.LongText active Boolean @default(true) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt runs AiAnalysisRun[] @@unique([key, version], map: "uq_ai_prompts_key_version") } model AiAnalysisRun { id BigInt @id @default(autoincrement()) @db.UnsignedBigInt userId BigInt @db.UnsignedBigInt enrollmentId BigInt? @db.UnsignedBigInt providerId BigInt @db.UnsignedBigInt modelId BigInt @db.UnsignedBigInt promptId BigInt? @db.UnsignedBigInt runType AiRunType status AiRunStatus @default(queued) inputRefType String? @db.VarChar(64) inputRefId BigInt? @db.UnsignedBigInt startedAt DateTime? finishedAt DateTime? latencyMs Int? totalTokens Int? errorCode String? @db.VarChar(64) errorMessage String? @db.Text createdAt DateTime @default(now()) updatedAt DateTime @updatedAt user User @relation(fields: [userId], references: [id], onDelete: Cascade) enrollment ProgramEnrollment? @relation(fields: [enrollmentId], references: [id], onDelete: SetNull) provider AiProvider @relation(fields: [providerId], references: [id], onDelete: Restrict) model AiModel @relation(fields: [modelId], references: [id], onDelete: Restrict) prompt AiPrompt? @relation(fields: [promptId], references: [id], onDelete: SetNull) outputs AiAnalysisOutput[] aiFeedbacks AiFeedback[] aiMemories AiMemory[] sessionSummaries SessionSummary[] @@index([userId, createdAt], map: "idx_ai_analysis_runs_user_id_created_at") @@index([status, createdAt], map: "idx_ai_analysis_runs_status_created_at") } model AiAnalysisOutput { id BigInt @id @default(autoincrement()) @db.UnsignedBigInt runId BigInt @db.UnsignedBigInt outputType String @db.VarChar(64) outputText String? @db.LongText outputJson Json? createdAt DateTime @default(now()) run AiAnalysisRun @relation(fields: [runId], references: [id], onDelete: Cascade) @@index([runId], map: "idx_ai_analysis_outputs_run_id") } model AiMemory { id BigInt @id @default(autoincrement()) @db.UnsignedBigInt userId BigInt @db.UnsignedBigInt enrollmentId BigInt? @db.UnsignedBigInt lessonId BigInt? @db.UnsignedBigInt sourceRunId BigInt? @db.UnsignedBigInt memoryType String @db.VarChar(64) summaryText String @db.Text summaryJson Json? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt user User @relation(fields: [userId], references: [id], onDelete: Cascade) enrollment ProgramEnrollment? @relation(fields: [enrollmentId], references: [id], onDelete: SetNull) lesson Lesson? @relation(fields: [lessonId], references: [id], onDelete: SetNull) sourceRun AiAnalysisRun? @relation(fields: [sourceRunId], references: [id], onDelete: SetNull) @@index([userId, createdAt], map: "idx_ai_memories_user_id_created_at") @@index([enrollmentId], map: "idx_ai_memories_enrollment_id") @@map("ai_memories") } model SessionSummary { id BigInt @id @default(autoincrement()) @db.UnsignedBigInt userId BigInt @db.UnsignedBigInt enrollmentId BigInt @db.UnsignedBigInt lessonId BigInt @db.UnsignedBigInt sessionId BigInt @unique @db.UnsignedBigInt sourceRunId BigInt? @db.UnsignedBigInt summaryText String @db.Text summaryJson Json? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt user User @relation(fields: [userId], references: [id], onDelete: Cascade) enrollment ProgramEnrollment @relation(fields: [enrollmentId], references: [id], onDelete: Cascade) lesson Lesson @relation(fields: [lessonId], references: [id], onDelete: Restrict) session ChatSession @relation(fields: [sessionId], references: [id], onDelete: Cascade) sourceRun AiAnalysisRun? @relation(fields: [sourceRunId], references: [id], onDelete: SetNull) @@index([userId, createdAt], map: "idx_session_summaries_user_id_created_at") @@index([enrollmentId, lessonId], map: "idx_session_summaries_enrollment_id_lesson_id") @@map("session_summaries") } model ChatSession { id BigInt @id @default(autoincrement()) @db.UnsignedBigInt userId BigInt @db.UnsignedBigInt enrollmentId BigInt @db.UnsignedBigInt programVersionId BigInt @db.UnsignedBigInt moduleId BigInt @db.UnsignedBigInt lessonId BigInt @db.UnsignedBigInt lessonProgressId BigInt? @db.UnsignedBigInt title String? @db.VarChar(191) startedAt DateTime @default(now()) endedAt DateTime? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt user User @relation(fields: [userId], references: [id], onDelete: Cascade) enrollment ProgramEnrollment @relation(fields: [enrollmentId, userId, programVersionId], references: [id, userId, programVersionId], onDelete: Cascade) programVersion ProgramVersion @relation(fields: [programVersionId], references: [id], onDelete: Restrict) module Module @relation(fields: [moduleId, programVersionId], references: [id, programVersionId], onDelete: Restrict) lesson Lesson @relation(fields: [lessonId, moduleId], references: [id, moduleId], onDelete: Restrict) lessonProgress UserLessonProgress? @relation(fields: [lessonProgressId], references: [id], onDelete: SetNull) messages ChatMessage[] summary SessionSummary? @@index([userId, startedAt], map: "idx_chat_sessions_user_id_started_at") @@index([userId, enrollmentId], map: "idx_chat_sessions_user_id_enrollment_id") @@index([enrollmentId, lessonId], map: "idx_chat_sessions_enrollment_id_lesson_id") } model ChatMessage { id BigInt @id @default(autoincrement()) @db.UnsignedBigInt sessionId BigInt @db.UnsignedBigInt userId BigInt? @db.UnsignedBigInt role ChatRole content String @db.LongText tokenCount Int? moderationFlag Boolean @default(false) createdAt DateTime @default(now()) session ChatSession @relation(fields: [sessionId], references: [id], onDelete: Cascade) user User? @relation(fields: [userId], references: [id], onDelete: SetNull) @@index([sessionId, createdAt], map: "idx_chat_messages_session_id_created_at") @@index([userId, createdAt], map: "idx_chat_messages_user_id_created_at") } model AiFeedback { id BigInt @id @default(autoincrement()) @db.UnsignedBigInt userId BigInt @db.UnsignedBigInt lessonProgressId BigInt? @db.UnsignedBigInt submissionId BigInt? @db.UnsignedBigInt runId BigInt? @db.UnsignedBigInt feedbackType String @db.VarChar(64) summary String? @db.Text feedbackJson Json? createdAt DateTime @default(now()) user User @relation(fields: [userId], references: [id], onDelete: Cascade) lessonProgress UserLessonProgress? @relation(fields: [lessonProgressId], references: [id], onDelete: SetNull) submission UserExerciseSubmission? @relation(fields: [submissionId], references: [id], onDelete: SetNull) run AiAnalysisRun? @relation(fields: [runId], references: [id], onDelete: SetNull) @@index([userId, createdAt], map: "idx_ai_feedback_user_id_created_at") } model NotificationQueue { id BigInt @id @default(autoincrement()) @db.UnsignedBigInt userId BigInt @db.UnsignedBigInt enrollmentId BigInt? @db.UnsignedBigInt type String @db.VarChar(100) title String @db.VarChar(191) body String @db.Text payload Json? scheduledAt DateTime status NotificationQueueStatus @default(pending) sentAt DateTime? attemptCount Int @default(0) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt user User @relation(fields: [userId], references: [id], onDelete: Cascade) enrollment ProgramEnrollment? @relation(fields: [enrollmentId], references: [id], onDelete: SetNull) @@index([userId, status, scheduledAt], map: "idx_notification_queue_user_status_scheduled") @@index([status, scheduledAt], map: "idx_notification_queue_status_scheduled") @@map("notification_queue") } model BehaviorLog { id BigInt @id @default(autoincrement()) @db.UnsignedBigInt userId BigInt? @db.UnsignedBigInt eventName String @db.VarChar(100) source String @db.VarChar(64) payload Json? ipAddress String? @db.VarChar(45) userAgent String? @db.VarChar(512) createdAt DateTime @default(now()) user User? @relation(fields: [userId], references: [id], onDelete: SetNull) @@index([eventName, createdAt], map: "idx_behavior_logs_event_name_created_at") @@index([userId, createdAt], map: "idx_behavior_logs_user_id_created_at") } model AuditLog { id BigInt @id @default(autoincrement()) @db.UnsignedBigInt actorUserId BigInt? @db.UnsignedBigInt action String @db.VarChar(100) resourceType String @db.VarChar(100) resourceId String? @db.VarChar(191) metadata Json? createdAt DateTime @default(now()) actor User? @relation(fields: [actorUserId], references: [id], onDelete: SetNull) @@index([action, createdAt], map: "idx_audit_logs_action_created_at") @@index([actorUserId, createdAt], map: "idx_audit_logs_actor_user_id_created_at") } model AdminReportSnapshot { id BigInt @id @default(autoincrement()) @db.UnsignedBigInt reportDate DateTime @db.Date metricKey String @db.VarChar(100) metricValue Decimal @db.Decimal(16, 4) dimensions Json? createdAt DateTime @default(now()) @@index([reportDate, metricKey], map: "idx_admin_report_snapshots_report_date_metric_key") }