40227-vm/backend/docs/data-model-guide.md
2026-06-12 11:44:28 +02:00

83 lines
6.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 **and** the daily zone check-in (`item_id` = campus-local date). |
| Backend-owned editable content | **`content_catalog`** | global JSONB payloads by `content_type`. |
| 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``staff`, `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``staff`, `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
- **`staff`** — the **employment/HR profile** of a user (`employee_number`, `job_title`, `staff_type`, `hire_date`, `status`, `campus`), linked by `userId`. Distinct from `users` (the **account/identity**: login, email, role, password). One user ↔ one staff profile; students/guardians are users **without** a staff record. Already used by the staff-management and staff-attendance slices.
## 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.