Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

01 — Product Overview (Exams Module)

StudyLyon — multi-tenant ERP / School Management API. This package designs the Exams module client (Flutter, forward-looking spec) against the implemented NestJS backend. Every endpoint, DTO field, schema, index, event, and permission is derived directly from src/modules/exams/**, src/modules/results/**, src/infrastructure/bullmq/event-queue-map.ts, src/modules/rbac/permissions.constants.ts, and docs/IMPLEMENTATION_PLAN.md. No feature is invented; gaps are flagged in Open Questions (§10) and marked (planned) / (forward-looking) / (proposed) inline.


1. Purpose

Exams is the academic evaluation backbone: exam planning (schedule per academic year, type, status), subject scheduling (which class sits which subject on which date/time), marks entry per (exam, student, subject), and publication that releases results to the Results domain. The module owns two collections (examinations, examination_subjects); marks live in the Results module's examination_results collection (examination-result.schema.ts:7-8) — the Exams service writes them directly (examination.service.ts:11, 176-182).

ResponsibilitySource
Create an exam (always starts status: 'draft')examination.service.ts:43-59
List exams (paginated) / get one / update / soft-deleteexamination.service.ts:61-110
Add a subject-slot to an exam (class + date + time + marks)examination.service.ts:112-131; examination-subject.schema.ts:9-31
List an exam's subject-slotsexamination.service.ts:133-137
Enter/upsert marks for one student on one subjectexamination.service.ts:139-195
Publish an exam's results (atomic-ish, two writes)examination.service.ts:203-220
Read results by student / by exam-subject / report cardresult.controller.ts:16-37; result.service.ts:36-98
Grade letter computed from overall percentageresult.service.ts:130-138
Emit domain events (ExaminationCreated, MarksEntered, ExamResultsPublished, …)examination.service.ts:50-57, 163-174, 212-219
ExamResultsPublished → BullMQ in-app queue, job results-publishedevent-queue-map.ts:27

2. Business goals

GoalMeasure
Exam schedule creation per class/term is fastOne POST per exam (POST /examinations), one POST per subject slot (POST /examinations/:id/subjects)
Marks entry is per (exam, student, subject) and idempotentUpsert by (tenantId, studentId, examinationSubjectId) unique index (examination-result.schema.ts:31-33); re-entry updates in place (examination.service.ts:147-175)
Marks never exceed the subject maximumServer guard throws NotFoundException('Marks cannot exceed maximum.') when marksObtained > maximumMarks (examination.service.ts:145-146) — note: it surfaces as 404, client must pre-validate
Publication releases results atomicallypublishResults stamps publishedAt on all results of the exam, then flips exam status → 'published' (examination.service.ts:203-220)
Multi-tenant isolationEvery query tenant-scoped by BaseRepository.scopedFilter (base.repository.ts:20-30); cross-tenant IDs → 404
AuditabilityAll docs carry createdBy/updatedBy/version (base.schema.ts:13-31); $inc version on every update (base.repository.ts:57-66); domain events per mutation

3. User goals

  • Exam coordinator (admin/principal): create the term's exam calendar, attach subject-slots per class, watch statuses draft → active → completed → published, publish at the right moment.
  • Teacher: find my class's subject slot, enter marks for a whole class quickly, correct a wrong mark, never exceed the subject maximum.
  • Student: see my marks per subject, open my report card (aggregate with percentage + overall grade).
  • Parent: view the child's report card once published.
  • Principal/analyst: see exam coverage (which subjects have/need marks) and the publish state of every exam.

4. Stakeholders

Exam coordinators (primary planners), teachers (marks entry), students & parents (consumers of published results), principal (publish authority), support staff (correction disputes), QA/design/engineering.

5. Why this exists

Exams are the highest-stakes academic workflow: the schedule drives the whole school calendar, marks become report cards, and a premature publish erodes trust. The backend provides a simple, idempotent CRUD + publish surface; the client must make planning and marks entry so efficient that teachers finish a class's marks in one sitting — and make publish an explicit, confirmable act.

6. Scope boundaries (exact, from code)

In scope today (backend):

  • POST/GET /examinations, GET/PATCH/DELETE /examinations/:id (examination.controller.ts:27-44)
  • POST/GET /examinations/:id/subjects (examination.controller.ts:45-53)
  • POST /examinations/:id/publish (examination.controller.ts:54-56)
  • GET /results/student/:studentId, GET /results/exam-subject/:examSubjectId, POST /results/exam-subject/:examSubjectId/marks, GET /results/report-card/:studentId/:examId (result.controller.ts:16-37)
  • Domain events: ExaminationCreated, ExaminationUpdated, ExaminationDeleted, ExaminationSubjectAdded, MarksEntered, ExamResultsPublished (examination.service.ts:50-57, 88-96, 102-110, 119-130, 163-174, 212-219)

Not implemented (flag labels used below):

  • POST/GET /examinations/:id/schedule, /hall-tickets, /seating-plan, /marks-import, POST/GET/PATCH /examinations/:id/re-evaluation(planned); docs/IMPLEMENTATION_PLAN.md:213-220 (Phase 4, "Missing Endpoints").
  • Mock tests, DPP, test series — (planned); exam type enum extension with mock_test | dpp | practice_test | all_india_test, isAllIndiaRank, testSeriesId, plus test_series / dpp collections and ExamsService.createMockTest/createDPP/createTestSeries sketched in docs/IMPLEMENTATION_PLAN.md:501-567, 643.
  • RBAC enforcement — no exam.* permission exists in code (permissions.constants.ts:1-97 lists no exam.*; blueprint studylyon-blueprint/04-Modules/Exams.md:64-72 specifies exam.create/update/ delete/mark/publish but they are not seeded) and ExaminationController declares no @Permissions() decorator (examination.controller.ts:21-24) — treat 403 as contract once RBAC lands (OQ-4).
  • Conflict detection with Timetable (studylyon-blueprint/04-Modules/Exams.md:60) — no timetable check exists in examination.service.ts today (OQ-2).
  • Notifications for schedule/result alerts — (planned); only the in-app results-published job is routed (event-queue-map.ts:27); other exam events have no queue route (event-queue-map.ts contains no entry for ExaminationCreated etc.).
  • Client-side analytics — (proposed) per 00-shared/10_QA_Baseline.md §8.

7. Success metrics

  • Coordinator creates an exam + 5 subject slots in < 3 min (6 requests).
  • Teacher enters a 40-student class's marks in < 5 min — one POST per row, upsert safe for retries.
  • 0 marks persisted above a subject maximum (server guard + client pre-validation).
  • Publish round-trip < 1 s; post-publish read of results shows publishedAt stamped.
  • Report card always renders a full subject list — missing results render as 0 marks (result.service.ts:68-81).

8. Edge cases (backend-derived)

  • Marks exceed maximumNotFoundException('Marks cannot exceed maximum.')404 RESOURCE_NOT_FOUND, not 422 (examination.service.ts:145-146). Client must pre-validate marksObtained ≤ maximumMarks and treat a 404 on save as a stale-maximum refresh.
  • Duplicate subject slot: no server-side uniqueness on examination_subjects (index {tenantId, examinationId, classId} is not unique, examination-subject.schema.ts:37) and no duplicate check in addSubject (examination.service.ts:112-131) — the same subject can be added twice; client should warn (OQ-3).
  • Subject date conflicts: no server check that a subject slot's date/time collides with another slot or the timetable (examination.service.ts has no conflict logic) — client-side warning only (OQ-2).
  • Publish immutability: publishResults has no guard — it can be re-called, and PATCH /examinations/:id can still mutate a published exam (the UpdateExaminationDto.status is a free string, examination.dto.ts:49-52; schema enum is not enforced on the findOneAndUpdate path because updateById runs without runValidators, base.repository.ts:57-66) — a bad status could persist (OQ-5). Client must treat published as terminal in UI.
  • Entering marks before a subject exists → 404 "Exam subject not found." (examination.service.ts:144).
  • Report card with no subjects → 404 "No subjects found for this examination." (result.service.ts:51-53).
  • Report card missing results → those subjects count as 0 marks toward totals (result.service.ts:70); percentage can be 0 → grade F (result.service.ts:83-95, 137).
  • Per-subject grade is client-supplied free text (EnterMarksDto.grade, examination-subject.dto.ts:57-60) — never computed server-side per subject; the only computed grade is the report-card overallGrade (A+/A/B+/B/C/D/F at 90/80/70/60/50/40, result.service.ts:130-138).
  • passingMarks vs maximumMarks: both @Min(1) only (examination-subject.dto.ts:36-44); passingMarks > maximumMarks is not rejected — client must validate (OQ-6).
  • Exam window inversion: startDate > endDate is not validated (both IsDateString, examination.dto.ts:19-25); startTime > endTime likewise not validated (examination-subject.dto.ts:28-34) — client-side validation only (OQ-6).
  • Type not enum-validated on the wire: CreateExaminationDto.type is IsString (examination.dto.ts:13-17) though the schema enum is ['midterm','final','unit_test','quarterly','other'] (examination.schema.ts:15-20) — invalid values fail on save() (create path runs validators) but a bad type may 500 as a validator error; client constrains the picker.
  • Soft delete: DELETE /examinations/:idsoftDelete (examination.service.ts:99-110, base.repository.ts:68-74) — no cascade to subjects/results; deleted exam → 404 on every read path. Deleted-but-published results remain readable via Results routes.
  • Pagination: GET /examinations supports page/limit (1–100, default 20), but sort and q are accepted yet ignored by the service (examination.service.ts:67-79 uses find({}, {skip, limit}) only) — client-side sorting/filtering required (OQ-7).

9. Assumptions (module)

  • Client is forward-looking (shared ledger A1 — PRD Phase 1 is backend; these docs specify the full Flutter client per user instruction).
  • Marks entry scope: one ExaminationResult per (student, subject-slot); the roster of students comes from the Students module (students of classId on the subject slot) — no exam-specific roster endpoint exists.
  • Exam list is the module home; subject slots are always reached via an exam detail.
  • exam.* permissions will gate UI ((planned) per blueprint Exams.md:64-72); until the server enforces them, the client gates by role claim defensively but treats the server as authoritative (OQ-4).
  • Publish is a deliberate, confirmable act; the client never optimistically flips status to published (publish has side effects on Results visibility).

10. Open questions (module; global ledger in 00-shared/12)

#ItemImpact
OQ-1No exam "active/completed" transitions server-side — status is just a stored string; only create → draft and publish → published are forced (examination.service.ts:48, 209-211). Who flips active/completed and when?Status picker/stepper UI
OQ-2Blueprint promises "conflict detection with timetable" (Exams.md:60) but no timetable check exists. Is a 422 BUSINESS_RULE_VIOLATION endpoint planned?Subject date-conflict UX
OQ-3Duplicate subject slots are structurally possible (non-unique index). Server fix or client-side dedupe?Add-subject guard UX
OQ-4No exam.* permissions seeded (permissions.constants.ts), no RBAC decorators on ExaminationController (examination.controller.ts:21-24). When does RBAC land?Permission-gated UI visibility
OQ-5PATCH /examinations/:id accepts a free-string status (examination.dto.ts:49-52) and updateById skips runValidators (base.repository.ts:57-66) — bad status could persist; publish is re-callable. Server fix or client-only immutability?Post-publish edit rules
OQ-6passingMarks > maximumMarks, startTime ≥ endTime, startDate > endDate are not server-validated. Client-only constraints?Form validation strategy
OQ-7sort/q query params ignored on GET /examinations (examination.service.ts:67-79). Client-side sort/filter fallback?List UX
OQ-8MarksEntered/ExaminationCreated/ExaminationUpdated/ExaminationDeleted/ExaminationSubjectAdded have no BullMQ route (event-queue-map.ts) — only ExamResultsPublished fires in-app/results-published. When do notifications land?Result-published notification UX
OQ-9Per-subject grade is free text supplied by the client; subjectName in the report card is the raw subjectId string (result.service.ts:75). Are grade normalization and subject-name resolution planned?Report card rendering

11. Glossary (this module)

TermMeaning
Exam (Examination)examinations doc: academicYearId, name, type, startDate, endDate, status, gradingSchemeId? (examination.schema.ts:9-36)
Exam typemidterm, final, unit_test, quarterly, other (examination.schema.ts:15-20)
Exam statusdraft, active, completed, published (default draft, examination.schema.ts:28-33)
Subject slot (ExaminationSubject)examinations_subjects doc: exam + subject + class + date + time window + maximumMarks/passingMarks (examination-subject.schema.ts:9-31)
Mark (ExaminationResult)examination_results doc: student + slot + marksObtained/grade/remarks/publishedAt (examination-result.schema.ts:9-25)
PublishPOST /examinations/:id/publish — stamp publishedAt on all results, set exam status published (examination.service.ts:203-220)
Report cardGET /results/report-card/:studentId/:examId aggregate: per-subject rows + totals + percentage + overallGrade (result.service.ts:46-98)
Envelope{success,message,data,meta?,timestamp,requestId} (shared 00-shared/07)