Skip to content

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.generateQuizPOST /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.curateQuizPOST /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) expects MCQ / CODING / CASE_BASED with an answer: string[] (letter-based for MCQ). The Question Bank uses multiple-choice / coding / text with correctAnswer: string and testCases. 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, with TODO comments to integrate Mr. Hire AI evaluation. Only MCQ is exactly compared. (Candidate quizzes, by contrast, score coding by test-case pass ratio.)
  • verifyQuizAccess is permissive. It only checks that the module exists and returns true for 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 the isEnrolledMiddleware gate implies.
  • XP grant idempotency. The quiz XP hook keys on (userId, 'assignment_submitted', submissionId), so retries cannot double-credit; but note the XP type is literally assignment_submitted even though it fires for a quiz submission (assignments have no submission flow of their own).
  • Percentage basis differs. submitQuiz computes percentage = score / quiz.points, where score is summed question points and quiz.points is the quiz's total-points field; if those drift apart the percentage can exceed or undershoot expectations. passed is score >= passingScore (a raw-points comparison), while passingScore is documented as a percentage on the entity — a known inconsistency to watch.
  • AI generation is never auto-saved. generateQuiz / generateQuizForCourse return the draft to the client only; persistence requires a separate POST /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 their createdBy (or system).
  • Eval tests are read-mostly here. eval_tests is populated by Excel upload (UploadScoreController) reflecting external Mr. Test results; studentId is nullable because the external studentTestId may not map to a local user. Dashboards treat eval_tests/performance_metrics as a stale cache and prefer a live snapshot where available.
  • Multi-platform. None of these flows branch on the x-platform header; they are platform-agnostic.