03 — User Journey (Exams Module)
- 1. J1 — Plan an exam (Coordinator)
- 2. J2 — Enter marks (Teacher)
- 3. J3 — Publish results (Principal / Coordinator)
- 4. J4 — View own results (Student)
- 5. J5 — Correct a wrong mark (Teacher)
- 6. J6 — Edit / delete an exam (Coordinator)
- 7. J7 — Exam lifecycle glance (any role)
- 8. Journey → endpoint cheat sheet
End-to-end journeys mapped to exact API calls (
examination.controller.ts,result.controller.ts). Journey steps that rely on endpoints not yet implemented are marked(planned). Error paths quote real service messages (examination.service.ts,result.service.ts).
1. J1 — Plan an exam (Coordinator)
Goal: stand up a term exam with subject slots in under 3 minutes.
- Open Exams tab → list loads from
GET /api/v1/examinations?page=1&limit=20(examination.controller.ts:30-32). - Tap "New exam" → form (see 08_Form_Specifications.md §1):
academicYearId(MongoId),name,type(midterm|final|unit_test|quarterly|other,examination.schema.ts:15-20),startDate,endDate(CreateExaminationDto,examination.dto.ts:4-26).
- Submit →
POST /api/v1/examinations→ 201 doc withstatus: 'draft'(examination.service.ts:48) → navigates to exam detail. - Add slots: for each class+subject →
POST /api/v1/examinations/:id/subjectswith{examinationId, subjectId, classId, date, startTime ('09:00'), endTime, maximumMarks, passingMarks}(CreateExaminationSubjectDto,examination-subject.dto.ts:11-45).- Client warns if another slot overlaps date+time (no server check — OQ-2, 01_Product_Overview.md §10) and if the same subject is already added (no server dedupe — OQ-3).
- Client blocks
passingMarks > maximumMarksandstartTime ≥ endTime(OQ-6).
- List refresh shows the exam; badge
draft.
Success signal: detail screen lists N slots; list badge draft.
Failure paths: 400 VALIDATION_ERROR (bad ids/dates) → inline field errors;
409 not applicable today (no unique constraints on exam name/slot — OQ-3).
2. J2 — Enter marks (Teacher)
Goal: mark a full class in one sitting; correct errors in place.
- Open exam detail → tap a slot → marks screen (
GET /api/v1/examinations/:id/subjectsfor slot list, thenGET /api/v1/results/exam-subject/:examSubjectIdfor existing marks,result.controller.ts:21-25). - Roster = students of the slot's
classId(Students module); each row prefilled from the marks response (absent result = unmarked). - Type
marksObtained(numeric keyboard,@Min(0)—examination-subject.dto.ts:51-55), optionalgrade+remarks. - Save row →
POST /api/v1/results/exam-subject/:examSubjectId/markswith{studentId, marksObtained, grade?, remarks?}(EnterMarksDto,examination-subject.dto.ts:47-66) — upsert: existing(studentId, examinationSubjectId)result is updated in place (examination.service.ts:147-175). - Client pre-validates
marksObtained ≤ maximumMarks; if the server still returns 404 "Marks cannot exceed maximum." (examination.service.ts:145-146), treat as stale maximum → refresh slot, re-enter. - Coverage bar shows marked/total; teacher exits at 100%.
Success signal: every roster row has a saved mark; coverage bar 100%.
Failure paths: offline → row queued (module offline queue, 13_State_Management.md);
404 "Exam subject not found." (examination.service.ts:144) if slot deleted → return
to exam detail; 429 → backoff + retry.
3. J3 — Publish results (Principal / Coordinator)
Goal: release results for one exam; verify the published state.
- Exam detail → coverage check (per-slot
GET /api/v1/results/exam-subject/:idmarked/total counts). - Tap Publish → confirm dialog (explicit, never optimistic — publish has side effects: results become visible downstream).
POST /api/v1/examinations/:id/publish(examination.controller.ts:54-56): server stampspublishedAt = nowon every result of the exam's slots (examination-result.repository.ts:42-51) and sets examstatus: 'published'(examination.service.ts:209-211); emitsExamResultsPublished(examination.service.ts:212-219) →in-appqueue, jobresults-published(event-queue-map.ts:27).- Detail updates: status badge
published; slot marks showpublishedAt. - Students/parents notified
(planned)— only the in-app job route exists.
Success signal: badge published; marks rows show published timestamp.
Failure paths: 404 "Examination not found." (deleted/cross-tenant,
examination.service.ts:209 uses updateById on a missing id — no explicit
re-check; treat as refresh+return). Re-publish is possible server-side (no guard,
OQ-5) — UI hides the button after publish.
4. J4 — View own results (Student)
Goal: see marks per subject and the report card after publish.
- Results tab →
GET /api/v1/results/student/:studentId(result.controller.ts:16-20) → list of mark docs. - Open report card →
GET /api/v1/results/report-card/:studentId/:examId(result.controller.ts:32-37) → subject rows (name resolved client-side fromsubjectId— OQ-9), totals,percentage(2 dp),overallGrade(A+/A/B+/B/C/D/F,result.service.ts:130-138). - Missing marks render as 0 (
result.service.ts:70) with an explicit "not marked" hint — the payload cannot distinguish "0" from "absent".
Success signal: card renders with totals + grade.
Failure paths: 404 "No subjects found for this examination."
(result.service.ts:51-53) → empty state "No subjects scheduled yet."
5. J5 — Correct a wrong mark (Teacher)
Goal: fix a typo after saving.
- Marks screen → tap row → edit cell → save → same
POST /results/exam-subject/:examSubjectId/marksendpoint (upsert updatesmarksObtained/grade/remarks,examination.service.ts:151-161); docversionincrements (base.repository.ts:57-66). MarksEnteredevent re-emitted (examination.service.ts:163-174).- Snackbar "Mark updated"; no history shown (no per-field change log in payload —
only the event carries
examinationSubjectId+studentId).
Success signal: corrected value persists across re-fetch. Failure paths: same as J2; after publish, still editable server-side (OQ-5) — UI may allow but flag "published exam".
6. J6 — Edit / delete an exam (Coordinator)
- Exam detail → Edit →
PATCH /api/v1/examinations/:idwith partial{name?, type?, startDate?, endDate?, status?}(UpdateExaminationDto,examination.dto.ts:28-53). Client never sendsstatusexcept draft→active (OQ-1); disables status editing oncepublished(OQ-5). - Delete → confirm dialog →
DELETE /api/v1/examinations/:id→ soft delete (examination.service.ts:99-110,base.repository.ts:68-74); subjects/results are not cascaded — subject reads 404, but report cards keep working off the results collection. - Return to list; exam gone (filtered by
isDeleted: false,base.repository.ts:20-30).
Success signal: list no longer shows the exam; snackbar "Exam deleted".
Failure paths: 404 "Examination not found." (examination.service.ts:101, 87).
7. J7 — Exam lifecycle glance (any role)
- List (
GET /examinations, paginatedpage/limit,metafrombuildPaginationMeta,pagination-query.dto.ts:41-55);sort/qare ignored by the service (OQ-7) → client sorts bystartDatelocally. - Statuses map to badges:
draft(neutral),active(primary),completed(secondary),published(success) — schema enumexamination.schema.ts:28-33. - Row tap → detail (
GET /examinations/:id) → slots (GET /examinations/:id/subjects).
8. Journey → endpoint cheat sheet
| Journey | Primary endpoints |
|---|---|
| J1 Plan exam | POST /examinations, POST /examinations/:id/subjects, GET /examinations |
| J2 Enter marks | GET /examinations/:id/subjects, GET /results/exam-subject/:id, POST /results/exam-subject/:id/marks |
| J3 Publish | POST /examinations/:id/publish, GET /examinations/:id |
| J4 Own results | GET /results/student/:studentId, GET /results/report-card/:studentId/:examId |
| J5 Correct mark | POST /results/exam-subject/:id/marks (upsert) |
| J6 Edit/delete | PATCH /examinations/:id, DELETE /examinations/:id |
| J7 Lifecycle glance | GET /examinations, GET /examinations/:id, GET /examinations/:id/subjects |