# Data Model Guide — purpose, status & reuse

> **Read this before creating a new model, adding a column, or any DB
> manipulation (migration/seed).** Its job is to prevent reinventing tables that
> already exist. Many "new feature" needs are already covered by a built slice or
> by a **reserved** generated table that should be *wired*, not duplicated.
> Column-level detail lives in [`database-schema.md`](database-schema.md); this
> file is the **semantic / decision** layer.

## Before you add a model — checklist

1. Does a **built product slice** already cover it? (see "Built slices" below) → reuse it.
2. Does a **reserved SIS table** already model it? (see "Reserved SIS cluster") → wire it, don't recreate.
3. Is the data **per-user self-state**? → it likely belongs in `user_progress` (e.g. the zone check-in reuses it), not a new table.
4. Is it **per-campus configuration / aggregate**? → look at `content_catalog` (global content) or the `campus_*` tables.
5. Only if none fit: add a model, following the per-slice template, and **update `database-schema.md` + this guide**.

Also: every tenant-owned table carries `organizationId` (+ optional `campusId`) and is scoped in `db/api` via `findOwnedByPk`/`tenantWhere`; most are `paranoid` (soft delete). New tables must follow the same conventions.

## Built slices — already implemented (do NOT reinvent these)

| Need | Use | Notes |
|---|---|---|
| Staff acknowledge a policy/safety doc | **`policy_documents` + `policy_acknowledgments`** | per `userId × documentId × version`, idempotent. **Do not** repurpose `assessments` for acknowledgment. |
| Classroom-timer sounds (file/url/recipe) | **`audio_files`** | `kind` discriminator; recipe = synth params. |
| Campus daily attendance (working `/attendance`) | **`campus_attendance_config` + `campus_attendance_summaries`** | manual **aggregate** entry by `office_manager` (`FILL_ATTENDANCE`). NOT per-student rows. |
| Staff attendance | **`staff_attendance_records`** | the staff-attendance slice. |
| Weekly F.R.A.M.E. entry | **`frame_entries`** | `week_of` is the canonical Sunday-start ISO (`shared/constants/week.ts`). |
| Per-user progress / daily self-state | **`user_progress`** | `progress_type` + `item_id` + `value`. Backs sign-learned, Classroom Support favorites, and the daily zone check-in (`item_id` = campus-local date). |
| Backend-owned editable content | **`content_catalog`** | scoped/editable JSONB payloads by `content_type`; truly global static catalogs stay in frontend constants. |
| File upload/download | the **file subsystem** + `file` table | see `file.md`; downloads enforce per-file ownership. |

## Reserved SIS cluster — kept but **not yet wired**

These generated tables have **no product UI yet**. They are retained for future
development (owner-approved). When a feature needs one, **wire the existing
table** (build the service/route/UI) — do not create a parallel model. They form
a coherent academic/SIS graph:

### Academic structure

- **`academic_years`** — the school year (`name`, `start_date`, `end_date`, `current`). Anchors `classes` and `timetables` to a period. *Plug-in:* set/seed years, mark one `current`.
- **`grades`** — grade **levels** (Grade 1, K…: `name`, `code`, `sort_order`). NOT marks. A `class` belongs to a grade.
- **`subjects`** — the reusable **subject catalog** (Math, English: `name`, `code`, `description`).
- **`class_subjects`** — a **subject taught in a class by a teacher** (`classId` + `subjectId` + `teacherId`). The many-to-many junction class↔subject; `assessments`, `attendance_sessions`, and `timetable_periods` hang off it. (So `subjects` = "what"; `class_subjects` = "this offering".)
- **`classes`** — a class/group (`name`, `section`, `capacity`, `grade`, `homeroom_teacher`→`users`, `academic_year`, `campus`). The grouping unit relating teachers (via `class_subjects`), students (via `class_enrollments`), and guardians (via their student).
- **`class_enrollments`** — **student↔class membership** (`classId` + `studentId`, `enrolled_on`, `ended_on`, `status`).

### Assessments (header/detail pair)

- **`assessments`** — the **definition** of a task/exam (`name`, `assessment_type`, `max_score`, `due_at`, `instructions`), belongs to a `class_subject`.
- **`assessment_results`** — a **student's outcome** (`score`, `grade_letter`, `remarks`) per `assessment` + `studentId`.
- Two tables = one-to-many (one assessment → many student results). **Academic grading only** — not a general "acknowledgment" store (use `policy_acknowledgments`).

### Attendance (header/detail pair — student-level)

- **`attendance_sessions`** — one roll-call **event** for a class (`session_date`, `session_type`, `taken_by`→`users`, `class`/`class_subject`).
- **`attendance_records`** — a **student's status** in a session (`status` present/absent/late, `minutes_late`) per `studentId`.
- Two tables = one session → many student rows. **This is the per-student model and is currently unwired.** The working `/attendance` page uses the **aggregate** `campus_attendance_*` instead, and staff use `staff_attendance_records`. If per-student attendance is needed, wire these; do not fold student + staff + aggregate into one model without a deliberate decision.

### Scheduling (header/detail pair)

- **`timetables`** — a schedule **version** for a campus/year (`effective_from`, `effective_to`, `status`).
- **`timetable_periods`** — individual **slots** (`day_of_week`, `starts_at`, `ends_at`, `room`) for a `class_subject` within a timetable.

### People

- **`users`** — the account, identity, role, and scope. Do not add a separate employment profile table; employees, students, and guardians are all users with roles and scope columns. Staff classification is derived from `roles.name` plus the user's scope columns.

## Pruned — do NOT re-add

`students`, `guardians`, `fee_plans`, `invoices`, `payments`, and the old generic
`documents` entity were **removed** (this is not a SIS; students/guardians are
**roles** in `roles`/`users`, the finance cluster was unused, and the handbook
moved to `policy_documents`). Don't recreate them — see `docs/backlog.md`.

## Header/detail rationale

`assessments`/`results`, `attendance_sessions`/`records`, and
`timetables`/`periods` are all the same shape: a **parent** describes the event
and **children** hold per-subject rows. Keep that split when wiring — it is what
makes grouping, reporting, and scoping clean.
