83 lines
6.4 KiB
Markdown
83 lines
6.4 KiB
Markdown
# 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.
|