# Database Schema

> Generated from the Sequelize models (`backend/src/db/models/*`) — the source of truth.
> Regenerate after schema changes. Last generated: 2026-06-11.

## Overview

- **Engine:** PostgreSQL via **Sequelize 6** (models in `backend/src/db/models`, typed data access in `backend/src/db/api`).
- **Models:** 35 tables.
- **Primary keys:** every table has a `uuid` `id` (default `UUIDV4`).
- **Soft delete:** all tables are `paranoid` — rows are flagged with `deletedAt` instead of being physically removed.
- **Timestamps:** `createdAt` / `updatedAt` are managed automatically.
- **Audit:** `createdById` / `updatedById` reference `users` (aliases `createdBy` / `updatedBy`).
- **Multi-tenancy:** tenant-owned tables carry `organizationId` and are scoped to the current user's organization in `db/api` (the tenant boundary is enforced via `db/api/shared/repository.ts` — `findOwnedByPk`/`tenantWhere`).
- **Import idempotency:** `importHash` (unique) deduplicates seeded/imported rows.

### Type notes

Types below are the SQL column types. A few Sequelize types are returned as JS `string` at runtime to preserve precision: `DECIMAL`, `NUMERIC`, `BIGINT`, and `DATEONLY` (date-only). `DATE` maps to a JS `Date`.

## Domains

- **Tenancy & Access:** `organizations`, `users`, `roles`, `permissions`
- **Campuses & People:** `campuses`, `staff`
- **Academics:** `academic_years`, `grades`, `subjects`, `classes`, `class_enrollments`, `class_subjects`, `timetables`, `timetable_periods`, `assessments`, `assessment_results`
- **Attendance:** `attendance_sessions`, `attendance_records`, `campus_attendance_config`, `campus_attendance_summaries`, `staff_attendance_records`
- **Communication:** `messages`, `message_recipients`, `communication_events`
- **Content & Product modules:** `content_catalog`, `frame_entries`, `user_progress`, `safety_quiz_results`, `walkthrough_checkins`, `personality_quiz_results`
- **Policy & Audio:** `policy_documents`, `policy_acknowledgments`, `audio_files`
- **System:** `file`, `auth_refresh_tokens`

## Relationship graph (foreign keys)

Each edge is a `belongsTo` (the child holds the FK). `createdBy` / `updatedBy` edges to `users` are omitted for readability.

```mermaid
erDiagram
  organizations ||--o{ academic_years : "organization"
  organizations ||--o{ assessment_results : "organization"
  assessments ||--o{ assessment_results : "assessment"
  organizations ||--o{ assessments : "organization"
  class_subjects ||--o{ assessments : "class_subject"
  organizations ||--o{ attendance_records : "organization"
  attendance_sessions ||--o{ attendance_records : "attendance_session"
  organizations ||--o{ attendance_sessions : "organization"
  campuses ||--o{ attendance_sessions : "campus"
  classes ||--o{ attendance_sessions : "class"
  class_subjects ||--o{ attendance_sessions : "class_subject"
  staff ||--o{ attendance_sessions : "taken_by"
  users ||--o{ auth_refresh_tokens : "user"
  organizations ||--o{ auth_refresh_tokens : "organization"
  organizations ||--o{ campus_attendance_config : "organization"
  campuses ||--o{ campus_attendance_config : "campus"
  organizations ||--o{ campus_attendance_summaries : "organization"
  campuses ||--o{ campus_attendance_summaries : "campus"
  organizations ||--o{ campuses : "organization"
  organizations ||--o{ class_enrollments : "organization"
  classes ||--o{ class_enrollments : "class"
  organizations ||--o{ class_subjects : "organization"
  classes ||--o{ class_subjects : "class"
  subjects ||--o{ class_subjects : "subject"
  staff ||--o{ class_subjects : "teacher"
  organizations ||--o{ classes : "organization"
  campuses ||--o{ classes : "campus"
  academic_years ||--o{ classes : "academic_year"
  grades ||--o{ classes : "grade"
  staff ||--o{ classes : "homeroom_teacher"
  organizations ||--o{ communication_events : "organization"
  campuses ||--o{ communication_events : "campus"
  organizations ||--o{ frame_entries : "organization"
  campuses ||--o{ frame_entries : "campus"
  organizations ||--o{ grades : "organization"
  organizations ||--o{ message_recipients : "organization"
  messages ||--o{ message_recipients : "message"
  organizations ||--o{ messages : "organization"
  campuses ||--o{ messages : "campus"
  users ||--o{ messages : "sent_by"
  organizations ||--o{ personality_quiz_results : "organization"
  campuses ||--o{ personality_quiz_results : "campus"
  users ||--o{ personality_quiz_results : "user"
  organizations ||--o{ safety_quiz_results : "organization"
  campuses ||--o{ safety_quiz_results : "campus"
  users ||--o{ safety_quiz_results : "user"
  organizations ||--o{ staff : "organization"
  campuses ||--o{ staff : "campus"
  users ||--o{ staff : "user"
  organizations ||--o{ staff_attendance_records : "organization"
  campuses ||--o{ staff_attendance_records : "campus"
  users ||--o{ staff_attendance_records : "user"
  organizations ||--o{ subjects : "organization"
  organizations ||--o{ timetable_periods : "organization"
  timetables ||--o{ timetable_periods : "timetable"
  class_subjects ||--o{ timetable_periods : "class_subject"
  organizations ||--o{ timetables : "organization"
  campuses ||--o{ timetables : "campus"
  academic_years ||--o{ timetables : "academic_year"
  organizations ||--o{ user_progress : "organization"
  campuses ||--o{ user_progress : "campus"
  users ||--o{ user_progress : "user"
  roles ||--o{ users : "app_role"
  organizations ||--o{ users : "organizations"
  organizations ||--o{ walkthrough_checkins : "organization"
  campuses ||--o{ walkthrough_checkins : "campus"
  organizations ||--o{ policy_documents : "organization"
  campuses ||--o{ policy_documents : "campus"
  policy_documents ||--o{ policy_acknowledgments : "policyDocument"
  organizations ||--o{ policy_acknowledgments : "organization"
  users ||--o{ policy_acknowledgments : "user"
  organizations ||--o{ audio_files : "organization"
  campuses ||--o{ audio_files : "campus"
```

## Table reference

### Tenancy & Access

#### `organizations`

Tenant root. Every tenant-owned row references an organization via `organizationId`.

| Column | Type | Null | Default | Notes |
|---|---|---|---|---|
| `id` | uuid | no | UUIDV4 | PK |
| `name` | text | yes | — |  |
| `importHash` | varchar | yes | — | unique, audit |
| `createdAt` | timestamptz | yes | — | audit |
| `updatedAt` | timestamptz | yes | — | audit |
| `deletedAt` | timestamptz | yes | — | audit |
| `createdById` | uuid | yes | — | FK, audit |
| `updatedById` | uuid | yes | — | FK, audit |

_Relations:_

- **has many** `users` as `users_organizations` (FK `organizationId`)
- **has many** `campuses` as `campuses_organization` (FK `organizationId`)
- **has many** `academic_years` as `academic_years_organization` (FK `organizationId`)
- **has many** `grades` as `grades_organization` (FK `organizationId`)
- **has many** `subjects` as `subjects_organization` (FK `organizationId`)
- **has many** `staff` as `staff_organization` (FK `organizationId`)
- **has many** `classes` as `classes_organization` (FK `organizationId`)
- **has many** `class_enrollments` as `class_enrollments_organization` (FK `organizationId`)
- **has many** `class_subjects` as `class_subjects_organization` (FK `organizationId`)
- **has many** `timetables` as `timetables_organization` (FK `organizationId`)
- **has many** `timetable_periods` as `timetable_periods_organization` (FK `organizationId`)
- **has many** `attendance_sessions` as `attendance_sessions_organization` (FK `organizationId`)
- **has many** `attendance_records` as `attendance_records_organization` (FK `organizationId`)
- **has many** `assessments` as `assessments_organization` (FK `organizationId`)
- **has many** `assessment_results` as `assessment_results_organization` (FK `organizationId`)
- **has many** `messages` as `messages_organization` (FK `organizationId`)
- **has many** `message_recipients` as `message_recipients_organization` (FK `organizationId`)

#### `users`

Authentication identities. `email` is required (login + primary contact). Belongs to one organization (`organizationId`, alias `organizations`), one `app_role`, and may hold direct `custom_permissions`.

| Column | Type | Null | Default | Notes |
|---|---|---|---|---|
| `id` | uuid | no | UUIDV4 | PK |
| `firstName` | text | yes | — |  |
| `lastName` | text | yes | — |  |
| `phoneNumber` | text | yes | — |  |
| `email` | text | no | — |  |
| `disabled` | boolean | no | false |  |
| `password` | text | yes | — |  |
| `emailVerified` | boolean | no | false |  |
| `emailVerificationToken` | text | yes | — |  |
| `emailVerificationTokenExpiresAt` | timestamptz | yes | — |  |
| `passwordResetToken` | text | yes | — |  |
| `passwordResetTokenExpiresAt` | timestamptz | yes | — |  |
| `provider` | text | yes | — |  |
| `importHash` | varchar | yes | — | unique, audit |
| `organizationId` | uuid | yes | — | FK |
| `createdById` | uuid | yes | — | FK, audit |
| `updatedById` | uuid | yes | — | FK, audit |
| `createdAt` | timestamptz | yes | — | audit |
| `updatedAt` | timestamptz | yes | — | audit |
| `deletedAt` | timestamptz | yes | — | audit |
| `app_roleId` | uuid | yes | — | FK |
| `campusId` | uuid | yes | — | FK, campus scope for campus-bound roles |

_Relations:_

- **many-to-many with** `permissions` as `custom_permissions` (FK `users_custom_permissionsId`)
- **many-to-many with** `permissions` as `custom_permissions_filter` (FK `users_custom_permissionsId`)
- **has many** `staff` as `staff_user` (FK `userId`)
- **has many** `messages` as `messages_sent_by` (FK `sent_byId`)
- **belongs to** `roles` as `app_role` (FK `app_roleId`)
- **belongs to** `organizations` as `organizations` (FK `organizationId`)
- **belongs to** `campuses` as `campus` (FK `campusId`)
- **has many** `file` as `avatar` (FK `belongsToId`)

#### `roles`

Named permission sets (RBAC), the 11 first-class roles. Linked to permissions M:N; `globalAccess` grants cross-tenant access (system roles).

| Column | Type | Null | Default | Notes |
|---|---|---|---|---|
| `id` | uuid | no | UUIDV4 | PK |
| `name` | text | yes | — |  |
| `scope` | enum | no | — | `system` \| `organization` \| `campus` \| `external` \| `guest` |
| `globalAccess` | boolean | no | false |  |
| `importHash` | varchar | yes | — | unique, audit |
| `createdAt` | timestamptz | yes | — | audit |
| `updatedAt` | timestamptz | yes | — | audit |
| `deletedAt` | timestamptz | yes | — | audit |
| `createdById` | uuid | yes | — | FK, audit |
| `updatedById` | uuid | yes | — | FK, audit |

_Relations:_

- **many-to-many with** `permissions` as `permissions` (FK `roles_permissionsId`)
- **many-to-many with** `permissions` as `permissions_filter` (FK `roles_permissionsId`)
- **has many** `users` as `users_app_role` (FK `app_roleId`)

#### `permissions`

Flat permission catalog. Permission names follow `${METHOD}_${ENTITY}` (e.g. `READ_CAMPUSES`).

| Column | Type | Null | Default | Notes |
|---|---|---|---|---|
| `id` | uuid | no | UUIDV4 | PK |
| `name` | text | yes | — |  |
| `importHash` | varchar | yes | — | unique, audit |
| `createdAt` | timestamptz | yes | — | audit |
| `updatedAt` | timestamptz | yes | — | audit |
| `deletedAt` | timestamptz | yes | — | audit |
| `createdById` | uuid | yes | — | FK, audit |
| `updatedById` | uuid | yes | — | FK, audit |

### Campuses & People

#### `campuses`

A physical or online campus belonging to one organization. Parent of students, staff, classes, etc.

| Column | Type | Null | Default | Notes |
|---|---|---|---|---|
| `id` | uuid | no | UUIDV4 | PK |
| `name` | text | yes | — |  |
| `code` | text | yes | — |  |
| `address` | text | yes | — |  |
| `phone` | text | yes | — |  |
| `email` | text | yes | — |  |
| `mascot` | text | yes | — |  |
| `color` | text | yes | — |  |
| `bgGradient` | text | yes | — |  |
| `borderColor` | text | yes | — |  |
| `textColor` | text | yes | — |  |
| `bgLight` | text | yes | — |  |
| `description` | text | yes | — |  |
| `isOnline` | boolean | no | false |  |
| `active` | boolean | no | false |  |
| `importHash` | varchar | yes | — | unique, audit |
| `organizationId` | uuid | yes | — | FK |
| `createdById` | uuid | yes | — | FK, audit |
| `updatedById` | uuid | yes | — | FK, audit |
| `createdAt` | timestamptz | yes | — | audit |
| `updatedAt` | timestamptz | yes | — | audit |
| `deletedAt` | timestamptz | yes | — | audit |

_Relations:_

- **has many** `staff` as `staff_campus` (FK `campusId`)
- **has many** `classes` as `classes_campus` (FK `campusId`)
- **has many** `timetables` as `timetables_campus` (FK `campusId`)
- **has many** `attendance_sessions` as `attendance_sessions_campus` (FK `campusId`)
- **has many** `messages` as `messages_campus` (FK `campusId`)
- **belongs to** `organizations` as `organization` (FK `organizationId`)

#### `staff`

Staff members, optionally linked to a `user` account; can be homeroom teacher, subject teacher, attendance taker, payment receiver.

| Column | Type | Null | Default | Notes |
|---|---|---|---|---|
| `id` | uuid | no | UUIDV4 | PK |
| `employee_number` | text | yes | — |  |
| `job_title` | text | yes | — |  |
| `staff_type` | enum | yes | — |  |
| `hire_date` | timestamptz | yes | — |  |
| `status` | enum | yes | — |  |
| `importHash` | varchar | yes | — | unique, audit |
| `createdAt` | timestamptz | yes | — | audit |
| `updatedAt` | timestamptz | yes | — | audit |
| `deletedAt` | timestamptz | yes | — | audit |
| `campusId` | uuid | yes | — | FK |
| `organizationId` | uuid | yes | — | FK |
| `userId` | uuid | yes | — | FK |
| `createdById` | uuid | yes | — | FK, audit |
| `updatedById` | uuid | yes | — | FK, audit |

_Relations:_

- **has many** `classes` as `classes_homeroom_teacher` (FK `homeroom_teacherId`)
- **has many** `class_subjects` as `class_subjects_teacher` (FK `teacherId`)
- **has many** `attendance_sessions` as `attendance_sessions_taken_by` (FK `taken_byId`)
- **belongs to** `organizations` as `organization` (FK `organizationId`)
- **belongs to** `campuses` as `campus` (FK `campusId`)
- **belongs to** `users` as `user` (FK `userId`)
- **has many** `file` as `photo` (FK `belongsToId`)

### Academics

#### `academic_years`

School years (with `current` flag and effective dates) scoping classes, timetables, fee plans.

| Column | Type | Null | Default | Notes |
|---|---|---|---|---|
| `id` | uuid | no | UUIDV4 | PK |
| `name` | text | yes | — |  |
| `start_date` | timestamptz | yes | — |  |
| `end_date` | timestamptz | yes | — |  |
| `current` | boolean | no | false |  |
| `importHash` | varchar | yes | — | unique, audit |
| `createdAt` | timestamptz | yes | — | audit |
| `updatedAt` | timestamptz | yes | — | audit |
| `deletedAt` | timestamptz | yes | — | audit |
| `organizationId` | uuid | yes | — | FK |
| `createdById` | uuid | yes | — | FK, audit |
| `updatedById` | uuid | yes | — | FK, audit |

_Relations:_

- **has many** `classes` as `classes_academic_year` (FK `academic_yearId`)
- **has many** `timetables` as `timetables_academic_year` (FK `academic_yearId`)
- **belongs to** `organizations` as `organization` (FK `organizationId`)

#### `grades`

Grade levels (with `sort_order`).

| Column | Type | Null | Default | Notes |
|---|---|---|---|---|
| `id` | uuid | no | UUIDV4 | PK |
| `name` | text | yes | — |  |
| `code` | text | yes | — |  |
| `sort_order` | integer | yes | — |  |
| `description` | text | yes | — |  |
| `importHash` | varchar | yes | — | unique, audit |
| `createdAt` | timestamptz | yes | — | audit |
| `updatedAt` | timestamptz | yes | — | audit |
| `deletedAt` | timestamptz | yes | — | audit |
| `organizationId` | uuid | yes | — | FK |
| `createdById` | uuid | yes | — | FK, audit |
| `updatedById` | uuid | yes | — | FK, audit |

_Relations:_

- **has many** `classes` as `classes_grade` (FK `gradeId`)
- **belongs to** `organizations` as `organization` (FK `organizationId`)

#### `subjects`

Academic subjects (Math, English, …).

| Column | Type | Null | Default | Notes |
|---|---|---|---|---|
| `id` | uuid | no | UUIDV4 | PK |
| `name` | text | yes | — |  |
| `code` | text | yes | — |  |
| `description` | text | yes | — |  |
| `importHash` | varchar | yes | — | unique, audit |
| `createdAt` | timestamptz | yes | — | audit |
| `updatedAt` | timestamptz | yes | — | audit |
| `deletedAt` | timestamptz | yes | — | audit |
| `organizationId` | uuid | yes | — | FK |
| `createdById` | uuid | yes | — | FK, audit |
| `updatedById` | uuid | yes | — | FK, audit |

_Relations:_

- **has many** `class_subjects` as `class_subjects_subject` (FK `subjectId`)
- **belongs to** `organizations` as `organization` (FK `organizationId`)

#### `classes`

Class/group: belongs to campus, academic year, grade, and a homeroom teacher.

| Column | Type | Null | Default | Notes |
|---|---|---|---|---|
| `id` | uuid | no | UUIDV4 | PK |
| `name` | text | yes | — |  |
| `section` | text | yes | — |  |
| `capacity` | integer | yes | — |  |
| `status` | enum | yes | — |  |
| `importHash` | varchar | yes | — | unique, audit |
| `createdAt` | timestamptz | yes | — | audit |
| `updatedAt` | timestamptz | yes | — | audit |
| `deletedAt` | timestamptz | yes | — | audit |
| `academic_yearId` | uuid | yes | — | FK |
| `campusId` | uuid | yes | — | FK |
| `organizationId` | uuid | yes | — | FK |
| `gradeId` | uuid | yes | — | FK |
| `homeroom_teacherId` | uuid | yes | — | FK |
| `createdById` | uuid | yes | — | FK, audit |
| `updatedById` | uuid | yes | — | FK, audit |

_Relations:_

- **has many** `class_enrollments` as `class_enrollments_class` (FK `classId`)
- **has many** `class_subjects` as `class_subjects_class` (FK `classId`)
- **has many** `attendance_sessions` as `attendance_sessions_class` (FK `classId`)
- **belongs to** `organizations` as `organization` (FK `organizationId`)
- **belongs to** `campuses` as `campus` (FK `campusId`)
- **belongs to** `academic_years` as `academic_year` (FK `academic_yearId`)
- **belongs to** `grades` as `grade` (FK `gradeId`)
- **belongs to** `staff` as `homeroom_teacher` (FK `homeroom_teacherId`)

#### `class_enrollments`

Join of student ↔ class with enrolment dates and status.

| Column | Type | Null | Default | Notes |
|---|---|---|---|---|
| `id` | uuid | no | UUIDV4 | PK |
| `enrolled_on` | timestamptz | yes | — |  |
| `ended_on` | timestamptz | yes | — |  |
| `status` | enum | yes | — |  |
| `importHash` | varchar | yes | — | unique, audit |
| `createdAt` | timestamptz | yes | — | audit |
| `updatedAt` | timestamptz | yes | — | audit |
| `deletedAt` | timestamptz | yes | — | audit |
| `organizationId` | uuid | yes | — | FK |
| `classId` | uuid | yes | — | FK |
| `studentId` | uuid | yes | — | FK |
| `createdById` | uuid | yes | — | FK, audit |
| `updatedById` | uuid | yes | — | FK, audit |

_Relations:_

- **belongs to** `organizations` as `organization` (FK `organizationId`)
- **belongs to** `classes` as `class` (FK `classId`)

#### `class_subjects`

Join of class ↔ subject ↔ teacher; the unit that schedule, attendance, and assessments hang off.

| Column | Type | Null | Default | Notes |
|---|---|---|---|---|
| `id` | uuid | no | UUIDV4 | PK |
| `status` | enum | yes | — |  |
| `importHash` | varchar | yes | — | unique, audit |
| `createdAt` | timestamptz | yes | — | audit |
| `updatedAt` | timestamptz | yes | — | audit |
| `deletedAt` | timestamptz | yes | — | audit |
| `organizationId` | uuid | yes | — | FK |
| `classId` | uuid | yes | — | FK |
| `subjectId` | uuid | yes | — | FK |
| `teacherId` | uuid | yes | — | FK |
| `createdById` | uuid | yes | — | FK, audit |
| `updatedById` | uuid | yes | — | FK, audit |

_Relations:_

- **has many** `timetable_periods` as `timetable_periods_class_subject` (FK `class_subjectId`)
- **has many** `attendance_sessions` as `attendance_sessions_class_subject` (FK `class_subjectId`)
- **has many** `assessments` as `assessments_class_subject` (FK `class_subjectId`)
- **belongs to** `organizations` as `organization` (FK `organizationId`)
- **belongs to** `classes` as `class` (FK `classId`)
- **belongs to** `subjects` as `subject` (FK `subjectId`)
- **belongs to** `staff` as `teacher` (FK `teacherId`)

#### `timetables`

A timetable for a campus + academic year, with effective dates.

| Column | Type | Null | Default | Notes |
|---|---|---|---|---|
| `id` | uuid | no | UUIDV4 | PK |
| `name` | text | yes | — |  |
| `effective_from` | timestamptz | yes | — |  |
| `effective_to` | timestamptz | yes | — |  |
| `status` | enum | yes | — |  |
| `importHash` | varchar | yes | — | unique, audit |
| `createdAt` | timestamptz | yes | — | audit |
| `updatedAt` | timestamptz | yes | — | audit |
| `deletedAt` | timestamptz | yes | — | audit |
| `academic_yearId` | uuid | yes | — | FK |
| `campusId` | uuid | yes | — | FK |
| `organizationId` | uuid | yes | — | FK |
| `createdById` | uuid | yes | — | FK, audit |
| `updatedById` | uuid | yes | — | FK, audit |

_Relations:_

- **has many** `timetable_periods` as `timetable_periods_timetable` (FK `timetableId`)
- **belongs to** `organizations` as `organization` (FK `organizationId`)
- **belongs to** `campuses` as `campus` (FK `campusId`)
- **belongs to** `academic_years` as `academic_year` (FK `academic_yearId`)

#### `timetable_periods`

A single period (day/time/room) linking a timetable to a class_subject.

| Column | Type | Null | Default | Notes |
|---|---|---|---|---|
| `id` | uuid | no | UUIDV4 | PK |
| `day_of_week` | enum | yes | — |  |
| `starts_at` | timestamptz | yes | — |  |
| `ends_at` | timestamptz | yes | — |  |
| `room` | text | yes | — |  |
| `importHash` | varchar | yes | — | unique, audit |
| `createdAt` | timestamptz | yes | — | audit |
| `updatedAt` | timestamptz | yes | — | audit |
| `deletedAt` | timestamptz | yes | — | audit |
| `class_subjectId` | uuid | yes | — | FK |
| `organizationId` | uuid | yes | — | FK |
| `timetableId` | uuid | yes | — | FK |
| `createdById` | uuid | yes | — | FK, audit |
| `updatedById` | uuid | yes | — | FK, audit |

_Relations:_

- **belongs to** `organizations` as `organization` (FK `organizationId`)
- **belongs to** `timetables` as `timetable` (FK `timetableId`)
- **belongs to** `class_subjects` as `class_subject` (FK `class_subjectId`)

#### `assessments`

Assessments for a class_subject (type, dates, max score, attachments).

| Column | Type | Null | Default | Notes |
|---|---|---|---|---|
| `id` | uuid | no | UUIDV4 | PK |
| `name` | text | yes | — |  |
| `assessment_type` | enum | yes | — |  |
| `assigned_at` | timestamptz | yes | — |  |
| `due_at` | timestamptz | yes | — |  |
| `max_score` | decimal | yes | — |  |
| `status` | enum | yes | — |  |
| `instructions` | text | yes | — |  |
| `importHash` | varchar | yes | — | unique, audit |
| `createdAt` | timestamptz | yes | — | audit |
| `updatedAt` | timestamptz | yes | — | audit |
| `deletedAt` | timestamptz | yes | — | audit |
| `organizationId` | uuid | yes | — | FK |
| `class_subjectId` | uuid | yes | — | FK |
| `createdById` | uuid | yes | — | FK, audit |
| `updatedById` | uuid | yes | — | FK, audit |

_Relations:_

- **has many** `assessment_results` as `assessment_results_assessment` (FK `assessmentId`)
- **belongs to** `organizations` as `organization` (FK `organizationId`)
- **belongs to** `class_subjects` as `class_subject` (FK `class_subjectId`)
- **has many** `file` as `attachments` (FK `belongsToId`)

#### `assessment_results`

A student's result for an assessment (score, grade_letter).

| Column | Type | Null | Default | Notes |
|---|---|---|---|---|
| `id` | uuid | no | UUIDV4 | PK |
| `score` | decimal | yes | — |  |
| `grade_letter` | enum | yes | — |  |
| `remarks` | text | yes | — |  |
| `importHash` | varchar | yes | — | unique, audit |
| `createdAt` | timestamptz | yes | — | audit |
| `updatedAt` | timestamptz | yes | — | audit |
| `deletedAt` | timestamptz | yes | — | audit |
| `organizationId` | uuid | yes | — | FK |
| `assessmentId` | uuid | yes | — | FK |
| `studentId` | uuid | yes | — | FK |
| `createdById` | uuid | yes | — | FK, audit |
| `updatedById` | uuid | yes | — | FK, audit |

_Relations:_

- **belongs to** `organizations` as `organization` (FK `organizationId`)
- **belongs to** `assessments` as `assessment` (FK `assessmentId`)

### Attendance

#### `attendance_sessions`

An attendance session for a class/class_subject taken by a staff member.

| Column | Type | Null | Default | Notes |
|---|---|---|---|---|
| `id` | uuid | no | UUIDV4 | PK |
| `session_date` | timestamptz | yes | — |  |
| `session_type` | enum | yes | — |  |
| `notes` | text | yes | — |  |
| `importHash` | varchar | yes | — | unique, audit |
| `createdAt` | timestamptz | yes | — | audit |
| `updatedAt` | timestamptz | yes | — | audit |
| `deletedAt` | timestamptz | yes | — | audit |
| `organizationId` | uuid | yes | — | FK |
| `campusId` | uuid | yes | — | FK |
| `classId` | uuid | yes | — | FK |
| `class_subjectId` | uuid | yes | — | FK |
| `taken_byId` | uuid | yes | — | FK |
| `createdById` | uuid | yes | — | FK, audit |
| `updatedById` | uuid | yes | — | FK, audit |

_Relations:_

- **has many** `attendance_records` as `attendance_records_attendance_session` (FK `attendance_sessionId`)
- **belongs to** `organizations` as `organization` (FK `organizationId`)
- **belongs to** `campuses` as `campus` (FK `campusId`)
- **belongs to** `classes` as `class` (FK `classId`)
- **belongs to** `class_subjects` as `class_subject` (FK `class_subjectId`)
- **belongs to** `staff` as `taken_by` (FK `taken_byId`)

#### `attendance_records`

Per-student attendance within a session (status, minutes_late).

| Column | Type | Null | Default | Notes |
|---|---|---|---|---|
| `id` | uuid | no | UUIDV4 | PK |
| `status` | enum | yes | — |  |
| `minutes_late` | integer | yes | — |  |
| `remarks` | text | yes | — |  |
| `importHash` | varchar | yes | — | unique, audit |
| `createdAt` | timestamptz | yes | — | audit |
| `updatedAt` | timestamptz | yes | — | audit |
| `deletedAt` | timestamptz | yes | — | audit |
| `organizationId` | uuid | yes | — | FK |
| `attendance_sessionId` | uuid | yes | — | FK |
| `studentId` | uuid | yes | — | FK |
| `createdById` | uuid | yes | — | FK, audit |
| `updatedById` | uuid | yes | — | FK, audit |

_Relations:_

- **belongs to** `organizations` as `organization` (FK `organizationId`)
- **belongs to** `attendance_sessions` as `attendance_session` (FK `attendance_sessionId`)

#### `campus_attendance_config`

Product-module config for campus attendance.

| Column | Type | Null | Default | Notes |
|---|---|---|---|---|
| `id` | uuid | no | UUIDV4 | PK |
| `campus_key` | text | no | — |  |
| `attendance_link` | text | yes | — |  |
| `updated_by_label` | text | yes | — |  |
| `importHash` | varchar | yes | — | unique, audit |
| `createdAt` | timestamptz | yes | — | audit |
| `updatedAt` | timestamptz | yes | — | audit |
| `deletedAt` | timestamptz | yes | — | audit |
| `organizationId` | uuid | yes | — | FK |
| `campusId` | uuid | yes | — | FK |
| `createdById` | uuid | yes | — | FK, audit |
| `updatedById` | uuid | yes | — | FK, audit |

_Relations:_

- **belongs to** `organizations` as `organization` (FK `organizationId`)
- **belongs to** `campuses` as `campus` (FK `campusId`)

#### `campus_attendance_summaries`

Product-module aggregated campus attendance.

| Column | Type | Null | Default | Notes |
|---|---|---|---|---|
| `id` | uuid | no | UUIDV4 | PK |
| `campus_key` | text | no | — |  |
| `attendance_date` | date | no | — |  |
| `total_enrolled` | integer | no | — |  |
| `total_present` | integer | no | — |  |
| `total_absent` | integer | no | — |  |
| `total_tardy` | integer | no | 0 |  |
| `attendance_percentage` | decimal | no | — |  |
| `recorded_by_label` | text | yes | — |  |
| `notes` | text | yes | — |  |
| `importHash` | varchar | yes | — | unique, audit |
| `createdAt` | timestamptz | yes | — | audit |
| `updatedAt` | timestamptz | yes | — | audit |
| `deletedAt` | timestamptz | yes | — | audit |
| `organizationId` | uuid | yes | — | FK |
| `campusId` | uuid | yes | — | FK |
| `createdById` | uuid | yes | — | FK, audit |
| `updatedById` | uuid | yes | — | FK, audit |

_Relations:_

- **belongs to** `organizations` as `organization` (FK `organizationId`)
- **belongs to** `campuses` as `campus` (FK `campusId`)

#### `staff_attendance_records`

Staff attendance records (present/late/absent).

| Column | Type | Null | Default | Notes |
|---|---|---|---|---|
| `id` | uuid | no | UUIDV4 | PK |
| `attendance_date` | date | no | — |  |
| `status` | enum | no | — |  |
| `note` | text | yes | — |  |
| `user_name` | text | no | — |  |
| `user_role` | text | yes | — |  |
| `importHash` | varchar | yes | — | unique, audit |
| `createdAt` | timestamptz | yes | — | audit |
| `updatedAt` | timestamptz | yes | — | audit |
| `deletedAt` | timestamptz | yes | — | audit |
| `organizationId` | uuid | yes | — | FK |
| `campusId` | uuid | yes | — | FK |
| `userId` | uuid | yes | — | FK |
| `createdById` | uuid | yes | — | FK, audit |
| `updatedById` | uuid | yes | — | FK, audit |

_Relations:_

- **belongs to** `organizations` as `organization` (FK `organizationId`)
- **belongs to** `campuses` as `campus` (FK `campusId`)
- **belongs to** `users` as `user` (FK `userId`)

### Communication

#### `messages`

In-app announcements/messages (subject/body/channel/audience) with attachments and recipients.

| Column | Type | Null | Default | Notes |
|---|---|---|---|---|
| `id` | uuid | no | UUIDV4 | PK |
| `subject` | text | yes | — |  |
| `body` | text | yes | — |  |
| `channel` | enum | yes | — |  |
| `audience` | enum | yes | — |  |
| `sent_at` | timestamptz | yes | — |  |
| `status` | enum | yes | — |  |
| `importHash` | varchar | yes | — | unique, audit |
| `createdAt` | timestamptz | yes | — | audit |
| `updatedAt` | timestamptz | yes | — | audit |
| `deletedAt` | timestamptz | yes | — | audit |
| `campusId` | uuid | yes | — | FK |
| `organizationId` | uuid | yes | — | FK |
| `sent_byId` | uuid | yes | — | FK |
| `createdById` | uuid | yes | — | FK, audit |
| `updatedById` | uuid | yes | — | FK, audit |

_Relations:_

- **has many** `message_recipients` as `message_recipients_message` (FK `messageId`)
- **belongs to** `organizations` as `organization` (FK `organizationId`)
- **belongs to** `campuses` as `campus` (FK `campusId`)
- **belongs to** `users` as `sent_by` (FK `sent_byId`)
- **has many** `file` as `attachments` (FK `belongsToId`)

#### `message_recipients`

Per-recipient delivery/read tracking for a message (in-app).

| Column | Type | Null | Default | Notes |
|---|---|---|---|---|
| `id` | uuid | no | UUIDV4 | PK |
| `recipient_type` | enum | yes | — |  |
| `recipient_label` | text | yes | — |  |
| `destination` | text | yes | — |  |
| `delivery_status` | enum | yes | — |  |
| `delivered_at` | timestamptz | yes | — |  |
| `read_at` | timestamptz | yes | — |  |
| `importHash` | varchar | yes | — | unique, audit |
| `createdAt` | timestamptz | yes | — | audit |
| `updatedAt` | timestamptz | yes | — | audit |
| `deletedAt` | timestamptz | yes | — | audit |
| `organizationId` | uuid | yes | — | FK |
| `messageId` | uuid | yes | — | FK |
| `createdById` | uuid | yes | — | FK, audit |
| `updatedById` | uuid | yes | — | FK, audit |

_Relations:_

- **belongs to** `organizations` as `organization` (FK `organizationId`)
- **belongs to** `messages` as `message` (FK `messageId`)

#### `communication_events`

Product-module communication events (meetings, drills, events, deadlines).

| Column | Type | Null | Default | Notes |
|---|---|---|---|---|
| `id` | uuid | no | UUIDV4 | PK |
| `title` | text | no | — |  |
| `event_date` | date | no | — |  |
| `event_type` | text | no | — |  |
| `roles` | jsonb | no | Array |  |
| `importHash` | varchar | yes | — | unique, audit |
| `createdAt` | timestamptz | yes | — | audit |
| `updatedAt` | timestamptz | yes | — | audit |
| `deletedAt` | timestamptz | yes | — | audit |
| `organizationId` | uuid | yes | — | FK |
| `campusId` | uuid | yes | — | FK |
| `createdById` | uuid | yes | — | FK, audit |
| `updatedById` | uuid | yes | — | FK, audit |

_Relations:_

- **belongs to** `organizations` as `organization` (FK `organizationId`)
- **belongs to** `campuses` as `campus` (FK `campusId`)

### Content & Product modules

#### `content_catalog`

Product content catalog.

| Column | Type | Null | Default | Notes |
|---|---|---|---|---|
| `id` | uuid | no | UUIDV4 | PK |
| `content_type` | text | no | — | unique |
| `payload` | jsonb | no | — |  |
| `active` | boolean | no | true |  |
| `importHash` | varchar | yes | — | unique, audit |
| `createdAt` | timestamptz | yes | — | audit |
| `updatedAt` | timestamptz | yes | — | audit |
| `deletedAt` | timestamptz | yes | — | audit |

#### `frame_entries`

Product-module "frame" entries.

| Column | Type | Null | Default | Notes |
|---|---|---|---|---|
| `id` | uuid | no | UUIDV4 | PK |
| `week_of` | text | no | — |  |
| `posted_date` | text | no | — |  |
| `formal` | text | no | — |  |
| `recognition` | text | no | — |  |
| `application` | text | no | — |  |
| `management` | text | no | — |  |
| `emotional` | text | no | — |  |
| `author` | text | no | — |  |
| `importHash` | varchar | yes | — | unique, audit |
| `createdAt` | timestamptz | yes | — | audit |
| `updatedAt` | timestamptz | yes | — | audit |
| `deletedAt` | timestamptz | yes | — | audit |
| `organizationId` | uuid | yes | — | FK |
| `campusId` | uuid | yes | — | FK |
| `createdById` | uuid | yes | — | FK, audit |
| `updatedById` | uuid | yes | — | FK, audit |

_Relations:_

- **belongs to** `organizations` as `organization` (FK `organizationId`)
- **belongs to** `campuses` as `campus` (FK `campusId`)

#### `user_progress`

Per-user progress (sign learning, zone check-ins).

| Column | Type | Null | Default | Notes |
|---|---|---|---|---|
| `id` | uuid | no | UUIDV4 | PK |
| `progress_type` | text | no | — |  |
| `item_id` | text | no | — |  |
| `value` | text | yes | — |  |
| `score` | integer | yes | — |  |
| `metadata` | jsonb | yes | — |  |
| `importHash` | varchar | yes | — | unique, audit |
| `createdAt` | timestamptz | yes | — | audit |
| `updatedAt` | timestamptz | yes | — | audit |
| `deletedAt` | timestamptz | yes | — | audit |
| `organizationId` | uuid | yes | — | FK |
| `campusId` | uuid | yes | — | FK |
| `userId` | uuid | yes | — | FK |
| `createdById` | uuid | yes | — | FK, audit |
| `updatedById` | uuid | yes | — | FK, audit |

_Relations:_

- **belongs to** `organizations` as `organization` (FK `organizationId`)
- **belongs to** `campuses` as `campus` (FK `campusId`)
- **belongs to** `users` as `user` (FK `userId`)

#### `safety_quiz_results`

Product-module safety quiz results.

| Column | Type | Null | Default | Notes |
|---|---|---|---|---|
| `id` | uuid | no | UUIDV4 | PK |
| `quiz_id` | text | no | — |  |
| `quiz_title` | text | no | — |  |
| `week_of` | text | no | — |  |
| `score` | integer | no | — |  |
| `total_questions` | integer | no | — |  |
| `answers` | jsonb | no | — |  |
| `user_name` | text | no | — |  |
| `user_role` | text | no | — |  |
| `completed_at` | timestamptz | no | — |  |
| `importHash` | varchar | yes | — | unique, audit |
| `createdAt` | timestamptz | yes | — | audit |
| `updatedAt` | timestamptz | yes | — | audit |
| `deletedAt` | timestamptz | yes | — | audit |
| `organizationId` | uuid | yes | — | FK |
| `campusId` | uuid | yes | — | FK |
| `userId` | uuid | yes | — | FK |
| `createdById` | uuid | yes | — | FK, audit |
| `updatedById` | uuid | yes | — | FK, audit |

_Relations:_

- **belongs to** `organizations` as `organization` (FK `organizationId`)
- **belongs to** `campuses` as `campus` (FK `campusId`)
- **belongs to** `users` as `user` (FK `userId`)

#### `walkthrough_checkins`

Product-module walkthrough check-ins.

| Column | Type | Null | Default | Notes |
|---|---|---|---|---|
| `id` | uuid | no | UUIDV4 | PK |
| `teacher_name` | text | no | — |  |
| `classroom` | text | no | — |  |
| `director_name` | text | no | — |  |
| `check_in_date` | date | no | — |  |
| `check_in_time` | time | no | — |  |
| `attitude_rating` | integer | no | — |  |
| `attitude_comment` | text | yes | — |  |
| `classroom_management_rating` | integer | no | — |  |
| `classroom_management_comment` | text | yes | — |  |
| `cleanliness_rating` | integer | no | — |  |
| `cleanliness_comment` | text | yes | — |  |
| `vibes_rating` | integer | no | — |  |
| `vibes_comment` | text | yes | — |  |
| `team_dynamics_rating` | integer | no | — |  |
| `team_dynamics_comment` | text | yes | — |  |
| `emergency_exit_rating` | integer | no | — |  |
| `emergency_exit_comment` | text | yes | — |  |
| `lesson_plan_rating` | integer | no | — |  |
| `lesson_plan_comment` | text | yes | — |  |
| `overall_notes` | text | yes | — |  |
| `importHash` | varchar | yes | — | unique, audit |
| `createdAt` | timestamptz | yes | — | audit |
| `updatedAt` | timestamptz | yes | — | audit |
| `deletedAt` | timestamptz | yes | — | audit |
| `organizationId` | uuid | yes | — | FK |
| `campusId` | uuid | yes | — | FK |
| `createdById` | uuid | yes | — | FK, audit |
| `updatedById` | uuid | yes | — | FK, audit |

_Relations:_

- **belongs to** `organizations` as `organization` (FK `organizationId`)
- **belongs to** `campuses` as `campus` (FK `campusId`)

#### `personality_quiz_results`

Product-module personality quiz results.

| Column | Type | Null | Default | Notes |
|---|---|---|---|---|
| `id` | uuid | no | UUIDV4 | PK |
| `personality_type` | text | no | — |  |
| `quiz_answers` | jsonb | no | — |  |
| `completed_at` | timestamptz | no | — |  |
| `importHash` | varchar | yes | — | unique, audit |
| `createdAt` | timestamptz | yes | — | audit |
| `updatedAt` | timestamptz | yes | — | audit |
| `deletedAt` | timestamptz | yes | — | audit |
| `organizationId` | uuid | yes | — | FK |
| `campusId` | uuid | yes | — | FK |
| `userId` | uuid | yes | — | FK |
| `createdById` | uuid | yes | — | FK, audit |
| `updatedById` | uuid | yes | — | FK, audit |

_Relations:_

- **belongs to** `organizations` as `organization` (FK `organizationId`)
- **belongs to** `campuses` as `campus` (FK `campusId`)
- **belongs to** `users` as `user` (FK `userId`)

### Policy & Audio

#### `policy_documents`

Unified store for the Safety Protocols and Handbook & Policies pages (Workstream 11). `category` selects the page; `tag` is the finer sub-category.

| Column | Type | Null | Default | Notes |
|---|---|---|---|---|
| `id` | uuid | no | UUIDV4 | PK |
| `title` | text | no | — |  |
| `body` | text | yes | — |  |
| `category` | enum | no | — | `safety_protocol` \| `handbook_policy` |
| `tag` | varchar | yes | — | sub-category / safety card icon |
| `author` | varchar | yes | — | display name of the creating user |
| `steps` | jsonb | yes | — | author-filled procedure steps (safety) |
| `autism_considerations` | jsonb | yes | — | author-filled considerations (safety) |
| `version` | integer | no | 1 | bumped on title/body/steps/considerations change |
| `active` | boolean | no | true |  |
| `importHash` | varchar | yes | — | unique, audit |
| `organizationId` | uuid | yes | — | FK |
| `campusId` | uuid | yes | — | FK |
| `createdById` | uuid | yes | — | FK, audit |
| `updatedById` | uuid | yes | — | FK, audit |
| `createdAt` | timestamptz | yes | — | audit |
| `updatedAt` | timestamptz | yes | — | audit |
| `deletedAt` | timestamptz | yes | — | audit |

_Relations:_

- **belongs to** `organizations` as `organization` (FK `organizationId`)
- **belongs to** `campuses` as `campus` (FK `campusId`)
- **has many** `policy_acknowledgments` (FK `policyDocumentId`)

#### `policy_acknowledgments`

Per-user, per-version acknowledgment of a policy document. Not paranoid (no soft delete).

| Column | Type | Null | Default | Notes |
|---|---|---|---|---|
| `id` | uuid | no | UUIDV4 | PK |
| `policyDocumentId` | uuid | no | — | FK |
| `version` | integer | no | — | acknowledged document version |
| `userId` | uuid | no | — | FK |
| `acknowledgedAt` | timestamptz | no | — |  |
| `organizationId` | uuid | yes | — | FK |
| `campusId` | uuid | yes | — | FK |
| `createdById` | uuid | yes | — | FK, audit |
| `updatedById` | uuid | yes | — | FK, audit |
| `createdAt` | timestamptz | yes | — | audit |
| `updatedAt` | timestamptz | yes | — | audit |

_Unique:_ (`userId`, `policyDocumentId`, `version`) — `policy_acknowledgments_user_document_version_unique`.

_Relations:_

- **belongs to** `policy_documents` as `policyDocument` (FK `policyDocumentId`)
- **belongs to** `users` as `user` (FK `userId`)
- **belongs to** `organizations` as `organization` (FK `organizationId`)

#### `audio_files`

Flexible classroom-timer sound library (Workstream 13). Each row is one `kind`; exactly one of `url` / `recipe` is populated.

| Column | Type | Null | Default | Notes |
|---|---|---|---|---|
| `id` | uuid | no | UUIDV4 | PK |
| `title` | text | no | — |  |
| `kind` | enum | no | `file` | `file` \| `url` \| `recipe` |
| `url` | varchar | yes | — | set for `file` / `url` |
| `recipe` | jsonb | yes | — | synthesis params; set for `recipe` |
| `is_default` | boolean | no | false | platform-global default |
| `importHash` | varchar | yes | — | unique, audit |
| `organizationId` | uuid | yes | — | FK; null = global default |
| `campusId` | uuid | yes | — | FK |
| `createdById` | uuid | yes | — | FK, audit |
| `updatedById` | uuid | yes | — | FK, audit |
| `createdAt` | timestamptz | yes | — | audit |
| `updatedAt` | timestamptz | yes | — | audit |
| `deletedAt` | timestamptz | yes | — | audit |

_Relations:_

- **belongs to** `organizations` as `organization` (FK `organizationId`)
- **belongs to** `campuses` as `campus` (FK `campusId`)

### System

#### `file`

Polymorphic file/attachment store (`belongsTo` + `belongsToColumn` + `belongsToId`).

| Column | Type | Null | Default | Notes |
|---|---|---|---|---|
| `id` | uuid | no | UUIDV4 | PK |
| `belongsTo` | varchar | yes | — |  |
| `belongsToId` | uuid | yes | — | FK |
| `belongsToColumn` | varchar | yes | — |  |
| `name` | varchar | no | — |  |
| `sizeInBytes` | integer | yes | — |  |
| `privateUrl` | varchar | yes | — |  |
| `publicUrl` | varchar | no | — |  |
| `createdAt` | timestamptz | yes | — | audit |
| `updatedAt` | timestamptz | yes | — | audit |
| `deletedAt` | timestamptz | yes | — | audit |
| `createdById` | uuid | yes | — | FK, audit |
| `updatedById` | uuid | yes | — | FK, audit |

#### `auth_refresh_tokens`

Refresh-token rotation records for cookie-based auth (hash, family, expiry, revocation).

| Column | Type | Null | Default | Notes |
|---|---|---|---|---|
| `id` | uuid | no | UUIDV4 | PK |
| `userId` | uuid | no | — | FK |
| `organizationId` | uuid | yes | — | FK |
| `tokenHash` | text | no | — | unique |
| `familyId` | uuid | no | — | FK |
| `previousTokenId` | uuid | yes | — | FK |
| `userAgent` | text | yes | — |  |
| `ipAddress` | text | yes | — |  |
| `expiresAt` | timestamptz | no | — |  |
| `revokedAt` | timestamptz | yes | — |  |
| `replacedByTokenId` | uuid | yes | — | FK |
| `createdAt` | timestamptz | yes | — | audit |
| `updatedAt` | timestamptz | yes | — | audit |

_Relations:_

- **belongs to** `users` as `user` (FK `userId`)
- **belongs to** `organizations` as `organization` (FK `organizationId`)

