Assessments — Quizzes, Assignments & Question Bank¶
This document describes the assessment domain of the MAS / Mr. Mentor backend: course quizzes (manual + AI-generated), student quiz submissions and grading, assignments with a nightly due-date reminder worker, the shared Question Bank (categories + questions), quiz templates (used by the recruitment product), candidate quizzes (recruitment), and externally-synced eval tests. It spans three products that all happen to be "things a learner or candidate is graded on": the MAS LMS (course quizzes & assignments), Mr. Hire (candidate quizzes built from the question bank / quiz templates), and the eval test ingestion pipeline (Mr. Test scores uploaded via Excel).
Status: documented from source on this branch.
Overview¶
The assessment domain answers one question across multiple products: "how does a learner or candidate get scored?" It is deliberately not a single subsystem — it is a family of related entities and services that share concepts (questions, points, passing score, percentage, status lifecycle) but live in different flows:
| Sub-domain | Who authors | Who takes | Where graded | Primary entities |
|---|---|---|---|---|
| LMS course quizzes | ADMIN / SUPERADMIN | Enrolled students | StudentQuizService (in-process) |
Quiz, QuizSubmission |
| LMS assignments | ADMIN / SUPERADMIN | Enrolled students (submitted off-platform) | n/a (instructions only) | Assignment |
| Question Bank | ADMIN (and seed scripts) | n/a — a reusable pool | n/a | QuestionBankCategory, QuestionBankQuestion |
| Quiz templates | HR / recruiter (Mr. Hire) | n/a — reusable quiz blueprint | n/a | QuizTemplate |
| Candidate quizzes | HR / recruiter | Job candidates (public token link) | CandidateQuizController (in-process) |
CandidateQuizResponse |
| Eval tests | Admin Excel upload | external Mr. Test platform | external; ingested as scores | EvalTest |
Personas / roles
- ADMIN / SUPERADMIN — author and publish course quizzes and assignments (adminMiddleware); manage the question bank.
- EXPERT / mentor — not a primary actor here (mentorship lives in its own domain).
- Student (USER, enrolled) — lists, takes, and reviews course quizzes; gated by isEnrolledMiddleware.
- HR / recruiter — curates quizzes from a job description, saves reusable templates, emails candidate quiz links (hrRoleMiddleware).
- Candidate — takes a quiz via a tokenised public URL (no login).
Where it sits in the suite. Course quizzes/assignments hang off the LMS Module (see education-lms-courses). Quiz submission grants XP and re-evaluates badges (see student-engagement-gamification). AI quiz generation/curation is delegated to mr-hire-backend over HTTP (see ai-platform-llm-gateway and the Mr. Hire docs). Candidate quizzes feed the recruitment screening pipeline (see mr-hire-jobs-and-applications and mr-hire-ai-resume-screening).
Key concepts & entities¶
Glossary
- Quiz — an LMS assessment attached (optionally) to a Module. Questions are stored inline as JSON; there is no separate question-row table for course quizzes.
- Categorized question format — AI-generated quizzes return { coding: [], theoretical: [], case_based: [] }. The student service flattens this into a single question array on read/grade.
- QuizSubmission — one student attempt at a quiz, with computed score / percentage / pass flag and an incrementing attemptNumber. Unlimited attempts (no max enforced).
- Assignment — an LMS deliverable with a due date and urgency. The platform stores instructions/attachments only; there is no assignment-submission entity — submission happens off-platform, the backend only sends due reminders.
- Question Bank — a reusable, de-duplicated pool of questions organised into categories. Backs admin authoring and (in Mr. Hire) AI quiz curation.
- Quiz Template — a reusable, versioned quiz blueprint owned by an HR user, keyed by (roleSlug, version, createdBy). Created manual or ai.
- Candidate Quiz — a one-off quiz snapshot emailed to a job candidate; the encrypted token in the URL is the auth.
- Eval Test — a score row ingested from the external Mr. Test platform via Excel upload; not authored or taken inside this backend.
Main TypeORM entities (file paths)
| Entity | File | Table | Notes |
|---|---|---|---|
Quiz |
src/entities/Quiz.ts |
quizzes |
status enum QuizStatus; questions JSON; nullable moduleId |
QuizSubmission |
src/entities/QuizSubmission.ts |
quiz_submissions |
indexed on (userId, quizId); answers JSON |
QuizTemplate |
src/entities/QuizTemplate.entity.ts |
quiz_templates |
unique (roleSlug, version, createdBy); creationMode manual/ai |
QuestionBankCategory |
src/entities/QuestionBankCategory.ts |
question_bank_categories |
unique name; OneToMany questions |
QuestionBankQuestion |
src/entities/QuestionBankQuestion.ts |
question_bank_questions |
enums QuestionType, QuestionDifficulty; isSeeded, createdBy ownership |
Assignment |
src/entities/Assignment.ts |
assignments |
enums AssignmentStatus, Urgency |
CandidateQuizResponse |
src/entities/CandidateQuizResponse.ts |
candidate_quiz_responses |
unique token; questions/answers JSONB; expiresAt |
EvalTest |
src/entities/EvalTest.ts |
eval_tests |
externally-sourced scores; nullable studentId |
Architecture¶
flowchart TD
subgraph Clients["Clients"]
AdminUI["Admin dashboard (mr-mentor-frontend)"]
StudentUI["Student portal (mas-website-live)"]
HRUI["Mr. Hire UI"]
CandidateBrowser["Candidate browser (public link)"]
end
subgraph Routes["Express routes"]
AdminR["/api/admin/mas/quizzes and /mas/assignments and /mas/quiz/generate"]
QBR["/api/question-bank/*"]
StudentR["/api/student/quizzes/*"]
HireR["/api/mr-hire/quiz/* (curate, templates, send-email)"]
CandR["/api/candidate-quiz/*"]
end
subgraph Controllers["Controllers"]
AdminMasC["adminMas.controller"]
QBC["questionBank.controller"]
SQC["studentQuiz.controller"]
HireC["mrHire.controller"]
CandC["candidateQuiz.controller"]
end
subgraph Services["Services"]
QuizSvc["QuizService"]
AsgSvc["AssignmentService"]
GenSvc["QuizGenerationService"]
QBSvc["QuestionBankService"]
SQSvc["StudentQuizService"]
TplSvc["QuizTemplateService"]
ProgSvc["StudentProgressService / BadgeService"]
end
subgraph DB["PostgreSQL via TypeORM"]
QuizT["quizzes"]
SubT["quiz_submissions"]
AsgT["assignments"]
CatT["question_bank_categories"]
QT["question_bank_questions"]
TplT["quiz_templates"]
CandT["candidate_quiz_responses"]
EvalT["eval_tests"]
end
subgraph External["External and async"]
MrHire["mr-hire-backend (AI quiz gen and curate)"]
Queue["BullMQ assignmentReminderQueue"]
Notif["NotificationService"]
end
AdminUI --> AdminR --> AdminMasC
AdminUI --> QBR --> QBC
StudentUI --> StudentR --> SQC
HRUI --> HireR --> HireC
CandidateBrowser --> CandR --> CandC
AdminMasC --> QuizSvc --> QuizT
AdminMasC --> AsgSvc --> AsgT
AdminMasC --> GenSvc --> MrHire
QBC --> QBSvc --> CatT
QBSvc --> QT
SQC --> SQSvc --> QuizT
SQSvc --> SubT
SQC --> ProgSvc
HireC --> TplSvc --> TplT
HireC --> MrHire
CandC --> CandT
Queue --> AsgT
Queue --> Notif
Data model¶
erDiagram
MODULE ||--o{ QUIZ : "has"
MODULE ||--o{ ASSIGNMENT : "has"
QUIZ ||--o{ QUIZ_SUBMISSION : "graded as"
USER ||--o{ QUIZ_SUBMISSION : "submits"
QUESTION_BANK_CATEGORY ||--o{ QUESTION_BANK_QUESTION : "groups"
USER ||--o{ EVAL_TEST : "scored in"
QUIZ {
uuid id PK
uuid moduleId FK
string title
string status
timestamp dueDate
int points
int duration
int passingScore
json questions
}
QUIZ_SUBMISSION {
uuid id PK
uuid quizId FK
uuid userId FK
json answers
int score
int totalPoints
decimal percentage
boolean passed
int timeSpent
int attemptNumber
timestamp submittedAt
}
ASSIGNMENT {
uuid id PK
uuid moduleId FK
string title
string status
string urgency
timestamp dueDate
int points
json attachments
}
QUESTION_BANK_CATEGORY {
uuid id PK
string name UK
string icon
string description
}
QUESTION_BANK_QUESTION {
uuid id PK
uuid categoryId FK
string type
string difficulty
text question
int points
json options
text correctAnswer
jsonb testCases
json tags
boolean isSeeded
string createdBy
}
QUIZ_TEMPLATE {
uuid id PK
string name
string roleSlug
int version
int timeLimit
int passingScore
int totalPoints
jsonb questions
string creationMode
uuid createdBy
}
CANDIDATE_QUIZ_RESPONSE {
uuid id PK
string token UK
uuid applicationId
string candidateEmail
jsonb questions
jsonb answers
int score
decimal percentage
boolean submitted
timestamp expiresAt
}
EVAL_TEST {
uuid id PK
uuid studentId FK
string testName
decimal score
decimal totalMarks
int percentage
json results
timestamp completedAt
}
Notable enums / status fields
- QuizStatus (src/entities/Quiz.ts): draft | published | archived.
- AssignmentStatus (src/entities/Assignment.ts): draft | published | archived.
- Urgency (src/entities/Assignment.ts): low | medium | high | urgent.
- QuestionType (src/entities/QuestionBankQuestion.ts): multiple-choice | coding | text.
- QuestionDifficulty: easy | medium | hard.
- QuizTemplate.creationMode: manual | ai.
- Note the two "question type" vocabularies. The Question Bank uses multiple-choice / coding / text. The student-facing quiz grader (StudentQuizService) uses MCQ / CODING / CASE_BASED (matching the AI generator output). They are distinct enums in distinct flows.
API surface¶
All paths are derived from the actual route files plus their mount prefix in src/routes/index.ts.
LMS course quizzes & assignments — mounted at /api/admin (src/routes/admin.routes.ts)¶
| Method | Path | Auth/role | Purpose |
|---|---|---|---|
| GET | /api/admin/mas/quizzes |
auth + admin | List quizzes (QuizService.getAllQuizzes, filters: moduleId/status/search) |
| GET | /api/admin/mas/quizzes/:id |
auth + admin | Get one quiz |
| POST | /api/admin/mas/quizzes |
auth + admin | Create quiz |
| PUT | /api/admin/mas/quizzes/:id |
auth + admin | Update quiz |
| DELETE | /api/admin/mas/quizzes/:id |
auth + admin | Delete quiz |
| POST | /api/admin/mas/quizzes/:id/duplicate |
auth + admin | Duplicate quiz (resets to DRAFT) |
| POST | /api/admin/mas/quizzes/:id/questions |
auth + admin | Append a question to the inline JSON array |
| PUT | /api/admin/mas/quizzes/:id/questions/:questionIndex |
auth + admin | Replace a question by index |
| DELETE | /api/admin/mas/quizzes/:id/questions/:questionIndex |
auth + admin | Remove a question by index |
| POST | /api/admin/mas/quiz/generate |
auth + admin | AI-generate quiz from JD text (returns categorized quiz, not persisted) |
| POST | /api/admin/mas/quiz/generate/:courseId/:moduleId |
auth + admin | AI-generate for a course/module (returns generated quiz) |
| GET | /api/admin/mas/assignments |
auth + admin | List assignments (filters: moduleId/status/urgency/search) |
| GET | /api/admin/mas/assignments/:id |
auth + admin | Get one assignment |
| POST | /api/admin/mas/assignments |
auth + admin | Create assignment |
| PUT | /api/admin/mas/assignments/:id |
auth + admin | Update assignment |
| DELETE | /api/admin/mas/assignments/:id |
auth + admin | Delete assignment |
| POST | /api/admin/mas/assignments/:id/duplicate |
auth + admin | Duplicate assignment (resets to DRAFT) |
Question Bank — mounted at /api/question-bank (src/routes/questionBank.routes.ts)¶
| Method | Path | Auth/role | Purpose |
|---|---|---|---|
| GET | /api/question-bank/categories |
none in route file* | List categories with question counts |
| POST | /api/question-bank/categories |
none* | Create category (idempotent on name) |
| DELETE | /api/question-bank/categories/:id |
none* | Delete category (cascades questions) |
| GET | /api/question-bank/questions?category=&page=&pageSize=&search=&type=&difficulty= |
none* | Paged questions in a category |
| POST | /api/question-bank/questions |
none* | Create question (dedup-checked) |
| PUT | /api/question-bank/questions/:id |
none* | Update question (ownership/seed guards) |
| DELETE | /api/question-bank/questions/:id |
none* | Delete question (ownership/seed guards) |
| POST | /api/question-bank/import |
none* | Bulk import questions into a category |
| GET | /api/question-bank/export?category= |
none* | Export a category as import payload |
| GET | /api/question-bank/stats |
none* | Totals by type / difficulty |
* The controller reads req.userId for ownership tagging when a JWT is present. See the internal hardening notes for route-level auth details.
Student course quizzes — mounted at /api/student (src/routes/student.routes.ts)¶
| Method | Path | Auth/role | Purpose |
|---|---|---|---|
| GET | /api/student/quizzes |
auth + enrolled | List available (published) quizzes with per-quiz status |
| GET | /api/student/quizzes/stats |
auth + enrolled | Aggregate completion / pass stats |
| GET | /api/student/quizzes/history |
auth + enrolled | Recent submission history |
| GET | /api/student/quizzes/:id |
auth + enrolled | Get quiz to take (correct answers stripped) |
| POST | /api/student/quizzes/:id/submit |
auth + enrolled | Submit answers, grade, save submission, grant XP |
| GET | /api/student/quizzes/:id/result |
auth + enrolled | Get a graded result (with correct answers + explanations) |
Mr. Hire quiz curation & templates — mounted at /api (src/routes/mrHire.routes.ts)¶
| Method | Path | Auth/role | Purpose |
|---|---|---|---|
| POST | /api/mr-hire/quiz/curate |
auth + HR | Curate a quiz for an application via mr-hire-backend |
| POST | /api/mr-hire/quiz/curate-for-template |
auth + HR | Curate questions for a reusable template |
| POST | /api/mr-hire/quiz/send-email |
auth + HR | Email a candidate quiz link |
| GET | /api/mr-hire/quiz/templates |
auth + HR | List templates (scoped to the logged-in user) |
| GET | /api/mr-hire/quiz/templates/roles |
auth + HR | Distinct role list for filtering |
| POST | /api/mr-hire/quiz/templates |
auth + HR | Create template (auto-versioned) |
| PUT | /api/mr-hire/quiz/templates/:id |
auth + HR | Update template |
| DELETE | /api/mr-hire/quiz/templates/:id |
auth + HR | Delete template |
Candidate quizzes — mounted at /api/candidate-quiz (src/routes/candidateQuiz.routes.ts)¶
| Method | Path | Auth/role | Purpose |
|---|---|---|---|
| GET | /api/candidate-quiz/take/:token |
public (token is auth) | Fetch quiz for the candidate (no answers) |
| POST | /api/candidate-quiz/take/:token/submit |
public (token is auth) | Submit + auto-grade, advance screening |
| GET | /api/candidate-quiz/admin/quiz-results/:applicationId |
auth | HR view of a candidate's results |
User journeys¶
Journey 1 — Admin authors a quiz manually¶
An admin builds a quiz against a module, adds questions one at a time, then flips it to published so enrolled students can see it.
sequenceDiagram
participant Admin as Admin UI
participant API as Express /api/admin
participant Ctrl as adminMas.controller
participant Svc as QuizService
participant DB as PostgreSQL
Admin->>API: POST /mas/quizzes with title and moduleId and status draft
API->>Ctrl: createQuiz after auth and admin checks
Ctrl->>Svc: createQuiz quizData
Svc->>DB: validate module exists then insert quiz
Svc->>DB: recount published quizzes and update module totalQuizzes
DB-->>Svc: saved quiz
Svc-->>Ctrl: quiz
Ctrl-->>Admin: 201 created quiz
Admin->>API: POST /mas/quizzes/:id/questions with question payload
API->>Ctrl: addQuestionToQuiz
Ctrl->>Svc: addQuestionToQuiz pushes into questions JSON
Svc->>DB: save quiz
DB-->>Admin: updated quiz
Admin->>API: PUT /mas/quizzes/:id with status published
API->>Ctrl: updateQuiz
Ctrl->>Svc: updateQuiz then recount published quizzes
Svc->>DB: save and update module count
DB-->>Admin: published quiz
Journey 2 — Admin AI-generates a quiz from a job description¶
The admin asks the backend to draft questions from JD text. The work is delegated to mr-hire-backend; if that service is unreachable the generator returns a baked-in mock so the UI never breaks. The result is returned to the client and is not auto-persisted — the admin reviews and then saves it as a normal quiz.
sequenceDiagram
participant Admin as Admin UI
participant API as Express /api/admin
participant Ctrl as adminMas.controller
participant Gen as QuizGenerationService
participant Hire as mr-hire-backend
Admin->>API: POST /mas/quiz/generate with jdText and difficulty and count
API->>Ctrl: generateQuiz after auth and admin
Ctrl->>Gen: generateQuiz options
Gen->>Hire: POST /api/v1/quiz/generate with jd_text and types
alt mr-hire reachable
Hire-->>Gen: categorized quiz coding theoretical case_based
Gen-->>Ctrl: generated quiz
else mr-hire down or error
Note over Gen: catch then fall back to generateMockQuiz
Gen-->>Ctrl: mock quiz
end
Ctrl-->>Admin: 200 generated quiz for review
Note over Admin: admin edits then saves via POST /mas/quizzes
Journey 3 — Student takes a quiz and it gets graded¶
The headline LMS journey. A student lists quizzes, opens one (answers stripped), submits, and the service grades synchronously, persists a QuizSubmission, logs activity, and fires a non-blocking XP + badge hook.
sequenceDiagram
participant Stu as Student portal
participant API as Express /api/student
participant Ctrl as studentQuiz.controller
participant Svc as StudentQuizService
participant DB as PostgreSQL
participant Prog as StudentProgressService and BadgeService
Stu->>API: GET /quizzes after auth and enrolled
API->>Ctrl: getAvailableQuizzes
Ctrl->>Svc: getAvailableQuizzes userId
Svc->>DB: find enrollments then modules then published quizzes plus submissions
DB-->>Stu: quiz list with status and bestScore
Stu->>API: GET /quizzes/:id
API->>Ctrl: getQuizForStudent
Ctrl->>Svc: getQuizForStudent strips correct answers and flattens categorized format
Svc-->>Stu: quiz without answers
Stu->>API: POST /quizzes/:id/submit with answers and timeSpent
API->>Ctrl: submitQuiz
Ctrl->>Svc: submitQuiz grades per question type
Note over Svc: MCQ compared exactly. CODING and CASE_BASED auto-pass if answer is substantial
Svc->>DB: count prior attempts then insert QuizSubmission with attemptNumber
Svc->>DB: insert StudentActivity QUIZ_COMPLETED
Svc-->>Ctrl: QuizResult with score and percentage and passed
Ctrl-->>Stu: 200 result returned immediately
Note over Ctrl,Prog: fire and forget after response
Ctrl->>Prog: grantXp assignment_submitted keyed on submissionId
Ctrl->>Prog: evaluateForUser badges
Journey 4 — Student reviews a graded result¶
sequenceDiagram
participant Stu as Student portal
participant API as Express /api/student
participant Ctrl as studentQuiz.controller
participant Svc as StudentQuizService
participant DB as PostgreSQL
Stu->>API: GET /quizzes/:id/result with optional submissionId
API->>Ctrl: getQuizResult
Ctrl->>Svc: getQuizResult
alt submissionId provided
Svc->>DB: find that submission for user and quiz
else no submissionId
Svc->>DB: find latest submission ordered by submittedAt desc
end
Svc->>Svc: recompute per question correctness and attach correctAnswers and explanations
Svc-->>Ctrl: full result with answers status
Ctrl-->>Stu: 200 result with explanations
Journey 5 — Assignment published, nightly reminder, student notified¶
There is no submission endpoint; the platform's job is to remind enrolled students before the due date. A repeatable BullMQ job runs at 19:00 IST daily.
sequenceDiagram
participant Admin as Admin UI
participant Asg as AssignmentService
participant DB as PostgreSQL
participant Cron as BullMQ assignmentReminderQueue
participant Worker as assignmentReminder.worker
participant Notif as NotificationService
Admin->>Asg: POST /mas/assignments with dueDate and status published
Asg->>DB: insert assignment then recount published and update module totalAssignments
Note over Cron: scheduled repeat 0 19 star star star tz Asia Kolkata
Cron->>Worker: remindAssignmentsDue fires nightly
Worker->>DB: find published assignments with dueDate within next 24h
loop each due assignment
Worker->>DB: resolve module then courseId then distinct enrolled userIds
Worker->>Notif: fromTemplate userIds assignment_due_24h with title and id
Note over Notif: subject to the global 3 per day notification cap
end
Worker-->>Cron: logs sent and skipped counts
Journey 6 — Question Bank CRUD with de-duplication¶
The question bank is a shared pool. Creating or importing a question first computes a SHA-256 fingerprint of the normalised text plus sorted options, so the same question with reordered options is detected as a duplicate and skipped.
sequenceDiagram
participant Admin as Admin UI
participant API as Express /api/question-bank
participant Ctrl as questionBank.controller
participant Svc as QuestionBankService
participant DB as PostgreSQL
Admin->>API: POST /questions with categoryName and type and difficulty and question
API->>Ctrl: createQuestion
Ctrl->>Svc: getCategoryByName or createCategory
Ctrl->>Svc: createQuestion data
Svc->>Svc: buildFingerprint sha256 of lowercased text plus sorted options
Svc->>DB: load category questions and compare fingerprints
alt duplicate found
Svc-->>Ctrl: existing question with isDuplicate true
Ctrl-->>Admin: 200 already exists skipped
else new question
Svc->>DB: insert question with createdBy and isSeeded false
Svc-->>Ctrl: saved question
Ctrl-->>Admin: 201 created
end
Journey 7 — HR curates a quiz from the question bank and emails a candidate¶
This is the recruitment path that reuses the assessment building blocks. HR curates questions (via mr-hire-backend), optionally saves a reusable QuizTemplate, then sends a tokenised quiz link. The candidate takes it without logging in, and the auto-graded score advances the screening pipeline.
sequenceDiagram
participant HR as Mr. Hire UI
participant API as Express /api
participant Ctrl as mrHire.controller
participant Hire as mr-hire-backend
participant Tpl as QuizTemplateService
participant DB as PostgreSQL
participant Cand as Candidate browser
participant CC as candidateQuiz.controller
HR->>API: POST /mr-hire/quiz/curate with jdText and resumeText and resumeScore
API->>Ctrl: curateQuiz after auth and HR
Ctrl->>Hire: POST /api/v1/quiz/curate
Hire-->>Ctrl: curated quiz questions
Ctrl->>DB: save quizQuestions onto ScreeningResult for applicationId
Ctrl-->>HR: curated quiz
opt save reusable template
HR->>API: POST /mr-hire/quiz/templates
API->>Ctrl: createQuizTemplate
Ctrl->>Tpl: create auto increments version per roleSlug and createdBy
Tpl->>DB: insert quiz_template
end
HR->>API: POST /mr-hire/quiz/send-email with applicationId and candidateEmail
API->>Ctrl: sendQuizEmail reads ScreeningResult quizQuestions
Ctrl->>DB: create CandidateQuizResponse snapshot with token and expiresAt
Ctrl-->>HR: email queued
Cand->>API: GET /candidate-quiz/take/:token
API->>CC: getQuizByToken
CC->>DB: decrypt token then load response
alt expired or already submitted
CC-->>Cand: 410 expired or 200 already submitted with score
else valid
CC-->>Cand: quiz questions without answers
end
Cand->>API: POST /candidate-quiz/take/:token/submit with answers
API->>CC: submitQuiz auto grades MCQ and coding by test cases
CC->>DB: save score and percentage and submitted true
CC->>DB: update ScreeningResult quizScore and advance application status
CC-->>Cand: 200 submitted
Journey 8 — Eval test score ingestion (external Mr. Test)¶
Eval tests are not authored or taken in this backend. Scores are produced on the external Mr. Test platform and ingested by an admin Excel upload into the eval_tests table, then surfaced on the student dashboard.
sequenceDiagram
participant Admin as Admin UI
participant Ctrl as UploadScoreController
participant DB as PostgreSQL
participant Perf as PerformanceMetricService
participant Stu as Student portal
Admin->>Ctrl: upload Excel of test scores
loop each row
Ctrl->>DB: find existing EvalTest by student and test
alt found
Ctrl->>DB: update EvalTest score and percentage
Ctrl->>Perf: updateEvalTestScore studentId
else student not in eval_tests
Note over Ctrl: skip row
end
end
Ctrl->>DB: recompute averageScore across a student tests
Stu->>DB: dashboard reads recentEvaluations from eval_tests
Background jobs & async¶
| Mechanism | Where | Trigger / schedule | Purpose |
|---|---|---|---|
assignmentReminderQueue (BullMQ) |
src/services/QueueService.ts (scheduleAssignmentReminders) |
Repeatable cron 0 19 * * *, tz Asia/Kolkata; registered at startup (src/index.ts) |
Enqueue the nightly remindAssignmentsDue job |
assignmentReminder.worker |
src/workers/assignmentReminder.worker.ts |
Consumes remindAssignmentsDue |
Find assignments due within 24h, resolve enrolled students per course, send assignment_due_24h notifications |
| XP grant + badge re-eval | src/controllers/studentQuiz.controller.ts (submitQuiz) |
Fire-and-forget after quiz submission response | grantXp('assignment_submitted', submissionId) (idempotent on submissionId) then BadgeService.evaluateForUser |
| Student activity log | StudentQuizService.logQuizActivity |
On every quiz submission | Insert StudentActivity of type QUIZ_COMPLETED |
| Candidate-quiz screening advance | candidateQuiz.controller.submitQuiz |
On candidate submit | Update ScreeningResult.quizScore, advance application status, optionally schedule the AI call (Mr. Hire) |
There are no Socket.IO events and no inbound webhooks in this domain. The worker uses the shared Redis connection (REDIS_HOST / REDIS_PORT). removeOnComplete: 10 / removeOnFail: 10 keep the queue trimmed.
External integrations¶
| Integration | Used by | Env var | Failure / fallback |
|---|---|---|---|
| mr-hire-backend AI quiz generation | QuizGenerationService.generateQuiz → POST /api/v1/quiz/generate |
MR_HIRE_BACKEND_URL (default http://localhost:8001) |
On any error/non-OK response, returns a hard-coded generateMockQuiz so the admin UI keeps working |
| mr-hire-backend AI quiz curation | mrHire.controller.curateQuiz → POST /api/v1/quiz/curate |
MR_HIRE_BACKEND_URL |
Propagates upstream status/body to the client (no mock fallback here) |
| NotificationService templates | assignmentReminder.worker |
n/a (in-process) | Per-user 3/day cap applies; failures logged per-assignment and the loop continues |
| Judge0 proxy (code execution) | mounted at /api (src/routes/judge0.routes.ts), called from public quiz pages |
(Judge0 config) | Used to run coding answers; out of scope here but referenced by coding questions |
Feature flags / notable env: none specific to this domain beyond MR_HIRE_BACKEND_URL. ENABLE_SEEDING governs whether the question bank / course seed scripts run at startup.
Status lifecycles¶
Quiz / Assignment status¶
Both Quiz and Assignment share the same three-state lifecycle. Only published rows are visible to students; the module's totalQuizzes / totalAssignments counters are recomputed from published rows on every status change. Duplicating either entity always resets the copy to draft.
stateDiagram-v2
[*] --> draft: created
draft --> published: admin publishes
published --> archived: admin archives
published --> draft: unpublish
archived --> published: re-publish
draft --> [*]: deleted
published --> [*]: deleted
archived --> [*]: deleted
QuizSubmission lifecycle¶
A submission is created already graded — there is no in-progress persisted state (the controller explicitly reports inProgress: 0). Each submit increments attemptNumber; passed is derived once and frozen.
stateDiagram-v2
[*] --> graded: submitQuiz computes score
graded --> graded: re-attempt creates a new submission attemptNumber plus 1
note right of graded
passed equals score greater or equal passingScore
best attempt drives quiz list status completed
end note
CandidateQuizResponse lifecycle¶
stateDiagram-v2
[*] --> issued: HR sends link token created with expiresAt
issued --> submitted: candidate submits before expiry
issued --> expired: now greater than expiresAt
submitted --> [*]
expired --> reissued: HR sends a newer link
reissued --> submitted
Edge cases, limits & gotchas¶
- Hardening note (internal). A security/hardening observation for this area is tracked in the team's private notes (
internal/security-and-hardening-notes.md) and is intentionally not published on this site. - Course quizzes store questions inline as JSON, not as rows. Editing questions is index-based (
/questions/:questionIndex), so concurrent edits can clobber each other and indices shift on delete. - Two question-shape vocabularies. The student grader (
StudentQuizService) expectsMCQ/CODING/CASE_BASEDwith ananswer: string[](letter-based for MCQ). The Question Bank usesmultiple-choice/coding/textwithcorrectAnswer: stringandtestCases. The grader also flattens the AI generator's categorized{ coding, theoretical, case_based }shape on read. - Coding / case-based answers are not really graded in
StudentQuizService.submitQuiz: any answer longer than 10 chars (coding) or 20 chars (case-based) is marked correct, withTODOcomments to integrate Mr. Hire AI evaluation. Only MCQ is exactly compared. (Candidate quizzes, by contrast, score coding by test-case pass ratio.) verifyQuizAccessis permissive. It only checks that the module exists and returnstruefor any module — the enrolled-course restriction is commented out. Combined with the list query showing all published quizzes when a student has no enrollments, quiz access is broader than theisEnrolledMiddlewaregate implies.- XP grant idempotency. The quiz XP hook keys on
(userId, 'assignment_submitted', submissionId), so retries cannot double-credit; but note the XPtypeis literallyassignment_submittedeven though it fires for a quiz submission (assignments have no submission flow of their own). - Percentage basis differs.
submitQuizcomputespercentage = score / quiz.points, wherescoreis summed question points andquiz.pointsis the quiz's total-points field; if those drift apart the percentage can exceed or undershoot expectations.passedisscore >= passingScore(a raw-points comparison), whilepassingScoreis documented as a percentage on the entity — a known inconsistency to watch. - AI generation is never auto-saved.
generateQuiz/generateQuizForCoursereturn the draft to the client only; persistence requires a separatePOST /mas/quizzes. The mock fallback means a "successful" generation may be entirely canned content if mr-hire-backend is down. - Assignments have no submission tracking. The only server-side behaviour is the 24h reminder; "pending assignments" surfaced on dashboards are computed from due dates, not submission records.
- Quiz template uniqueness.
(roleSlug, version, createdBy)is unique and version auto-increments per(roleSlug, createdBy); templates are filtered by the logged-in user, so they are private per recruiter. - Question ownership guards. Seeded questions (
isSeeded: true) cannot be edited/deleted (403); non-seeded questions can only be modified by theircreatedBy(orsystem). - Eval tests are read-mostly here.
eval_testsis populated by Excel upload (UploadScoreController) reflecting external Mr. Test results;studentIdis nullable because the externalstudentTestIdmay not map to a local user. Dashboards treateval_tests/performance_metricsas a stale cache and prefer a live snapshot where available. - Multi-platform. None of these flows branch on the
x-platformheader; they are platform-agnostic.
Related docs¶
- Education & LMS — Courses, Modules & Enrollment — where
Quiz/Assignmentattach toModule, and enrollment drives visibility. - Student Engagement & Gamification — XP grants and badge re-evaluation triggered by quiz submission.
- AI Platform — LLM Gateway — the AI services behind quiz generation/curation.
- Mr. Hire — Jobs & Applications — where candidate quizzes and quiz templates fit the recruitment pipeline.
- Mr. Hire — AI Resume Screening —
ScreeningResult.quizScoreconsumes candidate quiz outcomes. - Identity & Access —
authMiddleware,adminMiddleware,isEnrolledMiddleware,hrRoleMiddleware.