40227-vm/backend/docs/data-model-guide.md

6.9 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 attendance config / legacy aggregates campus_attendance_config + campus_attendance_summaries config plus legacy aggregate rows. Current /attendance entry is staff-only and does not write student/classroom aggregate rows.
Staff attendance (working /attendance) staff_attendance_records active staff-attendance slice for campus, school, and organization attendance rollups.
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.

Organization-level feature flags

organizations.esaEnabled controls whether the ESA Funding Info module is available for an organization. It defaults to true for existing and newly seeded organizations. Turning it off hides the frontend ESA module/routes for users in that organization and blocks backend reads/management of the campus-scoped esa-funding-content payload.

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_teacherusers, 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_byusers, 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 staff_attendance_records; legacy campus_attendance_* aggregate rows remain separate. 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.