Mr. Hire — Jobs, Applications & Candidate Quizzes¶
This document describes the recruitment / ATS core of Mr. Hire as implemented inside the
mr-mentor-backend Node service: how HR users author job posts (often from a job template),
publish them to external platforms, how candidates apply, how an application travels through
the pipeline stages, how a candidate takes a screening quiz that produces a ScreeningResult,
how the screening config gates auto-advance / auto-shortlist, and how placement scores are
uploaded and computed. The AI-heavy stages (resume parsing/scoring, AI voice calls) live in the
resumeAnalysisQueue worker and the Python mr-hire-backend; those are documented in sibling docs and
only referenced here.
Status: documented from source on this branch.
Overview¶
Mr. Hire is the recruitment product hosted inside the shared mr-mentor-backend. The jobs &
applications domain covers the structured parts of the ATS — everything that is plain CRUD,
pipeline movement, configuration, and quiz delivery. The intelligent parts (resume parsing,
resume scoring, voice interviews) are queued out to the AI subsystem.
Who uses it (personas / roles):
| Persona | Role value | What they do |
|---|---|---|
| External HR / recruiter | EXTERNAL_HR |
Create/manage their own job posts, templates, screening config, send quizzes, trigger AI calls, manage their candidate pipeline. |
| Admin / Superadmin | ADMIN, SUPERADMIN |
Manage all job posts/templates, global templates, the master platform list, AI-screening config per user, view all applications. |
| Candidate (public) | (no auth) | Apply via the public hiring form, take a screening quiz via a signed link. |
| Student (placement) | USER student |
Subject of placement-readiness scoring (a separate concept — see Placement score). |
Roles are checked by auth.middleware.ts (authMiddleware → populates req.user),
admin.middleware.ts (adminMiddleware → ADMIN | SUPERADMIN), and
hrRole.middleware.ts (hrRoleMiddleware → EXTERNAL_HR | ADMIN, see src/middleware/hrRole.middleware.ts).
Where it sits in the suite: the mr-hire-frontend (React/Vite) and the public Mr. Hire hiring
page call these endpoints. HR-only AI features (JD generation, quiz curation, AI call triggering,
resume parsing) are proxied through this backend to the Python mr-hire-backend at
MR_HIRE_BACKEND_URL (default http://localhost:8001) so the frontend never calls it directly.
Key concepts & entities¶
Glossary
- Job post — a published opening.
statusdrives whether it is visible/active. - Job template — a reusable blueprint for a job post;
global(admin-authored, visible to all HR) orprivate(HR-owned). Templates can also carry quiz/AI-call defaults. - Job platform posting — a record that a job was published to one external platform (Naukri,
LinkedIn, Indeed, Google for Jobs, etc.), with
method(automatic/api/manual) and a postingstatus. - Application (candidate) — a
JobApplication; the candidate record that moves through the pipeline. - Screening config —
JobScreeningConfig: per-job → per-HR-global → system-global → hardcoded defaults cascade controlling weights, thresholds, quiz settings, AI-call gating. - AI screening config —
AiScreeningConfig: a per-user on/off switch for auto-screening on apply. - Candidate quiz response —
CandidateQuizResponse: a snapshot of quiz questions plus a signed JWT token, the candidate's answers, and the computed score. - Screening result —
ScreeningResult: the aggregate scorecard (resume + quiz + call scores, parsed data, final score, recommendation, cost tracking, config snapshot).
Main TypeORM entities (file paths)
| Entity | File | Table | Notes |
|---|---|---|---|
JobPost |
src/entities/JobPost.ts |
job_posts |
status: draft\|active\|paused\|closed; postedBy (email), belongsTo (owner userId). |
JobTemplate |
src/entities/JobTemplate.ts |
job_templates |
scope: global\|private; createdBy FK → User. |
JobPlatformPosting |
src/entities/JobPlatformPosting.ts |
job_platform_postings |
Unique (jobPostId, platformId); status: pending\|posted\|failed\|manual_required. |
JobScreeningConfig |
src/entities/JobScreeningConfig.entity.ts |
job_screening_configs |
Per-job (jobPostId) or global (isGlobal, belongsTo). Weights must sum to 100. |
AiScreeningConfig |
src/entities/AiScreeningConfig.entity.ts |
ai_screening_configs |
Unique userId; autoScreenEnabled, autoScreenOnApply. |
JobApplication |
src/entities/JobApplication.ts |
job_applications |
status: CandidateStatus; priority 1-5; resumeKey/resumeUrl. |
CandidateQuizResponse |
src/entities/CandidateQuizResponse.ts |
candidate_quiz_responses |
Unique token; questions snapshot, answers, percentage, expiresAt. |
ScreeningResult |
src/entities/ScreeningResult.entity.ts |
screening_results |
One-to-one-ish with application; screeningStatus, finalScore, screeningConfig snapshot. |
Application |
src/entities/Application.ts |
— | Distinct legacy/placement application entity (used by placement scoring), only mentioned here. |
Related sibling entities referenced but documented elsewhere: ResumeAnalysis (resume scoring),
Platform / HrPlatformAccount (platform master + HR connections), QuizTemplate, EmailTemplate.
Architecture¶
flowchart TD
subgraph FE["Clients"]
PUB["Public hiring page"]
HRUI["mr-hire-frontend (HR)"]
ADMINUI["Admin UI"]
end
subgraph RT["Routes (mounted under /api)"]
R1["jobPost.routes"]
R2["jobTemplate.routes"]
R3["jobApplication.routes (/api/admin)"]
R4["mrHire.routes"]
R5["candidateQuiz.routes (/api/candidate-quiz)"]
R6["aiScreeningConfig.routes (/api/admin)"]
R7["platform.routes"]
end
subgraph CTRL["Controllers"]
C1["JobPostController"]
C1b["JobScreeningConfigController"]
C2["JobTemplateController"]
C3["JobApplicationController"]
C4["MrHireController"]
C5["CandidateQuizController"]
C6["AiScreeningConfigController"]
C7["PlatformController"]
end
subgraph SVC["Services"]
S1["JobPostService"]
S2["JobTemplateService"]
S3["JobApplicationService"]
S4["PlatformService"]
S5["S3Service"]
S6["QueueService"]
end
subgraph DB["PostgreSQL (TypeORM)"]
D1["job_posts"]
D2["job_templates"]
D3["job_applications"]
D4["job_platform_postings"]
D5["job_screening_configs"]
D6["ai_screening_configs"]
D7["candidate_quiz_responses"]
D8["screening_results"]
end
subgraph EXT["External / async"]
Q["resumeAnalysisQueue + emailQueue (BullMQ)"]
PY["mr-hire-backend (Python, port 8001)"]
S3B["AWS S3 (resumes)"]
end
PUB --> R4
PUB --> R5
HRUI --> R1 & R2 & R3 & R4 & R7
ADMINUI --> R1 & R2 & R3 & R6 & R7
R1 --> C1
R1 --> C1b
R2 --> C2
R3 --> C3
R4 --> C4
R5 --> C5
R6 --> C6
R7 --> C7
C1 --> S1 --> D1
C1b --> D5
C2 --> S2 --> D2
C3 --> S3 --> D3
C3 --> D8
C4 --> S3
C4 --> S6
C4 --> PY
C5 --> D7
C5 --> D8
C6 --> D6
C7 --> S4 --> D4
S3 --> S5 --> S3B
S6 --> Q
Q --> PY
Q --> D8
Data model¶
erDiagram
JOB_POST ||--o{ JOB_APPLICATION : "receives"
JOB_POST ||--o{ JOB_PLATFORM_POSTING : "published to"
JOB_POST ||--o| JOB_SCREENING_CONFIG : "configured by"
JOB_TEMPLATE ||--o{ JOB_POST : "blueprint for"
JOB_APPLICATION ||--o| SCREENING_RESULT : "scored by"
JOB_APPLICATION ||--o{ CANDIDATE_QUIZ_RESPONSE : "quiz attempts"
USER ||--o{ JOB_TEMPLATE : "creates"
USER ||--o| AI_SCREENING_CONFIG : "owns"
PLATFORM ||--o{ JOB_PLATFORM_POSTING : "target of"
HR_PLATFORM_ACCOUNT ||--o{ JOB_PLATFORM_POSTING : "credential for"
JOB_POST {
uuid id PK
string name
string hiringLabel
json roles
json tools
json domains
boolean isActive
string status "draft|active|paused|closed"
string postedBy "HR email"
uuid belongsTo "owner userId"
string salaryBasis "fixed|ctc_based|negotiable"
int headcount
}
JOB_TEMPLATE {
uuid id PK
string name
string scope "global|private"
uuid createdBy FK
string quizAutoSet
int quizQuestionCount
string aiCallScript
}
JOB_PLATFORM_POSTING {
uuid id PK
uuid jobPostId FK
string platformId FK
uuid hrPlatformAccountId FK
string method "automatic|api|manual"
string status "pending|posted|failed|manual_required"
int applicationsReceived
string externalUrl
}
JOB_SCREENING_CONFIG {
uuid id PK
uuid jobPostId FK
boolean isGlobal
uuid belongsTo
int weightResume
int weightQuiz
int weightVoiceInterview
int autoShortlistThreshold
int autoRejectThreshold
boolean aiCallEnabled
int aiCallMinimumScore
}
AI_SCREENING_CONFIG {
uuid id PK
uuid userId FK
boolean autoScreenEnabled
boolean autoScreenOnApply
}
JOB_APPLICATION {
uuid id PK
string fullName
string email
string phone
string jobPostId
string role
string resumeKey
string status "CandidateStatus"
int priority "1-5"
string source
}
CANDIDATE_QUIZ_RESPONSE {
uuid id PK
string token UK
uuid applicationId
string jobPostId
uuid belongsToId
json questions
json answers
int score
decimal percentage
boolean submitted
timestamp expiresAt
}
SCREENING_RESULT {
uuid id PK
uuid applicationId FK
float overallResumeScore
float quizScore
float overallCallScore
float finalScore
string finalRecommendation
string screeningStatus
json screeningConfig
}
Notable enums / status fields
JobPost.status:draft | active | paused | closed(PortalType,JobType,SalaryBasisalso defined inJobPost.ts).JobApplication.status(CandidateStatus):resume_received | resume_parsed | outreach_sent | quiz_invited | quiz_completed | voice_interview_scheduled | voice_interview_completed | scoring_complete | shortlisted | talent_pool | rejected.ScreeningResult.screeningStatus(ScreeningStatus):pending | resume_screening | resume_screened | quiz_sent | quiz_completed | call_scheduled | call_completed | completed | failed.JobPlatformPosting.status(PostingStatus):pending | posted | failed | manual_required.
API surface¶
Paths below are the full paths including the mount prefix from src/routes/index.ts.
Job posts — jobPost.routes (mounted at /api)¶
| Method | Path | Auth/role | Purpose |
|---|---|---|---|
| GET | /api/mr-hire/job-openings |
public | List active job posts for the hiring page. |
| GET | /api/mr-hire/job-openings/:id |
public | Get a single job post. |
| GET | /api/hr/job-posts |
auth (HR) | HR's own posts (by belongsTo + legacy postedBy). |
| POST | /api/hr/job-posts |
auth (HR) | Create a post (drafts allow missing fields). |
| PUT | /api/hr/job-posts/:id |
auth (HR) | Update own post. |
| DELETE | /api/hr/job-posts/:id |
auth (HR) | Delete own post. |
| PATCH | /api/hr/job-posts/:id/toggle |
auth (HR) | Toggle isActive (syncs status active↔paused). |
| GET | /api/hr/job-posts-counts |
auth (HR) | Applicant + shortlisted counts per job. |
| GET | /api/hr/screening-config/global |
auth (HR) | Get HR's global screening config (cascade). |
| PUT | /api/hr/screening-config/global |
auth (HR) | Upsert HR's global screening config. |
| GET | /api/hr/job-posts/:jobPostId/screening-config |
auth (HR) | Get per-job config (job→global→default cascade). |
| PUT | /api/hr/job-posts/:jobPostId/screening-config |
auth (HR) | Upsert per-job config. |
| GET | /api/admin/job-posts |
admin | All posts. |
| POST | /api/admin/job-posts |
admin | Create post (admin). |
| POST | /api/admin/job-posts/import |
admin | Bulk import posts. |
| PUT | /api/admin/job-posts/:id |
admin | Update any post. |
| DELETE | /api/admin/job-posts/:id |
admin | Delete any post. |
| PATCH | /api/admin/job-posts/:id/toggle |
admin | Toggle any post. |
Job templates — jobTemplate.routes (mounted at /api)¶
| Method | Path | Auth/role | Purpose |
|---|---|---|---|
| GET | /api/hr/job-templates |
auth (HR) | Global + own private templates. |
| POST | /api/hr/job-templates |
auth (HR) | Create a private template. |
| PUT | /api/hr/job-templates/:id |
auth (HR) | Update own (cannot promote to global). |
| DELETE | /api/hr/job-templates/:id |
auth (HR) | Delete own private template. |
| GET | /api/admin/job-templates |
admin | All templates. |
| POST | /api/admin/job-templates |
admin | Create a global template. |
| PUT | /api/admin/job-templates/:id |
admin | Update any template. |
| DELETE | /api/admin/job-templates/:id |
admin | Delete any template. |
Applications / pipeline — jobApplication.routes (mounted at /api/admin)¶
Candidate/Kanban endpoints require
authMiddlewareonly (HR uses them); the/job-applications/*group additionally requiresadminMiddleware.
| Method | Path | Auth/role | Purpose |
|---|---|---|---|
| GET | /api/admin/candidates |
auth | Paginated candidates (search/status/job/belongsTo filters) with resume analysis + screening result. |
| GET | /api/admin/candidates/recent |
auth | Recent candidates with final score. |
| GET | /api/admin/candidates/top |
auth | Top candidates by finalScore. |
| GET | /api/admin/candidates/counts-by-status |
auth | Pipeline counts grouped by status (scoped by belongsTo). |
| PATCH | /api/admin/candidates/bulk-status |
auth | Bulk status update. |
| GET | /api/admin/candidates/:id |
auth | Single candidate (UUID validated) with analysis + screening. |
| PATCH | /api/admin/candidates/:id/status |
auth | Move candidate stage + send status-change email. |
| PATCH | /api/admin/candidates/:id/manual-scores |
auth | Set manual resume/quiz/call scores, recompute final score. |
| PATCH | /api/admin/candidates/:id/clear-stage |
auth | Clear stage data on backward Kanban move. |
| PATCH | /api/admin/candidates/:id/priority |
auth | Set priority 1-5. |
| GET | /api/admin/candidates/:id/resume |
auth | Fresh pre-signed resume URL. |
| GET | /api/admin/candidates/:id/analysis |
auth | Resume analysis for candidate. |
| GET | /api/admin/job-applications |
admin | All applications (paginated). |
| GET | /api/admin/job-applications/export |
admin | Export (CSV-shaped JSON). |
| GET | /api/admin/job-applications/:id |
admin | Single application. |
| GET | /api/admin/job-applications/by-job/:jobPostId |
admin | Applications for a job. |
| GET | /api/admin/job-applications/stats/summary |
admin | Stats by job/source. |
| DELETE | /api/admin/job-applications/:id |
admin | Delete application. |
| GET | /api/admin/job-applications/:id/resume |
admin | Pre-signed resume URL. |
Mr. Hire apply + HR tools — mrHire.routes (mounted at /api)¶
| Method | Path | Auth/role | Purpose |
|---|---|---|---|
| GET | /api/mr-hire/check-applied/:jobPostId |
auth | Has the current user applied (and to which roles). |
| POST | /api/mr-hire/apply |
public, multipart (resume) |
Direct candidate apply (creates application + uploads resume). |
| POST | /api/mr-hire/send-mas101-email |
public | Send MAS101 info email. |
| POST | /api/mr-hire/import-from-drive |
HR | Import resumes from Google Drive → create applications. |
| POST | /api/mr-hire/parse-resumes |
HR | Parse Drive resumes (preview only, proxied to Python). |
| POST | /api/mr-hire/bulk-apply |
HR | Bulk-create applications from parsed data. |
| GET | /api/mr-hire/calls |
public proxy | List AI calls (proxied to Python). |
| GET | /api/mr-hire/call/:callId |
public proxy | Call details (proxied). |
| POST | /api/mr-hire/call |
HR | Trigger AI call (proxied). |
| POST | /api/mr-hire/generate-jd |
HR | AI job-description generation (proxied to /api/v1/generate-jd). |
| GET/PUT/POST | /api/mr-hire/interview-prompt[...] |
HR | Get/update/reset interview system prompt. |
| POST | /api/mr-hire/quiz/curate |
HR | Curate quiz from JD + resume (proxied to /api/v1/quiz/curate), saves to ScreeningResult. |
| POST | /api/mr-hire/quiz/curate-for-template |
HR | Curate a quiz for a template (only needs JD + count). |
| POST | /api/mr-hire/quiz/send-email |
HR | Generate signed quiz token + queue invite email. |
| GET/POST/PUT/DELETE | /api/mr-hire/quiz/templates[...] |
HR | Quiz template CRUD + roles list. |
| GET/POST/PUT/DELETE | /api/mr-hire/call-prompts[...] |
HR | AI call prompt CRUD + active prompt. |
Candidate quiz — candidateQuiz.routes (mounted at /api/candidate-quiz)¶
| Method | Path | Auth/role | Purpose |
|---|---|---|---|
| GET | /api/candidate-quiz/take/:token |
public (token IS auth) | Get quiz questions (answers stripped). |
| POST | /api/candidate-quiz/take/:token/submit |
public (token IS auth) | Submit answers, auto-score, advance pipeline. |
| GET | /api/candidate-quiz/admin/quiz-results/:applicationId |
auth | HR fetch submitted quiz + score. |
AI screening config — aiScreeningConfig.routes (mounted at /api/admin)¶
| Method | Path | Auth/role | Purpose |
|---|---|---|---|
| GET | /api/admin/mr-hire/ai-screening-configs |
admin | List per-user auto-screen configs. |
| POST | /api/admin/mr-hire/ai-screening-config |
admin | Upsert per-user config. |
Multi-platform posting — platform.routes (mounted at /api)¶
| Method | Path | Auth/role | Purpose |
|---|---|---|---|
| GET | /api/careers/google-jobs/:id |
public | Google for Jobs JSON-LD for a post. |
| GET | /api/careers/google-jobs |
public | JSON-LD for all active posts. |
| GET | /api/careers/indeed-feed.xml |
public | Indeed XML feed. |
| GET | /api/hr/platforms |
auth | Active platforms. |
| GET/POST/PUT/DELETE | /api/hr/platform-accounts[...] |
auth | HR's connected platform accounts. |
| POST | /api/hr/job-posts/:id/publish |
auth | Publish a job to selected platforms (body platformIds). |
| GET | /api/hr/job-posts/:id/platforms |
auth | Postings for a job. |
| PATCH | /api/hr/job-posts/:id/platforms/:platformId |
auth | Mark a manual posting as posted. |
| GET/POST/PUT/PATCH | /api/admin/platforms[...] |
admin | Master platform list management. |
Placement / score upload (adjacent)¶
| Method | Path | Auth/role | Purpose |
|---|---|---|---|
| GET | /api/placement-score/student/:studentId |
auth | Student placement-readiness score. |
| GET | /api/placement-score/batch/:batchId |
auth | Batch placement scores. |
| POST | /api/placement-score/students/batch |
auth | Scores for many students. |
| GET | /api/placement-score/weights |
auth | Placement weight reference. |
| POST | /api/batchlead/upload-score |
(see batchLead.routes) |
UploadScoreController.uploadScores — bulk score upload. |
User journeys¶
1. HR creates a job post from a template and publishes to platforms¶
HR picks a template (global or their own), the frontend pre-fills the create form, the post is saved
(owned by belongsTo = req.user.id), and then HR publishes to one or more platforms. Note the
frontend copies template fields into the create payload — the backend JobPostService.create does not
read the template itself.
sequenceDiagram
participant HR as HR Frontend
participant API as Backend API
participant TS as JobTemplateService
participant PS as JobPostService
participant PLS as PlatformService
participant DB as PostgreSQL
HR->>API: GET /api/hr/job-templates
API->>TS: getForUser userId
TS->>DB: select global plus own private
DB-->>TS: templates
TS-->>API: templates
API-->>HR: template list
Note over HR: HR selects a template and edits the form
HR->>API: POST /api/hr/job-posts with filled fields
API->>PS: create dto with belongsTo and postedBy
PS->>DB: insert job_posts
DB-->>PS: saved post
Note over API: if about text is long it records a JD generation cost
PS-->>API: saved post
API-->>HR: 201 created post
HR->>API: POST /api/hr/job-posts/:id/publish with platformIds
API->>PLS: publishToPlaftorms jobPostId userId platformIds
loop each platform
PLS->>DB: upsert job_platform_postings
Note over PLS: automatic platform set status posted, api or oauth set manual_required
end
PLS-->>API: postings
API-->>HR: 200 postings with statuses
2. Candidate applies via the public hiring form¶
The public apply endpoint validates fields (relaxed for HR uploads), uploads the resume to S3, creates
the JobApplication, and conditionally queues resume analysis based on the global auto-screen flag or
a special source.
sequenceDiagram
participant C as Candidate Browser
participant API as Backend API
participant JAS as JobApplicationService
participant S3 as S3Service
participant SC as SystemConfigService
participant Q as resumeAnalysisQueue
C->>API: POST /api/mr-hire/apply multipart with resume
Note over API: multer accepts pdf doc docx up to 5MB
API->>API: validate required fields and email format
API->>JAS: createApplication dto
JAS->>JAS: hasAppliedForRole check
alt already applied for this role
JAS-->>API: throw already applied
API-->>C: 400 already applied for this role
else new application
JAS->>S3: uploadJobApplicationResume buffer
S3-->>JAS: s3Key and url
JAS-->>API: saved application
API->>SC: getValue global_auto_screen_on_apply
SC-->>API: flag value
alt auto screen enabled or source is external hr form
API->>Q: addResumeAnalysisJob resume-analysis with priority
Note over Q: AI parsing and scoring handled by resume screening worker
else auto screen off
Note over API: no job queued
end
API-->>C: 201 application submitted
end
3. HR sends a screening quiz, candidate takes it, ScreeningResult updates¶
HR curates a quiz (questions are saved onto the ScreeningResult), then sends it. Sending invalidates
old quiz links, generates a fresh signed JWT and a CandidateQuizResponse record, and queues the
invite email. The candidate opens the link, answers, and submission auto-scores and advances the
pipeline.
sequenceDiagram
participant HR as HR Frontend
participant API as Backend API
participant PY as Python mr-hire-backend
participant SR as screening_results
participant CQ as candidate_quiz_responses
participant Q as emailQueue
participant C as Candidate
HR->>API: POST /api/mr-hire/quiz/curate with jd and resume
API->>PY: POST /api/v1/quiz/curate
PY-->>API: quiz questions
API->>SR: save quizQuestions onto screening result
API-->>HR: curated quiz
HR->>API: POST /api/mr-hire/quiz/send-email
API->>SR: load screening result and quizQuestions
API->>CQ: expire existing responses for application
API->>CQ: create response then sign JWT with responseId
API->>SR: set quizLink and screeningStatus quiz_sent
API->>API: set application status quiz_invited
API->>Q: addEmailJob quiz-invitation with quizLink
API-->>HR: 200 quizUrl and expiresAt
C->>API: GET /api/candidate-quiz/take/:token
API->>API: verify JWT and check expiry and submitted
API->>CQ: load questions
Note over API: correct answers stripped before sending
API-->>C: questions without answers
C->>API: POST /api/candidate-quiz/take/:token/submit with answers
API->>API: score answers and compute percentage
API->>CQ: save answers score and submitted true
API->>SR: set quizScore and quizCompletedAt and quiz_completed
API->>API: advance application to quiz_completed unless later stage
alt aiCallEnabled and percentage above call minimum and phone present
API->>Q: addResumeAnalysisJob ai-call delayed
else no call
API->>API: compute final score from resume plus quiz
API->>API: auto shortlist or reject or scoring_complete
end
API-->>C: 200 score and percentage
4. Application moves through the pipeline (Kanban)¶
HR drags a candidate between stages. Forward moves can set status and trigger templated emails; backward moves clear downstream stage data and reset the screening result.
sequenceDiagram
participant HR as HR Frontend
participant API as Backend API
participant AR as job_applications
participant SR as screening_results
participant ET as email_templates
participant Q as emailQueue
HR->>API: PATCH /api/admin/candidates/:id/status with status
API->>AR: update status
API->>ET: find template by trigger and owner or global
alt template found or default exists for shortlisted or rejected
API->>Q: addEmailJob workflow-email with rendered subject and body
Note over API: email failures are non blocking
else no email mapping
Note over API: no email sent
end
API-->>HR: 200 updated candidate
Note over HR: backward move triggers clear-stage
HR->>API: PATCH /api/admin/candidates/:id/clear-stage with targetStageId
API->>SR: clear call quiz or resume fields by stage index
API->>SR: reset finalScore and screeningStatus pending
API-->>HR: 200 stage data cleared
Note over HR: manual scores for skipped stages
HR->>API: PATCH /api/admin/candidates/:id/manual-scores
API->>SR: apply provided scores and load weights from config
API->>SR: recompute final score with weight normalization
API-->>HR: 200 finalScore and recommendation
5. HR bulk-imports candidates from Google Drive¶
sequenceDiagram
participant HR as HR Frontend
participant API as Backend API
participant GD as Google Drive
participant JAS as JobApplicationService
participant S3 as S3Service
participant Q as resumeAnalysisQueue
HR->>API: POST /api/mr-hire/import-from-drive
Note over API: requires HR or admin role
API->>GD: download resume files
GD-->>API: file buffers
loop each resume
API->>JAS: createApplication with resume buffer
JAS->>S3: upload resume
JAS-->>API: saved application
API->>Q: addResumeAnalysisJob resume-analysis
end
API-->>HR: 200 imported summary
6. Admin configures AI auto-screening and screening thresholds¶
sequenceDiagram
participant ADM as Admin UI
participant API as Backend API
participant ASC as ai_screening_configs
participant JSC as job_screening_configs
ADM->>API: POST /api/admin/mr-hire/ai-screening-config with userId and flags
API->>ASC: upsert by userId
API-->>ADM: saved config
ADM->>API: PUT /api/hr/screening-config/global with weights and thresholds
API->>API: validate weights sum to 100
alt weights invalid
API-->>ADM: 400 weights must sum to 100
else valid
API->>JSC: upsert global config for owner
API-->>ADM: saved config
end
Placement score (a separate concept)¶
The PlacementScoreController / PlacementScoreService compute a student placement-readiness
score (weighted blend of evaluation tests 15%, learn progress 15%, super-mentor 30%, communication
15%, attendance 15%, mock interviews 10% — see PLACEMENT_WEIGHTS in
src/services/PlacementScoreService.ts). This is a placement/LMS concept and is not the same as
the Mr. Hire candidate finalScore in ScreeningResult. UploadScoreController.uploadScores
(POST /api/batchlead/upload-score) ingests external test scores used by this computation. These are
included here only because they were in scope; see the placement/LMS docs for the full picture.
Background jobs & async¶
resumeAnalysisQueue(BullMQ, workersrc/workers/resumeAnalysis.worker.ts): processesresume-analysis/full-screeningjobs (resume parse + score, quiz generation) andai-calljobs (voice interview). Enqueued byapplyDirect,importFromDrive,bulkApply, and the quiz submit flow (delayed AI call).prioritymaps fromJobApplication.priority(lower = higher priority). The quiz submit handler also importscalculateFinalScoreandaddToTalentPoolIfEligiblefrom this worker module to compute final scores inline when no AI call is scheduled.emailQueue(viaQueueService.addEmailJob): quiz invitations (quiz-invitation), pipeline status-change emails (workflow-email), MAS101 info email.- Quiz token expiry / re-send:
CandidateQuizResponse.expiresAtplus the signed JWTexpiresIn(default 7 days). Re-sending a quiz immediately expires all prior responses for the application and resetsScreeningResult.quizScore/quizCompletedAt, so old links return HTTP 410. - AI call gap:
MR_HIRE_CALL_GAP(minutes, default 1) delays the queuedai-calljob after a passing quiz. - No Socket.IO events or inbound webhooks are owned by this domain (call/result webhooks land in the voice-interview / Python subsystem).
External integrations¶
| Integration | Where | Env / config | Failure behavior |
|---|---|---|---|
Python mr-hire-backend |
MrHireController proxy methods |
MR_HIRE_BACKEND_URL (default http://localhost:8001) |
Proxied responses pass upstream status through; quiz curation persists questions only when applicationId given. |
| AWS S3 | S3Service.uploadJobApplicationResume, getJobApplicationResumeSignedUrl |
AWS_* / bucket env |
Resume upload failure is caught — application is still saved without a resume. Pre-signed URLs valid ~1 hour. |
| Quiz token signing | candidateQuiz.controller.ts |
QUIZ_TOKEN_SECRET or JWT_SECRET (HS256). Startup throws if neither set. |
Invalid token → 400; expired → 410. |
| Quiz frontend URL | generateQuizToken |
MR_HIRE_FRONTEND_URL (default http://localhost:5173) |
Builds /quiz/take/:token. |
| Google Drive | importFromDrive, parseResumes |
Drive connector / Google OAuth | Per-file errors are collected; import continues. |
| Auto-screen flag | applyDirect |
SystemConfig key global_auto_screen_on_apply and AiScreeningConfig |
When off and source is not a special HR form, no screening job is queued. |
| OpenAI (cost ledger) | JobPostController.create records JD-generation cost |
PipelineCostService / PipelineCostLedger |
Cost recording failures are warnings only. |
Status lifecycles¶
JobApplication.status (candidate pipeline)¶
stateDiagram-v2
[*] --> resume_received
resume_received --> resume_parsed
resume_parsed --> outreach_sent
outreach_sent --> quiz_invited
resume_parsed --> quiz_invited
quiz_invited --> quiz_completed
quiz_completed --> voice_interview_scheduled
voice_interview_scheduled --> voice_interview_completed
voice_interview_completed --> scoring_complete
quiz_completed --> scoring_complete
scoring_complete --> shortlisted
scoring_complete --> rejected
scoring_complete --> talent_pool
shortlisted --> [*]
rejected --> [*]
talent_pool --> [*]
Note: the quiz-submit handler only advances to quiz_completed if the application is not already in a
later stage, then auto-routes to shortlisted / rejected / scoring_complete based on thresholds,
or to voice_interview_scheduled indirectly by queueing the AI call.
ScreeningResult.screeningStatus¶
stateDiagram-v2
[*] --> pending
pending --> resume_screening
resume_screening --> resume_screened
resume_screened --> quiz_sent
pending --> quiz_sent
quiz_sent --> quiz_completed
quiz_completed --> call_scheduled
call_scheduled --> call_completed
call_completed --> completed
quiz_completed --> completed
resume_screening --> failed
call_scheduled --> failed
failed --> [*]
completed --> [*]
JobPost.status¶
stateDiagram-v2
[*] --> draft
draft --> active
active --> paused
paused --> active
active --> closed
paused --> closed
closed --> [*]
toggleActive flips isActive and keeps status in sync (active↔paused); closed is set via
explicit update.
JobPlatformPosting.status¶
stateDiagram-v2
[*] --> pending
pending --> posted
pending --> manual_required
pending --> failed
manual_required --> posted
failed --> posted
posted --> [*]
Edge cases, limits & gotchas¶
- Auth split: candidate/Kanban endpoints under
/api/admin/candidates/*require onlyauthMiddleware(so HR can use them) while/api/admin/job-applications/*additionally requireadminMiddleware. Despite the/api/adminmount, the candidate group is not admin-gated. - HR role: Mr. Hire HR tools require
hrRoleMiddleware(EXTERNAL_HR | ADMIN).SUPERADMINis not in the HR-allowed set, onlyADMIN. - Ownership scoping: candidate lists scope by
belongsTovia an inner joinjp.id = app."jobPostId"::uuid— applications with nojobPostIdare excluded frombelongsTofilters and from counts-by-status. - Templates are not server-applied: creating a post from a template is a frontend copy;
JobPostService.createnever readsJobTemplate. HR cannot promote a private template toglobal(scope/createdBy are stripped on HR update). - Screening config cascade: per-job → user-global (
isGlobal && belongsTo) → system-global (isGlobal && belongsTo IS NULL) →HARDCODED_DEFAULTS. Weights must sum to 100 or the upsert returns 400. A snapshot of the resolved config is stored on eachScreeningResult.screeningConfig. - Quiz idempotency / re-send: re-sending a quiz expires every prior
CandidateQuizResponsefor the application; opening a stale link returns 410 with a "link has been replaced" message if a newer non-expired record exists. Submission rejects ifsubmittedalready true or expired. - Quiz scoring: supports MCQ (letter↔option-text resolution), multi-select (sorted, normalized),
and coding questions (scored by stored
{passed, totalTestCases}ratio). Correct answers are stripped before questions are sent to candidates. - Resume upload resilience: an S3 upload failure does not fail the application — it is saved
without a resume, which also means no screening job will be queued (
application.resumeKeyfalsy). - Legacy source typo: the special auto-screen source string is
exterenal-hr-hiring-form(intentional typo kept for compatibility); default form source ismr-hire-hiring-form. - Duplicate apply: enforced per
(email, jobPostId, role); a duplicate returns 400 "already applied for this role". - TypeORM auto-sync is on in dev — these entities create/update tables automatically; the
job_applications.jobPostIdis a plainvarchar(not a FK), hence the::uuidcasts in joins. - Placement score ≠ candidate final score: see the Placement score
note — different entity (
Application), different math (PLACEMENT_WEIGHTS).
Related docs¶
- Mr. Hire — Resume Analysis & AI Screening — the
resumeAnalysisQueueworker, resume parsing/scoring,ResumeAnalysis,calculateFinalScore. - Mr. Hire — Voice Interviews — AI calling (ElevenLabs / Aarya /
MissOzone), call scoring written back to
ScreeningResult. - Mr. Hire — Talent Pool & Salary Benchmarking —
talent_poolstatus andaddToTalentPoolIfEligible. - Mr. Hire — Multi-platform Posting & Job Feeds —
Platform,HrPlatformAccount, Google for Jobs / Indeed feeds (inferred sibling doc). - Pipeline Cost Ledger — JD-generation and screening cost tracking (inferred).
- Background Jobs & Queues — BullMQ queues/workers overview (inferred).