03 — User Journey (Results Module)
- Journey A — Teacher: enter marks for one exam-subject (happy path)
- Journey A′ — rejected marks (error path)
- Journey B — Coordinator: verify & publish exam results
- Journey C — Student: view report card
- Journey D — Admin: create exam + subjects (precondition to A)
- Journey E — (planned) bulk marks import
- Flow summary
Journeys map to real endpoints; async hops go through BullMQ (
event-queue-map.ts:27—ExamResultsPublished→in-app/results-published). States marked(planned)are not implemented server-side.
Journey A — Teacher: enter marks for one exam-subject (happy path)
- Open exam → subject list:
GET /api/v1/examinations/:id/subjects(examination.controller.ts:51-53). - Open subject detail: shows
date,startTime/endTime,maximumMarks,passingMarks(examination-subject.schema.ts:18-31). - Load existing entries:
GET /api/v1/results/exam-subject/:examSubjectId(result.controller.ts:21-25). - Enter marks per student:
POST /api/v1/results/exam-subject/:examSubjectId/markswithEnterMarksDto(examination-subject.dto.ts:47-65). - Re-enter to correct → same endpoint, server updates in place (idempotent upsert,
examination.service.ts:147-175); no 409 duplicates. - Client keeps local draft; syncs in background (see 15_Flutter_Implementation_Guide.md §Offline).
Journey A′ — rejected marks (error path)
- Marks >
maximumMarks→ server throws 404RESOURCE_NOT_FOUND(examination.service.ts:145-146). Client normalises to "marks cannot exceed maximum" and keeps the field in error state. See 14_QA_Checklist.md §5 for the 404-vs-422 quirk. - Missing/invalid
studentIdorexamSubjectId→VALIDATION_ERROR(400) per 07_API_Conventions.md.
Journey B — Coordinator: verify & publish exam results
- Open exam detail:
GET /api/v1/examinations/:id(examination.controller.ts:33-35). - Check status (
draft | active | completed | published,examination.schema.ts:28-33). - Inspect subject-wise completion:
GET /api/v1/examinations/:id/subjects+ per-subject results (Journey A step 3). - Publish:
POST /api/v1/examinations/:id/publish(examination.controller.ts:54-56).- Server stamps
publishedAton every result row of every subject (examination.service.ts:207), sets examstatus: 'published'(:209-211), emitsExamResultsPublished(:212-219) →in-appqueue →results-publishedjob (event-queue-map.ts:27).
- Server stamps
- Student/parent in-app notification delivered (async — job on
in-appqueue). - ⚠️ No un-publish endpoint exists. No immutability lock after publish (
enterMarksstill works) — by design for now, flagged in 14_QA_Checklist.md §2.
Journey C — Student: view report card
- Open results list:
GET /api/v1/results/student/:studentId(result.controller.ts:16-20) → list ofexamination_resultsrows withmarksObtained,grade,remarks,publishedAt(examination-result.schema.ts:15-25). - Open report card for an exam:
GET /api/v1/results/report-card/:studentId/:examId(result.controller.ts:32-37). - Read: per-subject
marksObtained/maximumMarks/grade/remarks; totals;percentage(2 decimals);overallGrade(A+…F bands,result.service.ts:130-138);generatedAt(result.service.ts:96). - Missing entries render as 0 with grade F (
result.service.ts:70) — client should visually distinguish "0 entered" from "not entered" (data limitation: cannot distinguish; see 14_QA_Checklist.md §3). - ⚠️
subjectNameis the raw subject ID (result.service.ts:75) — client maps to display names. - Empty exam (no subjects) → 404
No subjects found for this examination.(result.service.ts:51-52) → client shows empty state.
Journey D — Admin: create exam + subjects (precondition to A)
POST /api/v1/examinations(examination.controller.ts:27-29) — always startsstatus: 'draft'(examination.service.ts:48); body perCreateExaminationDto(examination.dto.ts:4-26):academicYearId,name,type(midterm|final|unit_test|quarterly|other),startDate,endDate.POST /api/v1/examinations/:id/subjects(examination.controller.ts:45-50) withCreateExaminationSubjectDto(examination-subject.dto.ts:11-45):subjectId,classId,date,startTime,endTime,maximumMarks(≥1),passingMarks(≥1).- Marks entry enabled (Journey A).
Journey E — (planned) bulk marks import
POST /api/v1/examinations/:id/marks-import— spreadsheet import with validation + rollback (IMPLEMENTATION_PLAN.md:219).(planned)GET /api/v1/examinations/:id/schedule(hall tickets/seatingIMPLEMENTATION_PLAN.md:216-218)(planned).
Flow summary
flowchart LR
A[Exam created draft] --> B[Subjects added]
B --> C[Marks entered per student]
C -->|POST marks| D{valid <= maximumMarks?}
D -->|no| E[404 → client shows 'exceeds max']
D -->|yes| F[Upsert examination_results row]
F --> G[Coordinator: POST publish]
G --> H[stamp publishedAt + exam status=published]
H --> I[ExamResultsPublished event]
I --> J[in-app queue → results-published job]
C --> K[Report card GET]
K --> L[Totals + % + overallGrade computed]