- Get Started
- Localhost
- Architecture
- Structure
- Pattern
- Page
- Layout
- Content
- Types
- Config
- Actions
- Queries
- Authorization
- Validation
- Form
- Table
- Detail
- Card
- Util
- Hooks
- List Params
- Views
- README.md
- ISSUE.md
- Technology Stack
- Database
- File
- CDN Assets
- Entry Points
- Dashboard
- Authentication
- Credentials
- OAuth
- Flow Diagrams
- Multi-Tenancy
- Offline
- Onboarding
- Onboarding Videos
- Add Values
- Admission
- Application
- Attendance
- Compliance
- Profile
- Exams
- Exam Wizard
- Timetable
- Classrooms
- Notifications
- Conference
- LMS (Lumos)
- Finance
- Fee Management
- Invoice
- Wallet
- Salary
- Payroll
- Timesheet
- Expenses
- Budget
- Receipt
- Accounts
- Banking
- Reports
- Dashboard
- Permissions
- Messages
- Integration Flow
- Provision
- AI Document Processing
- Document Intelligence
- Internationalization
- Translation
- Translation Guide
- Icons
- Docs Factory
- Inspiration
- Listings
- Teachers
- Students
- Catalog
- Library
- Contributing
- Code of conduct
- GitHub Workflow
- Database Seeds
- Database Safety
- Test Accounts
- Bulk import
- Playwright
- Prettier
- Block Rebound
- Transportation
Every school progresses through the same dependency chain. Most layers are auto-provisioned the moment onboarding completes — the machinery (stages, idempotency, the repair doctor, the terms-aware calendar engine) is documented in Provision; this page is the map.
The ordering encodes one deliberate idea: the timetable comes before people. A timetable is derivable from structure alone (grades × subjects × sections × schedule), so it is generated teacher-less. Students are then placed into sections, teachers assigned to subjects — and attendance, exams, grades, and reports flow from there.
The layers
| Layer | Name | What it creates | Depends on |
|---|---|---|---|
| 1 | Global catalog | Subject (grade-specific) → chapters → lessons → books | — |
| 2 | School onboarding | School profile + provisioning trigger | 1 |
| 3 | Academic structure | AcademicLevel, AcademicGrade, AcademicStream | 2 |
| 4 | Subject selection | SubjectSelection bridge rows (grade ↔ catalog) | 1, 3 |
| 5 | Schedule | SchoolYear, Term ×N (terms-aware), Period, SchoolWeekConfig | 2 |
| 6 | Classrooms & sections | ClassroomType, Classroom, Section — N per grade | 3 |
| 7 | Timetable | Timetable slots — section × subject × period × room, teacher-less | 4, 5, 6 |
| 8a | Teacher assignment | TeacherSubjectExpertise, TeacherDepartment, slot teacherId | 4, 7 |
| 8b | Student placement | Student.sectionId (+ LMS Enrollment in the grade's subjects) | 6 |
| 9 | Attendance | Attendance — section rosters, period-aware via timetable slots | 7, 8b |
| 10 | Exams | SchoolExam, sessions, marking, ExamResult | 4, 8 |
| 11 | Grades & reports | ReportCard, certificates, transcripts, promotion | 9, 10 |
| 12 | Downstream consumers | Transportation, LMS, conference, library, finance, messaging | varies |
Layers 1–7 are fully auto-provisioned. Layers 8a/8b are admin actions with assistive UIs. Layers 9–11 are operational features that light up as soon as their dependencies exist.
Layer 1 — Global catalog
The shared backbone. All curriculum content lives once in global tables (no schoolId); schools connect through bridge tables.
Subject (grade-specific, per curriculum: us-g1-math, sd-g7-arabic, …)
└─ Chapter
└─ Lesson
Subjects are grade-specific, not level-based. Teacher-created content is saved PENDING + PRIVATE, reviewed by the SaaS operator, then published. School-proposed subjects go through the opt-in proposal flow (approve → publish to catalog → school adds from its picker). Full reference: Catalog.
Layer 2 — School onboarding
Onboarding answers drive everything downstream:
| Onboarding step | Saves into | Drives |
|---|---|---|
| Description | School.schoolType (public, private, international…) | Structure recommendation, curriculum inference |
| Location | School.country, city, state | Curriculum inference, term calendar |
| Capacity | School.sectionsPerGrade, studentsPerSection | Classroom/section provisioning |
| Schedule | School.timetableStructure (structure slug) | Periods, working days, calendar override |
| Computed | Formula |
|---|---|
maxClasses | grades × sectionsPerGrade |
maxStudents | maxClasses × studentsPerSection |
Accepting terms on the legal step fires completeOnboarding() → the provisioning chain. Stages, ordering, and repair semantics: Provision.
Timetable structures
~23 structures define the weekly period layout per country and school type (sd-gov-default 8×45m Sun–Thu, gulf-standard 7×45m, us-standard 7×50m Mon–Fri, ae-primary, jo-secondary, intl-default, …). getRecommendedStructures(country, schoolType, schoolLevel) ranks them:
| Signal | Score |
|---|---|
| Country exact match | +40 |
| Region match | +25 |
Wildcard * | +5 |
schoolType match | +20 |
schoolLevel match | +10 |
Score ≥ 30 → recommended; ≥ 50 → auto-select. Region map: SA/AE/QA/KW/BH/OM → GULF; EG/JO/LB/TN/MA/DZ → MENA.
A structure can carry a calendar override — sd-british follows the GB 3-term calendar even though the school's country is SD.
Layer 3 — Academic structure
setupCatalogForSchool() creates the hierarchy per curriculum config (SD Arabic 6+3+3 with Science/Arts streams, US 5+3+4, GB Key Stages, CBSE, generic fallback):
| Model | Key fields |
|---|---|
AcademicLevel | level, levelOrder, startGrade, endGrade |
AcademicGrade | gradeNumber, levelId, yearLevelId |
AcademicStream | streamType (SCIENCE, ARTS), gradeId |
schoolLevel scopes the range: primary → grades 1–6, secondary → 7–12, both/null → 1–12.
Layer 4 — Subject selection
The bridge between global catalog and school grades — knowing the grade and the curriculum means knowing the subjects. Auto-assignment matches catalog subject.grades arrays with a progressive fallback (country+curriculum+type → country+curriculum → worldwide → US baseline), ~99% auto-assigned. Details: Catalog.
Schools customize from there: add subjects from other systems, hide per grade (isActive), hide chapters/lessons (ContentOverride), rename (customName), and set weeklyPeriods — the number the timetable generator schedules per week.
Layer 5 — Schedule (terms-aware)
The schedule stage derives the academic year, term count, term boundaries, and the currently-active term from the school's country, structure, and today's date — ACADEMIC_CALENDARS in timetable/calendars.ts maps each country to its ministry pattern (SA = 2 semesters post-1447 revert, AE/KW/GB = 3 terms, IN = April-anchored year-wrap, …).
What gets created: one SchoolYear, N Terms with a date-correct isActive, all Periods from the structure, and SchoolWeekConfig (working days + lunch). Every record is admin-editable afterwards in School → Configuration → Academic. Full calendar table and resolution chain: Provision.
Layer 6 — Classrooms & sections
Capacity settings (sections-per-grade + capacity) drive N sections per grade, each paired one-to-one with exactly one main classroom — Section.classroomId is a single nullable FK, so a section can never have more than one. Room type governs: the main room is the classroom-type homeroom (Grade 7-A room ↔ Grade 7-A section). The chain this encodes:
Classroom (main) → belongs to a grade → grade + curriculum → subjects taught here
Section → the student cohort that occupies that classroom
Labs and common spaces are a different thing — not grade-owned, never set as a section's main classroom; created with their own ClassroomType (Lab, Computer Room, Library) and reached per-period through the timetable (Timetable.classroomId), available to the generator for lab-flagged subjects.
No course axis (Class removed)
There is no separate course record. A grade has subjects (SubjectSelection, with weekly periods) and sections; the timetable, exams, results, assignments and attendance are set per section (or the whole grade) and subject, and teaching is SubjectTeacher. The Class, StudentClass and ClassTeacher models, and every classId column, were removed on 2026-10-04 (see the classes-removed note).
Layer 7 — Timetable
Slots are section-based:
Timetable {
schoolId, termId, dayOfWeek, periodId,
sectionId, // who (the cohort) — primary
subjectId, // what
classroomId, // where
teacherId, // who teaches — nullable, assigned later
weekOffset
}
Unique constraints prevent double-booking (school, term, day, period, section, week offset — and the same for the classroom). Auto-generation distributes each grade's subjects (weeklyPeriods each) across working days × teaching periods into the section's paired room, teacher-less. Manual editing in the grid is also section-first: pick section + subject (+ teacher when known).
getRecommendedStructures ranks layouts at onboarding; if no terms exist yet, the grid renders a non-persisting draft schedule with an admin CTA to provision for real.
Layer 8a — Teacher assignment
Teachers attach to subjects, then to slots:
| Role | Where | Description |
|---|---|---|
| Subject expert | TeacherSubjectExpertise | Qualification per subject (expertiseLevel) |
| Slot teacher | Timetable.teacherId | Who teaches this section-period |
| Homeroom teacher | Section.homeroomTeacherId | Pastoral, per section |
| Department member / head | TeacherDepartment | isPrimary, isDepartmentHead |
| Subject teacher | SubjectTeacher | One teacher per section × subject per term |
deleteTeacher blocks while SubjectTeacher or Timetable references exist; deleteSubject blocks on expertise, exam, or question-bank references. A section × subject pair with no teacher shows as "Needs teacher" in the timetable; assign one from the Add-teacher wizard, the /teachers row menu ("Assign subjects"), or the board at /timetable/assignments.
Layer 8b — Student placement
The 5-step admission wizard ends in ADMITTED → placement:
placement-dialog.tsx(per student) andbulk-placement.tsx(batch) both callplaceStudentInSection(applicationId, sectionId)— validatesmaxCapacity, setsStudent.sectionId(the operational placement), then gives the student an LMSEnrollmentin every subject of the section's grade (syncStudentSubjectEnrollments— stream-filtered, adds only). NoStudentClassrow is written any more.- Through the section, a student inherits the classroom, the timetable, the attendance roster, and every exam, assignment or notice set for the section or its whole grade.
extractGradeNumber()insrc/lib/grade-utils.tsparses free-text grades ("Grade 5","5th grade","الصف الخامس").
Layer 9 — Attendance
Attendance is section-based: the roster is Student.sectionId (there is no class roster), and period-mode lights up directly from the timetable —
- Teacher scoping: a teacher can mark sections they have timetable slots in (or homeroom).
- Current-period auto-selection: the active
Period× the teacher's slot resolves the section automatically. - Period marking writes
sectionId+periodId+timetableId; whole-day marking works with no timetable at all. - Sessions, hall passes and competition entries are per section too; there is no
classIdanywhere.
Status: ~92%, production-hardened (security pass 2026-06-02, 575 tests green). Compliance (ADEK eSIS) exports daily-mode records. Full reference: Attendance.
Layer 10 — Exams
The assessment stack is set for a grade plus one of its sections (or the whole grade) + subject + term:
SchoolExam → ExamSession → StudentAnswer → MarkingResult → ExamResult
QuestionBank → GeneratedExam → GeneratedPaper
Result (assignments / homework grades)
Exam scheduling checks timetable slots for conflicts (app-layer). Known open items from the production audit: two competing result models (Result + ExamResult), score-precision mismatch (Int vs Decimal), and cascade chains that don't preserve certificate proof — tracked in the exams block records. Full reference: Exams.
Layer 11 — Grades & reports
ExamResult + Result
→ ReportCard (per student per term) → ReportCardGrade (per subject)
├─ Certificate PDF (composable engine, 4 regional presets) — production-ready
├─ Transcript (multi-year frozen snapshot, QR verify)
├─ Notifications (grade_posted, report_ready, certificate_issued, promotion_decided)
└─ Promotion / Retention → Student.academicGradeId
Promotion operates per grade (PromotionPolicy by gradeId): batch evaluation → GPA + failed subjects + attendance percentage (layer 9 feeds layer 11) → admin review → execute (StudentYearLevel, grade reassignment) → notify.
Known gap: two different DEFAULT_BOUNDARIES scales exist (reports 9-tier vs grades 12-tier) when a school has no SchoolGradingConfig — converge before relying on letter grades.
Layer 12 — Downstream consumers
Each block consumes a different axis of the academic core:
| Block | Consumes | Coupling |
|---|---|---|
| Conference | sectionId (eligibility + fan-out), timetableId (start from slot), termId | Section-based |
| Transportation | studentId only (routes, boarding) | Decoupled from sections/timetable |
| LMS (Lumos) | catalogSubjectId (enrollment per catalog subject) | Catalog-based, not section-based |
| Library | schoolId only (global-first book adoption) | Fully decoupled |
| Finance | studentId (fee assignment), optional grade grouping | Student-based |
| Messaging / WhatsApp | Section groups | Section-based |
None of these block the academic chain; they activate independently as their own setup completes.
Failure modes
| Missing | Blocks |
|---|---|
| Catalog | Subject selection, timetable generation |
| Academic structure | Subject selection, sections |
| Terms | Timetable (draft fallback shown), attendance period-mode |
| Sections | Timetable generation, attendance rosters, placement |
SubjectSelection | Timetable generation (No sections have subjects) |
| Timetable slots | Attendance period-mode, teacher section-scoping, conference-from-slot |
SubjectTeacher | Timetable slots show "Needs teacher" |
| Exams / Results | Report cards, transcripts |
| Report cards | Promotion (GPA required) |
Provisioning-specific failure modes and the readiness check live in Provision.
See also
- Provision — the auto-provisioning engine, terms-aware calendars, the doctor
- Onboarding
- Catalog
- Admission
- Attendance
- Multi-Tenancy
On This Page
The layersLayer 1 — Global catalogLayer 2 — School onboardingTimetable structuresLayer 3 — Academic structureLayer 4 — Subject selectionLayer 5 — Schedule (terms-aware)Layer 6 — Classrooms & sectionsNo course axis (Class removed)Layer 7 — TimetableLayer 8a — Teacher assignmentLayer 8b — Student placementLayer 9 — AttendanceLayer 10 — ExamsLayer 11 — Grades & reportsLayer 12 — Downstream consumersFailure modesSee also