12 — API Mapping (Results Module)
- E1 — List results for a student (S3)
- E2 — List results for an exam-subject (S1, S5)
- E3 — Enter / update marks (S1)
- E4 — Report card (S4)
- E5 — Get examination (S3 grouping, S5 header)
- E6 — List subjects of an exam (S1 header, S2, S5)
- E7 — Publish exam results (S5)
- E8 — List examinations (S1/S5 entry navigation) — paginated
- E9 — (planned) bulk marks import
- E10 — (proposed) analytics
- E11 — (planned) rank
- Event side-effect map
Exact endpoints per screen. Wire contract per 00-shared/07_API_Conventions.md: base
/api/v1, Bearer JWT, success{success:true, message:"OK", data, meta?, timestamp, requestId}, error{success:false, message, error:{code, details?}, timestamp, requestId}; codesVALIDATION_ERROR(400),UNAUTHENTICATED(401),PERMISSION_DENIED(403),RESOURCE_NOT_FOUND(404),DUPLICATE_RESOURCE(409),BUSINESS_RULE_VIOLATION(422),RATE_LIMITED(429),INTERNAL_SERVER_ERROR(5xx). Paginationpage/limit(1-100, default 20)/sort(-desc)/q. Sources:result.controller.ts,examination.controller.ts,examination.service.ts,result.service.ts. Note: no endpoint is paginated in this module — all results routes return bare arrays; pagination exists only on the exams list.
E1 — List results for a student (S3)
| Endpoint | GET /api/v1/results/student/:studentId (result.controller.ts:16-20) |
| Guard | JwtAuthGuard only (:10) — no ownership/visibility check; RBAC (planned) |
| Response | 200 envelope, data = flat ExaminationResult[] (examination-result.repository.ts:26-28) — no meta |
| Errors | 400 (malformed id); 429 RATE_LIMITED; 5xx; cross-tenant/deleted → [] (scoped repo, not 404) |
| Client | S3 rows; group by examinationSubjectId → E5 |
E2 — List results for an exam-subject (S1, S5)
| Endpoint | GET /api/v1/results/exam-subject/:examSubjectId (result.controller.ts:21-25) |
| Response | 200 envelope, data = ExaminationResult[] (examination-result.repository.ts:20-24) |
| Errors | 400; 404 via repo scoping (subject not in tenant → []); 429; 5xx |
| Client | S1 table rows; S5 per-subject aggregation |
| Notes | Includes rows with publishedAt — no filter param exists ((planned)) |
E3 — Enter / update marks (S1)
| Endpoint | POST /api/v1/results/exam-subject/:examSubjectId/marks (result.controller.ts:26-31) |
| Guard | JwtAuthGuard; intended result.compute (planned) |
| Body | EnterMarksDto (examination-subject.dto.ts:47-65): studentId (MongoId, req), marksObtained (Number, Min 0, req), grade? (String), remarks? (String) |
| Behaviour | idempotent upsert on (studentId, examSubjectId) (examination.service.ts:147-175); unknown subject → 404 (:143-144); marksObtained > maximumMarks → 404 RESOURCE_NOT_FOUND (:145-146, quirk — see 14_QA_Checklist.md §5) |
| Response | 2xx envelope, data = saved ExaminationResult |
| Errors | 400 VALIDATION_ERROR (bad id, negative marks); 404 (subject missing / over-max); 429; 5xx (incl. dup-key race → 500, examination-result.schema.ts:31-33) |
| Side effects | MarksEntered event → no queue route in event-queue-map.ts (audit only, examination.service.ts:163-173) |
| Client | S1 per-row save; replay-safe offline (idempotent) |
E4 — Report card (S4)
| Endpoint | GET /api/v1/results/report-card/:studentId/:examId (result.controller.ts:32-37) |
| Response | 200 envelope, data = ReportCard (result.service.ts:8-26): studentId, examinationId, subjects[] (subjectId, subjectName=subject ID string :75, marksObtained 0-filled :70, maximumMarks, grade?, remarks?), totalMarksObtained, totalMaximumMarks, percentage (2 dp :83-86), overallGrade (computeGrade bands :130-138), generatedAt |
| Errors | 404 No subjects found for this examination. (:51-52); 400 malformed ids; 429; 5xx |
| Client | S4; cache key (studentId, examId); subjectName → local display-name map |
| Notes | publishedAt not checked — report card works pre-publish ((planned) visibility) |
E5 — Get examination (S3 grouping, S5 header)
| Endpoint | GET /api/v1/examinations/:id (examination.controller.ts:33-35) |
| Response | 200 envelope, data = Examination (examination.schema.ts:8-36): academicYearId, name, type (`midterm |
| Errors | 404 Examination not found. (examination.service.ts:63); 429; 5xx |
| Client | S3 group headers (name + status chip); orphan-group drop when 404 |
E6 — List subjects of an exam (S1 header, S2, S5)
| Endpoint | GET /api/v1/examinations/:id/subjects (examination.controller.ts:51-53) |
| Response | 200 envelope, data = ExaminationSubject[] (examination-subject.schema.ts:8-31): examinationId, subjectId, classId, date, startTime, endTime, maximumMarks, passingMarks |
| Errors | 400; 429; 5xx (no subject → []) |
| Client | S1 header context; S2 single-row pick; S5 aggregation input |
E7 — Publish exam results (S5)
| Endpoint | POST /api/v1/examinations/:id/publish (examination.controller.ts:54-56) |
| Behaviour | stamps publishedAt on all results of all subjects (examination.service.ts:203-207, examination-result.repository.ts:42-51), sets exam status: 'published' (:209-211), emits ExamResultsPublished (:212-219) → in-app queue / results-published job (event-queue-map.ts:27) |
| Response | 2xx envelope, data null; exam with zero subjects → still publishes (subjectIds.length guard, :206) |
| Errors | 404 (exam missing via subjectRepo/examRepo); 429; 5xx |
| Notes | No unpublish endpoint; repeat publish is idempotent (re-stamps publishedAt) |
| Blueprint | Results.md:37 names event ResultPublished — code emits ExamResultsPublished (naming mismatch, flag for backend alignment) |
E8 — List examinations (S1/S5 entry navigation) — paginated
| Endpoint | GET /api/v1/examinations?page&limit&sort&q (examination.controller.ts:30-32) |
| Params | page ≥1 default 1; limit 1–100 default 20; sort (-field desc); q (pagination-query.dto.ts:5-30) |
| Response | 200 envelope, data + meta {page,limit,totalItems,totalPages,hasNext,hasPrevious} (pagination-query.dto.ts:32-39) |
| Client | exam picker for S1/S5; infinite scroll while hasNext |
E9 — (planned) bulk marks import
| Endpoint | POST /api/v1/examinations/:id/marks-import — spreadsheet import, validation + rollback (IMPLEMENTATION_PLAN.md:219) (planned) |
| Client | S1 "Import" action; async job progress (planned) |
E10 — (proposed) analytics
| Endpoint | GET /api/v1/results/analytics (blueprint Results.md:28) — not implemented (proposed) |
| Client | S5 today = client-side aggregation of E2 + E6 |
E11 — (planned) rank
| Fact | Blueprint Results.md:54 "Rank computed per class per exam"; no code exists (planned) |
| Client | S4 rank slot reserved; hidden until shipped |
Event side-effect map
| Event | Queue / job | Source |
|---|---|---|
MarksEntered | none routed (audit-write (planned)) | examination.service.ts:163-173 |
ExaminationResultCreated | none routed (emitted by ResultService.create, result.service.ts:114-126 — path unused by controller) | result.service.ts:114-126 |
ExamResultsPublished | in-app / results-published | event-queue-map.ts:27 |