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

6.4 KiB
Raw Blame History

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; 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_teacherstaff, academic_year, campus). The grouping unit relating teachers (via class_subjects), students (via class_enrollments), and guardians (via their student).
  • class_enrollmentsstudent↔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_bystaff, 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.