- Introduction
- Pitch
- Hogwarts
- Live Demo
- MVP
- Roadmap
- Launch Sprint
- PRD
- 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
- Playwright
- Prettier
- Block Rebound
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 (+ StudentClass for the grade-book axis) | 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.
The course axis (Class records)
Separate from sections, a Class is one subject-course (Math – Grade 7, per term) — the de-emphasized grouping axis the grade-book needs. The standalone "Generate Classes from Catalog" / "Enroll Students" actions were removed from Classrooms › Configure (2026-06-18): classrooms stays lean (grades → sections → one main classroom), and because the grade + curriculum already imply the subjects, Class/StudentClass are maintained in the Classes module rather than provisioned from the rooms page. The timetable no longer requires Class records; exams, results, and assignments still do (by design — see the legacy classId policy).
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
classId, // legacy rows only — backfilled to sectionId on edit
weekOffset
}
Three unique constraints prevent double-booking (section, classroom, and legacy-class per period-slot). 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 |
| Course teacher | Class.teacherId + ClassTeacher | The course axis (grade-book, co-teaching) |
deleteTeacher blocks while Class.teacherId or Timetable references exist; deleteSubject blocks on expertise, class, exam, or question-bank references.
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 — only when the section is linked to anAcademicGrade— createsStudentClassrows for the grade's course classes (the grade-book) and syncs LMSEnrollment. A section with nogradeIdgets the placement but silently skips the course-class and LMS sync.- Through the section, a student inherits the classroom, the timetable, and the attendance roster. Through StudentClass, they appear in grade-books and exams.
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 (not StudentClass), 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. - Legacy
classIdrows remain as read-only history.
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 runs on the course axis (classId + subjectId):
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), classId? optional grouping | Student-based |
| Messaging / WhatsApp | Section groups + Class groups | Both axes |
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 |
StudentClass | Grade-books, exams enrollment (not the timetable — section covers that) |
| 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 & sectionsThe course axis (Class records)Layer 7 — TimetableLayer 8a — Teacher assignmentLayer 8b — Student placementLayer 9 — AttendanceLayer 10 — ExamsLayer 11 — Grades & reportsLayer 12 — Downstream consumersFailure modesSee also