// CompetenceTrack — Full Database Schema // SQLite via Prisma ORM | UUIDs for portability | UTC timestamps generator client { provider = "prisma-client-js" } datasource db { provider = "sqlite" url = env("DATABASE_URL") } // ─── Core Organizational Entities ─────────────────────────────────── model School { id String @id @default(cuid()) name String schoolType String // ELEMENTARY, MIDDLE, GYMNASIUM, OTHER country String @default("DE") timezone String @default("Europe/Berlin") createdAt DateTime @default(now()) updatedAt DateTime @updatedAt // School branding fields logoUrl String? // school logo URL primaryColor String? @default("#10b981") // primary brand color secondaryColor String? @default("#14b8a6") // secondary brand color accentColor String? @default("#059669") // accent brand color fontFamily String? @default("Inter") // preferred font customCss String? // custom CSS overrides motto String? // school motto websiteUrl String? // school website URL emailDomain String? // school email domain (e.g., "@schule.de") address String? // school address phone String? // school phone number users User[] schoolYears SchoolYear[] classGroups ClassGroup[] students Student[] subjects Subject[] competencyTemplates CompetencyTemplate[] gradingSchemes GradingScheme[] auditLogs AuditLog[] dataExportRequests DataExportRequest[] behaviorCategories BehaviorCategory[] behaviorIncidents BehaviorIncident[] rubrics Rubric[] commentCategories CommentCategory[] commentBankEntries CommentBankEntry[] notebooks Notebook[] drawings Drawing[] notifications Notification[] calendarEvents CalendarEvent[] parentStudentLinks ParentStudentLink[] curriculumStandards CurriculumStandard[] backups Backup[] behaviorInterventions BehaviorIntervention[] reportSchedules ReportSchedule[] homeworks Homework[] announcements Announcement[] selfAssessments SelfAssessment[] learningGoals LearningGoal[] portfolioEntries PortfolioEntry[] timetableSlots TimetableSlot[] resources Resource[] emailTemplates EmailTemplate[] emailLogs EmailLog[] peerAssessments PeerAssessment[] emergencyContacts EmergencyContact[] schoolEvents SchoolEvent[] studentTransports StudentTransport[] healthRecords HealthRecord[] badges Badge[] studentBadges StudentBadge[] newsletters Newsletter[] district SchoolDistrict? @relation(fields: [districtId], references: [id]) districtId String? } model User { id String @id @default(cuid()) schoolId String? email String @unique passwordHash String firstName String lastName String role String @default("TEACHER") // TEACHER, SCHOOL_ADMIN, SUPER_ADMIN, STUDENT, PARENT locale String @default("de") twoFactorSecret String? isDemo Boolean @default(false) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt deletedAt DateTime? school School? @relation(fields: [schoolId], references: [id]) classGroupTeachers ClassGroupTeacher[] learningProgressEntries LearningProgressEntry[] assessments Assessment[] generatedReports Report[] auditLogs AuditLog[] dataExportRequests DataExportRequest[] teacherNotes TeacherNote[] attendanceSessions AttendanceSession[] lessonPlans LessonPlan[] parentMessagesSent ParentMessage[] behaviorIncidentsLogged BehaviorIncident[] @relation("BehaviorLogger") behaviorIncidentsResolved BehaviorIncident[] @relation("BehaviorResolver") rubrics Rubric[] commentBankEntries CommentBankEntry[] notebooks Notebook[] drawings Drawing[] notifications Notification[] calendarEvents CalendarEvent[] parentLinks ParentStudentLink[] assignedInterventions BehaviorIntervention[] @relation("InterventionAssignee") homeworks Homework[] announcements Announcement[] timetableSlots TimetableSlot[] resources Resource[] organizedSchoolEvents SchoolEvent[] eventRegistrations EventRegistration[] awardedBadges StudentBadge[] newsletters Newsletter[] } model SchoolYear { id String @id @default(cuid()) schoolId String label String // e.g. "2025/2026" startDate DateTime endDate DateTime createdAt DateTime @default(now()) updatedAt DateTime @updatedAt school School @relation(fields: [schoolId], references: [id]) classGroups ClassGroup[] enrollments Enrollment[] computedGrades ComputedGrade[] reports Report[] } model ClassGroup { id String @id @default(cuid()) schoolId String schoolYearId String name String // e.g. "3a" gradeLevel Int schoolType String @default("ELEMENTARY") seatingOrder String? // JSON array of student IDs createdAt DateTime @default(now()) updatedAt DateTime @updatedAt school School @relation(fields: [schoolId], references: [id]) schoolYear SchoolYear @relation(fields: [schoolYearId], references: [id]) teachers ClassGroupTeacher[] enrollments Enrollment[] classCompetencyAssignments ClassCompetencyAssignment[] assessments Assessment[] learningProgressEntries LearningProgressEntry[] gradingSchemes GradingScheme[] computedGrades ComputedGrade[] reports Report[] attendanceSessions AttendanceSession[] lessonPlans LessonPlan[] behaviorIncidents BehaviorIncident[] notebooks Notebook[] drawings Drawing[] calendarEvents CalendarEvent[] reportSchedules ReportSchedule[] homeworks Homework[] announcements Announcement[] selfAssessments SelfAssessment[] timetableSlots TimetableSlot[] resources Resource[] peerAssessments PeerAssessment[] schoolEvents SchoolEvent[] } model ClassGroupTeacher { id String @id @default(cuid()) classGroupId String userId String role String @default("SUBJECT_TEACHER") // HOMEROOM_TEACHER, SUBJECT_TEACHER classGroup ClassGroup @relation(fields: [classGroupId], references: [id]) user User @relation(fields: [userId], references: [id]) @@unique([classGroupId, userId]) } model Student { id String @id @default(cuid()) schoolId String firstName String lastName String dateOfBirth DateTime? externalId String? // for import matching avatarUrl String? // student photo/avatar URL avatarInitials String? // computed from firstName + lastName first chars createdAt DateTime @default(now()) updatedAt DateTime @updatedAt deletedAt DateTime? school School @relation(fields: [schoolId], references: [id]) enrollments Enrollment[] learningProgressEntries LearningProgressEntry[] assessmentResults AssessmentResult[] computedGrades ComputedGrade[] reports Report[] teacherNotes TeacherNote[] attendanceRecords AttendanceRecord[] parentContacts ParentContact[] parentMessages ParentMessage[] behaviorIncidents BehaviorIncident[] behaviorInterventions BehaviorIntervention[] parentStudentLinks ParentStudentLink[] homeworkSubmissions HomeworkSubmission[] selfAssessments SelfAssessment[] learningGoals LearningGoal[] portfolioEntries PortfolioEntry[] peerAssessmentsGiven PeerAssessment[] @relation("PeerAssessor") peerAssessmentsReceived PeerAssessment[] @relation("PeerAssessed") emergencyContacts EmergencyContact[] transports StudentTransport[] healthRecords HealthRecord[] studentBadges StudentBadge[] } model Enrollment { id String @id @default(cuid()) studentId String classGroupId String schoolYearId String startDate DateTime endDate DateTime? // null = still enrolled student Student @relation(fields: [studentId], references: [id]) classGroup ClassGroup @relation(fields: [classGroupId], references: [id]) schoolYear SchoolYear @relation(fields: [schoolYearId], references: [id]) } // ─── Subject & Competency Structure ──────────────────────────────── model Subject { id String @id @default(cuid()) schoolId String? // null = global/template name String gradeLevelMin Int @default(1) gradeLevelMax Int @default(13) school School? @relation(fields: [schoolId], references: [id]) competencyTemplates CompetencyTemplate[] assessments Assessment[] classCompetencyAssignments ClassCompetencyAssignment[] gradingSchemes GradingScheme[] computedGrades ComputedGrade[] attendanceSessions AttendanceSession[] lessonPlans LessonPlan[] rubrics Rubric[] commentBankEntries CommentBankEntry[] notebooks Notebook[] drawings Drawing[] calendarEvents CalendarEvent[] curriculumStandards CurriculumStandard[] homeworks Homework[] timetableSlots TimetableSlot[] resources Resource[] } model CompetencyTemplate { id String @id @default(cuid()) name String description String? subjectId String? schoolType String @default("ELEMENTARY") gradeLevelMin Int @default(1) gradeLevelMax Int @default(4) isGlobalTemplate Boolean @default(false) createdByUserId String? version Int @default(1) schoolId String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt subject Subject? @relation(fields: [subjectId], references: [id]) school School? @relation(fields: [schoolId], references: [id]) categories CompetencyCategory[] classCompetencyAssignments ClassCompetencyAssignment[] } model CompetencyCategory { id String @id @default(cuid()) competencyTemplateId String name String order Int @default(0) color String? // hex color for chart petals competencyTemplate CompetencyTemplate @relation(fields: [competencyTemplateId], references: [id]) competencies Competency[] reportSections ReportSection[] } model Competency { id String @id @default(cuid()) categoryId String code String // e.g. "D.3.LES.01" title String description String? order Int @default(0) category CompetencyCategory @relation(fields: [categoryId], references: [id]) masteryLevelDefinitions MasteryLevelDefinition[] learningProgressEntries LearningProgressEntry[] assessmentCompetencyLinks AssessmentCompetencyLink[] curriculumStandardLinks CurriculumStandardLink[] selfAssessments SelfAssessment[] learningGoals LearningGoal[] portfolioEntries PortfolioEntry[] peerAssessments PeerAssessment[] } model MasteryLevelDefinition { id String @id @default(cuid()) competencyId String levelValue Int // e.g. 1-4 label String // e.g. "noch nicht erreicht", "entwickelt sich", "kompetent", "weit entwickelt" description String? competency Competency @relation(fields: [competencyId], references: [id]) } model ClassCompetencyAssignment { id String @id @default(cuid()) classGroupId String subjectId String competencyTemplateId String schoolYearId String clonedTemplateId String? // if customized, points to the school's cloned copy classGroup ClassGroup @relation(fields: [classGroupId], references: [id]) subject Subject @relation(fields: [subjectId], references: [id]) competencyTemplate CompetencyTemplate @relation(fields: [competencyTemplateId], references: [id]) @@unique([classGroupId, subjectId, schoolYearId]) } // ─── Progress Tracking & Assessment ──────────────────────────────── model LearningProgressEntry { id String @id @default(cuid()) studentId String competencyId String teacherId String classGroupId String date DateTime masteryLevelValue Int note String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt student Student @relation(fields: [studentId], references: [id]) competency Competency @relation(fields: [competencyId], references: [id]) teacher User @relation(fields: [teacherId], references: [id]) classGroup ClassGroup @relation(fields: [classGroupId], references: [id]) } model Assessment { id String @id @default(cuid()) classGroupId String subjectId String teacherId String title String date DateTime type String @default("TEST") // TEST, ORAL, PROJECT, HOMEWORK, OTHER maxScore Float? weight Float @default(1.0) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt classGroup ClassGroup @relation(fields: [classGroupId], references: [id]) subject Subject @relation(fields: [subjectId], references: [id]) teacher User @relation(fields: [teacherId], references: [id]) assessmentCompetencyLinks AssessmentCompetencyLink[] assessmentResults AssessmentResult[] } model AssessmentCompetencyLink { id String @id @default(cuid()) assessmentId String competencyId String weight Float @default(1.0) assessment Assessment @relation(fields: [assessmentId], references: [id]) competency Competency @relation(fields: [competencyId], references: [id]) @@unique([assessmentId, competencyId]) } model AssessmentResult { id String @id @default(cuid()) assessmentId String studentId String score Float? masteryLevelValue Int? note String? assessment Assessment @relation(fields: [assessmentId], references: [id]) student Student @relation(fields: [studentId], references: [id]) @@unique([assessmentId, studentId]) } // ─── Grading ─────────────────────────────────────────────────────── model GradingScheme { id String @id @default(cuid()) classGroupId String? subjectId String? schoolId String? name String type String @default("NUMERIC_GRADE") // NUMERIC_GRADE, VERBAL_FEEDBACK, COMBINED scaleDefinition String // JSON: e.g. {"min":1,"max":6,"labels":{"1":"sehr gut","6":"ungenügend"}} classGroup ClassGroup? @relation(fields: [classGroupId], references: [id]) subject Subject? @relation(fields: [subjectId], references: [id]) school School? @relation(fields: [schoolId], references: [id]) gradingWeightRules GradingWeightRule[] } model GradingWeightRule { id String @id @default(cuid()) gradingSchemeId String sourceType String // LEARNING_PROGRESS, ASSESSMENT targetRef String? // competencyId, assessmentId, or categoryId weightPercent Float @default(50.0) gradingScheme GradingScheme @relation(fields: [gradingSchemeId], references: [id]) } model ComputedGrade { id String @id @default(cuid()) studentId String subjectId String classGroupId String schoolYearId String period String @default("Semester 1") computedValue Float computedAt DateTime @default(now()) isFinalized Boolean @default(false) overriddenValue Float? overrideReason String? student Student @relation(fields: [studentId], references: [id]) subject Subject @relation(fields: [subjectId], references: [id]) classGroup ClassGroup @relation(fields: [classGroupId], references: [id]) schoolYear SchoolYear @relation(fields: [schoolYearId], references: [id]) } // ─── Reporting ───────────────────────────────────────────────────── model Report { id String @id @default(cuid()) studentId String classGroupId String schoolYearId String period String generatedByUserId String generatedAt DateTime @default(now()) status String @default("DRAFT") // DRAFT, FINAL pdfFilePath String? includesGrades Boolean @default(false) student Student @relation(fields: [studentId], references: [id]) classGroup ClassGroup @relation(fields: [classGroupId], references: [id]) schoolYear SchoolYear @relation(fields: [schoolYearId], references: [id]) generatedByUser User @relation(fields: [generatedByUserId], references: [id]) sections ReportSection[] } model ReportSection { id String @id @default(cuid()) reportId String competencyCategoryId String? generatedText String order Int @default(0) report Report @relation(fields: [reportId], references: [id]) competencyCategory CompetencyCategory? @relation(fields: [competencyCategoryId], references: [id]) } // ─── Auditing & Compliance ───────────────────────────────────────── model AuditLog { id String @id @default(cuid()) userId String? schoolId String? action String entityType String entityId String? changes String? // JSON: { before: {...}, after: {...} } ipAddress String? userAgent String? timestamp DateTime @default(now()) metadata String? // JSON user User? @relation(fields: [userId], references: [id]) school School? @relation(fields: [schoolId], references: [id]) } model DataExportRequest { id String @id @default(cuid()) requestedByUserId String schoolId String? scope String // STUDENT, CLASS, SCHOOL scopeId String? // studentId, classGroupId, or schoolId status String @default("PENDING") // PENDING, PROCESSING, COMPLETED, FAILED filePath String? requestedAt DateTime @default(now()) completedAt DateTime? requestedByUser User @relation(fields: [requestedByUserId], references: [id]) school School? @relation(fields: [schoolId], references: [id]) } // ─── Attendance Tracking ──────────────────────────────────────────── model AttendanceSession { id String @id @default(cuid()) classGroupId String teacherId String date DateTime subjectId String? period String? // e.g. "1. Stunde", "Morning" status String @default("OPEN") // OPEN, COMPLETED note String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt classGroup ClassGroup @relation(fields: [classGroupId], references: [id]) teacher User @relation(fields: [teacherId], references: [id]) subject Subject? @relation(fields: [subjectId], references: [id]) records AttendanceRecord[] @@index([classGroupId, date]) } model AttendanceRecord { id String @id @default(cuid()) sessionId String studentId String status String @default("PRESENT") // PRESENT, ABSENT, EXCUSED, LATE arrivalTime String? // for LATE status, e.g. "08:25" comment String? session AttendanceSession @relation(fields: [sessionId], references: [id]) student Student @relation(fields: [studentId], references: [id]) @@unique([sessionId, studentId]) @@index([studentId]) } // ─── Teacher Notes ───────────────────────────────────────────────── model TeacherNote { id String @id @default(cuid()) studentId String teacherId String category String // GENERAL, BEHAVIOR, ACADEMIC, INTERVENTION, PARENT_CONTACT content String isPrivate Boolean @default(true) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt student Student @relation(fields: [studentId], references: [id]) teacher User @relation(fields: [teacherId], references: [id]) @@index([studentId]) @@index([teacherId]) } // ─── Lesson Planning ──────────────────────────────────────────────── model LessonPlan { id String @id @default(cuid()) teacherId String classGroupId String subjectId String? title String description String? date DateTime durationMin Int @default(45) status String @default("draft") // draft|scheduled|completed|cancelled objectives String? // JSON array of strings materials String? // JSON array of strings homework String? reflection String? linkedCompetencyIds String? // JSON array of competency IDs createdAt DateTime @default(now()) updatedAt DateTime @updatedAt teacher User @relation(fields: [teacherId], references: [id]) classGroup ClassGroup @relation(fields: [classGroupId], references: [id]) subject Subject? @relation(fields: [subjectId], references: [id]) @@index([teacherId]) @@index([classGroupId]) @@index([date]) } // ─── Parent Communication ────────────────────────────────────────── model ParentContact { id String @id @default(cuid()) studentId String firstName String lastName String email String? phone String? relationship String @default("parent") // parent|guardian|emergency preferredContact String @default("email") // email|phone|both preferredLanguage String @default("de") notes String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt student Student @relation(fields: [studentId], references: [id]) messages ParentMessage[] @@index([studentId]) @@unique([studentId, email]) } model ParentMessage { id String @id @default(cuid()) parentId String teacherId String studentId String subject String body String category String @default("general") // general|progress|assessment|behavior|attendance|event priority String @default("normal") // low|normal|high|urgent status String @default("sent") // draft|sent|delivered|read|replied readAt DateTime? reply String? replyAt DateTime? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt parent ParentContact @relation(fields: [parentId], references: [id]) teacher User @relation(fields: [teacherId], references: [id]) student Student @relation(fields: [studentId], references: [id]) @@index([teacherId]) @@index([parentId]) @@index([studentId]) @@index([createdAt]) } // ─── Behavior Tracking ───────────────────────────────────────────── model BehaviorCategory { id String @id @default(cuid()) schoolId String school School @relation(fields: [schoolId], references: [id]) name String color String // hex color, e.g. "#10b981" valence String // "positive" | "negative" | "neutral" icon String? // emoji createdAt DateTime @default(now()) updatedAt DateTime @updatedAt incidents BehaviorIncident[] @@unique([schoolId, name]) @@index([schoolId]) } model BehaviorIncident { id String @id @default(cuid()) studentId String student Student @relation(fields: [studentId], references: [id]) teacherId String teacher User @relation("BehaviorLogger", fields: [teacherId], references: [id]) classGroupId String? classGroup ClassGroup? @relation(fields: [classGroupId], references: [id]) schoolId String school School @relation(fields: [schoolId], references: [id]) categoryId String category BehaviorCategory @relation(fields: [categoryId], references: [id]) date DateTime severity String // "minor" | "moderate" | "major" description String location String? // e.g., "Klassenzimmer", "Pausenhof", "Flur" followUpAction String? // e.g., "Elterngespräch", "Nachsitzen", "Lob im Kreis" resolved Boolean @default(false) resolvedAt DateTime? resolvedById String? resolvedBy User? @relation("BehaviorResolver", fields: [resolvedById], references: [id]) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([studentId, date]) @@index([classGroupId, date]) @@index([schoolId, date]) @@index([teacherId]) interventions BehaviorIntervention[] } // ─── Rubric Library ──────────────────────────────────────────────── model Rubric { id String @id @default(cuid()) schoolId String school School @relation(fields: [schoolId], references: [id]) teacherId String teacher User @relation(fields: [teacherId], references: [id]) subjectId String? subject Subject? @relation(fields: [subjectId], references: [id]) title String description String? type String @default("ANALYTIC") // ANALYTIC | HOLISTIC maxPoints Int @default(100) isPublic Boolean @default(false) // shared across school version Int @default(1) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt criteria RubricCriterion[] peerAssessments PeerAssessment[] @@index([schoolId]) @@index([teacherId]) @@index([subjectId]) } model RubricCriterion { id String @id @default(cuid()) rubricId String rubric Rubric @relation(fields: [rubricId], references: [id], onDelete: Cascade) name String // e.g., "Inhalt", "Sprache", "Darstellung" description String? weight Float @default(1.0) // relative weight for this criterion maxPoints Int // max points for this criterion order Int @default(0) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt levels RubricLevel[] @@index([rubricId]) } model RubricLevel { id String @id @default(cuid()) criterionId String criterion RubricCriterion @relation(fields: [criterionId], references: [id], onDelete: Cascade) label String // e.g., "Sehr gut", "Gut", "Befriedigend", "Ausreichend" description String // descriptor for this level points Int // points for this level order Int @default(0) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([criterionId]) } // ─── Report Card Comments Bank ──────────────────────────────────── model CommentCategory { id String @id @default(cuid()) schoolId String school School @relation(fields: [schoolId], references: [id]) name String color String // hex color icon String? // emoji createdAt DateTime @default(now()) updatedAt DateTime @updatedAt comments CommentBankEntry[] @@unique([schoolId, name]) @@index([schoolId]) } model CommentBankEntry { id String @id @default(cuid()) schoolId String school School @relation(fields: [schoolId], references: [id]) teacherId String teacher User @relation(fields: [teacherId], references: [id]) categoryId String category CommentCategory @relation(fields: [categoryId], references: [id]) subjectId String? subject Subject? @relation(fields: [subjectId], references: [id]) title String // short title text String // the full comment text gradeLevel String? // e.g., "3", "3-4", "all" — which grade levels this applies to schoolType String? // e.g., "ELEMENTARY", "SECONDARY" isPublic Boolean @default(false) // shared across school usageCount Int @default(0) // how many times used tags String? // comma-separated tags for search createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([schoolId]) @@index([teacherId]) @@index([categoryId]) @@index([subjectId]) } // ─── Digital Notebooks ───────────────────────────────────────────── model Notebook { id String @id @default(cuid()) schoolId String ownerId String // userId of the student or teacher ownerType String // STUDENT | TEACHER subjectId String? // null = personal/general notebook classGroupId String? title String description String? notebookType String @default("lined") // lined | grid | blank | dotted | music | calligraphy color String @default("#10b981") // cover color icon String? // lucide icon name isArchived Boolean @default(false) isPublic Boolean @default(false) // shared with class sortOrder Int @default(0) isDemo Boolean @default(false) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt deletedAt DateTime? school School @relation(fields: [schoolId], references: [id]) owner User @relation(fields: [ownerId], references: [id]) subject Subject? @relation(fields: [subjectId], references: [id]) classGroup ClassGroup? @relation(fields: [classGroupId], references: [id]) pages NotebookPage[] @@index([schoolId]) @@index([ownerId]) @@index([subjectId]) @@index([classGroupId]) @@index([isArchived]) } model NotebookPage { id String @id @default(cuid()) notebookId String pageNumber Int title String? contentType String @default("text") // text | drawing | mixed textContent String? // markdown/rich text content drawingData String? // JSON with strokes/paths for canvas background String @default("lined") // lined | grid | blank | dotted | music isBookmark Boolean @default(false) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt notebook Notebook @relation(fields: [notebookId], references: [id], onDelete: Cascade) versions NotebookPageVersion[] @@index([notebookId]) @@index([pageNumber]) } model NotebookPageVersion { id String @id @default(cuid()) pageId String version Int textContent String? drawingData String? editedBy String? editSummary String? createdAt DateTime @default(now()) page NotebookPage @relation(fields: [pageId], references: [id], onDelete: Cascade) @@index([pageId, version]) @@map("notebook_page_versions") } // ─── Drawing Canvas ──────────────────────────────────────────────── model Drawing { id String @id @default(cuid()) schoolId String ownerId String title String description String? drawingData String // JSON with all strokes, paths, colors imageData String? // base64 encoded PNG thumbnail subjectId String? classGroupId String? isPublic Boolean @default(false) isDemo Boolean @default(false) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt school School @relation(fields: [schoolId], references: [id]) owner User @relation(fields: [ownerId], references: [id]) subject Subject? @relation(fields: [subjectId], references: [id]) classGroup ClassGroup? @relation(fields: [classGroupId], references: [id]) @@index([schoolId]) @@index([ownerId]) @@index([subjectId]) } model Notification { id String @id @default(cuid()) schoolId String userId String // recipient type String // ASSESSMENT_DUE, MISSING_OBSERVATION, NOTEBOOK_SHARED, BEHAVIOR_ALERT, GRADE_COMPUTED, etc. title String message String isRead Boolean @default(false) actionUrl String? // optional deep link relatedId String? // optional related entity ID createdAt DateTime @default(now()) updatedAt DateTime @updatedAt deletedAt DateTime? school School @relation(fields: [schoolId], references: [id]) user User @relation(fields: [userId], references: [id]) @@index([userId, isRead]) @@index([schoolId]) @@map("notifications") } // ─── Calendar Events ────────────────────────────────────────────── model CalendarEvent { id String @id @default(cuid()) schoolId String teacherId String title String date DateTime startTime String? // HH:mm format, null = all day endTime String? // HH:mm format, null = all day eventType String @default("reminder") // assessment | lesson | reminder subjectId String? classGroupId String? notes String? allDay Boolean @default(false) recurrencePattern String? // JSON: { type: "daily|weekly|monthly|yearly", interval: 1, endDate?: string, daysOfWeek?: number[] } recurrenceEnd DateTime? // When recurrence ends parentEventId String? // For generated instances of a recurring event createdAt DateTime @default(now()) updatedAt DateTime @updatedAt school School @relation(fields: [schoolId], references: [id]) teacher User @relation(fields: [teacherId], references: [id]) subject Subject? @relation(fields: [subjectId], references: [id]) classGroup ClassGroup? @relation(fields: [classGroupId], references: [id]) parentEvent CalendarEvent? @relation("EventRecurrence", fields: [parentEventId], references: [id]) childEvents CalendarEvent[] @relation("EventRecurrence") @@index([schoolId]) @@index([teacherId]) @@index([date]) @@index([subjectId]) @@index([classGroupId]) @@index([parentEventId]) } // ─── Parent-Student Link ────────────────────────────────────────────── model ParentStudentLink { id String @id @default(cuid()) parentId String // User with PARENT role studentId String // Student record schoolId String relationship String? // mother, father, guardian, etc. createdAt DateTime @default(now()) updatedAt DateTime @updatedAt parent User @relation(fields: [parentId], references: [id]) student Student @relation(fields: [studentId], references: [id]) school School @relation(fields: [schoolId], references: [id]) @@unique([parentId, studentId]) @@map("parent_student_links") } // ─── Curriculum Standards ────────────────────────────────────────── model CurriculumStandard { id String @id @default(cuid()) schoolId String subjectId String? code String // e.g., "M.1.1", "D.2.3" title String description String? gradeLevel Int? category String? // e.g., "Number", "Algebra", "Geometry" source String? // e.g., "KMK", "Lehrplan NRW" isDemo Boolean @default(false) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt deletedAt DateTime? school School @relation(fields: [schoolId], references: [id]) subject Subject? @relation(fields: [subjectId], references: [id]) competencyLinks CurriculumStandardLink[] @@index([schoolId, subjectId]) @@map("curriculum_standards") } model CurriculumStandardLink { id String @id @default(cuid()) standardId String competencyId String coverageLevel String? // "full", "partial", "related" notes String? createdAt DateTime @default(now()) standard CurriculumStandard @relation(fields: [standardId], references: [id], onDelete: Cascade) competency Competency @relation(fields: [competencyId], references: [id], onDelete: Cascade) @@unique([standardId, competencyId]) @@map("curriculum_standard_links") } // ─── Data Backup ───────────────────────────────────────────────────── model Backup { id String @id @default(cuid()) schoolId String filename String size Int // bytes type String // "full", "incremental" status String // "pending", "completed", "failed" notes String? createdAt DateTime @default(now()) school School @relation(fields: [schoolId], references: [id], onDelete: Cascade) @@map("backups") } // ─── Behavior Interventions ───────────────────────────────────────── model BehaviorIntervention { id String @id @default(cuid()) schoolId String studentId String incidentId String? type String // "warning", "meeting", "action_plan", "support", "parent_contact" description String status String @default("planned") // "planned", "in_progress", "completed", "cancelled" assignedTo String? startDate DateTime? endDate DateTime? outcome String? isDemo Boolean @default(false) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt deletedAt DateTime? school School @relation(fields: [schoolId], references: [id]) student Student @relation(fields: [studentId], references: [id]) incident BehaviorIncident? @relation(fields: [incidentId], references: [id]) assignedUser User? @relation("InterventionAssignee", fields: [assignedTo], references: [id]) @@index([schoolId, studentId]) @@map("behavior_interventions") } // ─── Report Scheduling ────────────────────────────────────────────── model ReportSchedule { id String @id @default(cuid()) schoolId String classGroupId String? template String // "short", "full", "custom" frequency String // "weekly", "monthly", "quarterly", "end_of_term" dayOfWeek Int? // 0-6 for weekly dayOfMonth Int? // 1-31 for monthly monthOfYear Int? // 1-12 for quarterly recipients String? // JSON array of userIds includeStudents Boolean @default(true) includeGrades Boolean @default(true) includeAttendance Boolean @default(true) includeBehavior Boolean @default(false) includeCompetencies Boolean @default(true) isActive Boolean @default(true) lastRunAt DateTime? nextRunAt DateTime? isDemo Boolean @default(false) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt deletedAt DateTime? school School @relation(fields: [schoolId], references: [id]) classGroup ClassGroup? @relation(fields: [classGroupId], references: [id]) @@index([schoolId, isActive]) @@map("report_schedules") } // ─── School District ──────────────────────────────────────────────── model SchoolDistrict { id String @id @default(cuid()) name String code String? // district code region String? country String @default("DE") adminEmail String? isActive Boolean @default(true) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt deletedAt DateTime? schools School[] @@map("school_districts") } // ─── Homework Management ────────────────────────────────────────── model Homework { id String @id @default(cuid()) schoolId String classGroupId String subjectId String? teacherId String title String description String? dueDate DateTime homeworkType String @default("assignment") // assignment, reading, project, practice, research maxPoints Float? attachments String? // JSON array of attachment URLs isPublished Boolean @default(true) isDemo Boolean @default(false) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt deletedAt DateTime? school School @relation(fields: [schoolId], references: [id]) classGroup ClassGroup @relation(fields: [classGroupId], references: [id]) subject Subject? @relation(fields: [subjectId], references: [id]) teacher User @relation(fields: [teacherId], references: [id]) submissions HomeworkSubmission[] @@index([schoolId, classGroupId, dueDate]) @@map("homework") } model HomeworkSubmission { id String @id @default(cuid()) homeworkId String studentId String content String? // text submission attachments String? // JSON array of attachment URLs status String @default("pending") // pending, submitted, graded, late score Float? feedback String? submittedAt DateTime? gradedAt DateTime? isDemo Boolean @default(false) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt deletedAt DateTime? homework Homework @relation(fields: [homeworkId], references: [id], onDelete: Cascade) student Student @relation(fields: [studentId], references: [id]) @@unique([homeworkId, studentId]) @@index([homeworkId, status]) @@map("homework_submissions") } // ─── Announcements ──────────────────────────────────────────────── model Announcement { id String @id @default(cuid()) schoolId String authorId String title String content String priority String @default("normal") // low, normal, high, urgent targetAudience String @default("all") // all, teachers, students, parents, class classGroupId String? // if target is specific class isPinned Boolean @default(false) expiresAt DateTime? isDemo Boolean @default(false) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt deletedAt DateTime? school School @relation(fields: [schoolId], references: [id]) author User @relation(fields: [authorId], references: [id]) classGroup ClassGroup? @relation(fields: [classGroupId], references: [id]) @@index([schoolId, createdAt]) @@map("announcements") } // ─── Student Self-Assessment ────────────────────────────────────────── model SelfAssessment { id String @id @default(cuid()) schoolId String studentId String competencyId String classGroupId String? selfLevel Int // 1-6 self-assessed mastery confidence Int? // 1-5 confidence level reflection String? // student's self-reflection text evidence String? // what evidence supports this assessment goalId String? // linked learning goal isDemo Boolean @default(false) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt deletedAt DateTime? school School @relation(fields: [schoolId], references: [id]) student Student @relation(fields: [studentId], references: [id]) competency Competency @relation(fields: [competencyId], references: [id]) classGroup ClassGroup? @relation(fields: [classGroupId], references: [id]) goal LearningGoal? @relation(fields: [goalId], references: [id]) @@index([schoolId, studentId]) @@map("self_assessments") } // ─── Learning Goals ──────────────────────────────────────────────── model LearningGoal { id String @id @default(cuid()) schoolId String studentId String competencyId String? title String description String? targetLevel Int? // target mastery level currentLevel Int? // current mastery level deadline DateTime? status String @default("active") // active, completed, abandoned progress Int @default(0) // 0-100 isDemo Boolean @default(false) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt deletedAt DateTime? school School @relation(fields: [schoolId], references: [id]) student Student @relation(fields: [studentId], references: [id]) competency Competency? @relation(fields: [competencyId], references: [id]) selfAssessments SelfAssessment[] @@index([schoolId, studentId, status]) @@map("learning_goals") } // ─── Portfolio Entries ────────────────────────────────────────────── model PortfolioEntry { id String @id @default(cuid()) schoolId String studentId String title String description String? entryType String // "artwork", "writing", "project", "presentation", "achievement", "reflection" competencyId String? content String? // text content or description mediaUrls String? // JSON array of media URLs notebookPageId String? // link to notebook page drawingId String? // link to drawing isPublic Boolean @default(false) tags String? // JSON array of tags isDemo Boolean @default(false) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt deletedAt DateTime? school School @relation(fields: [schoolId], references: [id]) student Student @relation(fields: [studentId], references: [id]) competency Competency? @relation(fields: [competencyId], references: [id]) @@index([schoolId, studentId]) @@map("portfolio_entries") } // ─── Timetable ────────────────────────────────────────────────────── model TimetableSlot { id String @id @default(cuid()) schoolId String classGroupId String dayOfWeek Int // 0=Monday, 1=Tuesday, ..., 4=Friday period Int // 1-8 (lesson period) subjectId String? teacherId String? roomId String? // optional room startTime String // "08:00" endTime String // "08:45" isBreak Boolean @default(false) isDemo Boolean @default(false) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt deletedAt DateTime? school School @relation(fields: [schoolId], references: [id]) classGroup ClassGroup @relation(fields: [classGroupId], references: [id]) subject Subject? @relation(fields: [subjectId], references: [id]) teacher User? @relation(fields: [teacherId], references: [id]) @@unique([classGroupId, dayOfWeek, period]) @@index([schoolId, classGroupId]) @@map("timetable_slots") } // ─── Digital Resource Library ────────────────────────────────────── model Resource { id String @id @default(cuid()) schoolId String authorId String title String description String? resourceType String // "document", "worksheet", "presentation", "video_link", "image", "link", "audio" url String? // external URL or file path content String? // text content subjectId String? classGroupId String? gradeLevel Int? tags String? // JSON array of tags isPublic Boolean @default(true) downloadCount Int @default(0) isDemo Boolean @default(false) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt deletedAt DateTime? school School @relation(fields: [schoolId], references: [id]) author User @relation(fields: [authorId], references: [id]) subject Subject? @relation(fields: [subjectId], references: [id]) classGroup ClassGroup? @relation(fields: [classGroupId], references: [id]) @@index([schoolId, resourceType]) @@map("resources") } // ─── Email Templates & Logging ─────────────────────────────────────── model EmailTemplate { id String @id @default(cuid()) schoolId String name String // "weekly_report", "assessment_reminder", "behavior_alert", "attendance_notice", "welcome" subject String body String // HTML template with {{variables}} isDefault Boolean @default(false) isDemo Boolean @default(false) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt deletedAt DateTime? school School @relation(fields: [schoolId], references: [id]) @@unique([schoolId, name]) @@map("email_templates") } model EmailLog { id String @id @default(cuid()) schoolId String templateId String? recipientEmail String recipientName String? subject String body String status String @default("pending") // pending, sent, failed, bounced sentAt DateTime? error String? createdAt DateTime @default(now()) school School @relation(fields: [schoolId], references: [id]) @@index([schoolId, status]) @@map("email_logs") } // ─── Peer Assessment ────────────────────────────────────────────── model PeerAssessment { id String @id @default(cuid()) schoolId String assessorId String // student who is assessing assessedId String // student being assessed competencyId String? classGroupId String? assessmentType String // "competency", "project", "presentation", "teamwork", "behavior" level Int? // 1-6 rating comment String? rubricId String? // optional rubric reference isAnonymous Boolean @default(false) isDemo Boolean @default(false) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt deletedAt DateTime? school School @relation(fields: [schoolId], references: [id]) assessor Student @relation("PeerAssessor", fields: [assessorId], references: [id]) assessed Student @relation("PeerAssessed", fields: [assessedId], references: [id]) competency Competency? @relation(fields: [competencyId], references: [id]) classGroup ClassGroup? @relation(fields: [classGroupId], references: [id]) rubric Rubric? @relation(fields: [rubricId], references: [id]) @@index([schoolId, assessedId]) @@index([schoolId, assessorId]) @@map("peer_assessments") } // ─── Emergency Contacts ─────────────────────────────────────────── model EmergencyContact { id String @id @default(cuid()) schoolId String studentId String name String relationship String // "mother", "father", "guardian", "grandparent", "other" phone String phoneAlt String? email String? address String? isPrimary Boolean @default(false) priority Int @default(1) // 1=first call, 2=second call, etc. notes String? isDemo Boolean @default(false) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt deletedAt DateTime? school School @relation(fields: [schoolId], references: [id]) student Student @relation(fields: [studentId], references: [id]) @@index([schoolId, studentId]) @@map("emergency_contacts") } // ─── School Events ──────────────────────────────────────────────── model SchoolEvent { id String @id @default(cuid()) schoolId String title String description String? eventType String // "assembly", "field_trip", "sports_day", "concert", "fair", "parent_meeting", "graduation", "holiday" startDate DateTime endDate DateTime? location String? organizerId String? classGroupId String? // if event is for specific class isAllSchool Boolean @default(true) isPublic Boolean @default(true) requiresRegistration Boolean @default(false) maxParticipants Int? notes String? isDemo Boolean @default(false) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt deletedAt DateTime? school School @relation(fields: [schoolId], references: [id]) organizer User? @relation(fields: [organizerId], references: [id]) classGroup ClassGroup? @relation(fields: [classGroupId], references: [id]) registrations EventRegistration[] @@index([schoolId, startDate]) @@map("school_events") } model EventRegistration { id String @id @default(cuid()) eventId String userId String status String @default("registered") // registered, cancelled, attended notes String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt event SchoolEvent @relation(fields: [eventId], references: [id], onDelete: Cascade) user User @relation(fields: [userId], references: [id]) @@unique([eventId, userId]) @@map("event_registrations") } // ─── Student Transportation ────────────────────────────────────── model StudentTransport { id String @id @default(cuid()) schoolId String studentId String transportType String // "bus", "tram", "walk", "car", "bike", "other" routeNumber String? // bus/tram route number stopName String? // nearest stop name pickupTime String? // "07:45" dropoffTime String? // "15:30" driverName String? // for car/bus driverPhone String? // for car/bus distanceKm Float? // distance in km notes String? isDemo Boolean @default(false) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt deletedAt DateTime? school School @relation(fields: [schoolId], references: [id]) student Student @relation(fields: [studentId], references: [id]) @@index([schoolId, studentId]) @@map("student_transport") } // ─── Health Records ────────────────────────────────────────────── model HealthRecord { id String @id @default(cuid()) schoolId String studentId String bloodType String? // "A+", "A-", "B+", "B-", "O+", "O-", "AB+", "AB-" allergies String? // JSON array of allergy names medications String? // JSON array of { name, dosage, frequency } conditions String? // JSON array of condition names doctorName String? doctorPhone String? insuranceNumber String? insuranceProvider String? lastCheckup DateTime? notes String? isConfidential Boolean @default(true) isDemo Boolean @default(false) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt deletedAt DateTime? school School @relation(fields: [schoolId], references: [id]) student Student @relation(fields: [studentId], references: [id]) @@unique([schoolId, studentId]) @@map("health_records") } // ─── Badges & Gamification ─────────────────────────────────────── model Badge { id String @id @default(cuid()) schoolId String name String description String? icon String // Lucide icon name color String @default("#10b981") category String // "competency", "attendance", "behavior", "achievement", "milestone" requirementType String // "mastery_level", "attendance_rate", "behavior_count", "progress_entries", "custom" requirementValue Int? // threshold value isAuto Boolean @default(true) // auto-awarded or manual isDemo Boolean @default(false) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt deletedAt DateTime? school School @relation(fields: [schoolId], references: [id]) studentBadges StudentBadge[] @@index([schoolId, category]) @@map("badges") } model StudentBadge { id String @id @default(cuid()) schoolId String studentId String badgeId String awardedAt DateTime @default(now()) awardedBy String? // userId who awarded (null if auto) notes String? isDemo Boolean @default(false) createdAt DateTime @default(now()) school School @relation(fields: [schoolId], references: [id]) student Student @relation(fields: [studentId], references: [id]) badge Badge @relation(fields: [badgeId], references: [id]) awardedByUser User? @relation(fields: [awardedBy], references: [id]) @@unique([studentId, badgeId]) @@index([schoolId, studentId]) @@map("student_badges") } // ─── Newsletter / School Blog ────────────────────────────────────── model Newsletter { id String @id @default(cuid()) schoolId String authorId String title String content String // rich text content (HTML) summary String? // short summary for preview imageUrl String? // cover image URL category String @default("general") // general, academic, sports, arts, events, community isPublished Boolean @default(false) publishedAt DateTime? tags String? // JSON array of tags isDemo Boolean @default(false) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt deletedAt DateTime? school School @relation(fields: [schoolId], references: [id]) author User @relation(fields: [authorId], references: [id]) @@index([schoolId, isPublished, publishedAt]) @@map("newsletters") }