6.4 KiB
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
- Does a built product slice already cover it? (see "Built slices" below) → reuse it.
- Does a reserved SIS table already model it? (see "Reserved SIS cluster") → wire it, don't recreate.
- 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. - Is it per-campus configuration / aggregate? → look at
content_catalog(global content) or thecampus_*tables. - 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). Anchorsclassesandtimetablesto a period. Plug-in: set/seed years, mark onecurrent.grades— grade levels (Grade 1, K…:name,code,sort_order). NOT marks. Aclassbelongs 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, andtimetable_periodshang off it. (Sosubjects= "what";class_subjects= "this offering".)classes— a class/group (name,section,capacity,grade,homeroom_teacher→staff,academic_year,campus). The grouping unit relating teachers (viaclass_subjects), students (viaclass_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 aclass_subject.assessment_results— a student's outcome (score,grade_letter,remarks) perassessment+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 (statuspresent/absent/late,minutes_late) perstudentId.- Two tables = one session → many student rows. This is the per-student model and is currently unwired. The working
/attendancepage uses the aggregatecampus_attendance_*instead, and staff usestaff_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 aclass_subjectwithin a timetable.
People
staff— the employment/HR profile of a user (employee_number,job_title,staff_type,hire_date,status,campus), linked byuserId. Distinct fromusers(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.