Technical Document (LLD/HLD)

HireGenius- AI-Powered Recruitment Automation System

📘 Table of Contents

  1. System Overview
  2. Module Breakdown
  3. API Design
  4. Database Design
  5. Business Workflow
  6. System Architecture
  7. Sequence Diagram
  8. Flow Diagram

1. System Overview

1.1 🎯 Purpose

Automate the recruitment pipeline from resume ingestion to candidate ranking using NLP-based parsing, vector embeddings, and AI-powered re-ranking.


1.2 ⚙️ Key Capabilities

  • Incremental Resume Ingestion: Fetch new resumes from Gmail on schedule
  • Automated Resume Parsing: Extract structured data (skills, experience, education, contact)
  • Semantic Matching: Use embeddings to find similar candidates to job descriptions
  • AI Re-Ranking: Re-rank top candidates using GPT-4o-mini for better precision
  • Dashboard: HR views ranked candidates with explainability

1.3 🧰 Technology Stack

Layer Technology
Frontend React.js + Tailwind CSS
Backend Node.js + Express.js
Database PostgreSQL 15+ with pgvector extension
AI/ML OpenAI GPT-4o-mini
Embeddings OpenAI text-embedding-3-small
Email Integration Gmail API with OAuth 2.0
Scheduler node-cron

2. Module Breakdown

Module 1: Gmail Module

Responsibility: Email integration and resume fetching

Functions:

  • Authenticate with Gmail API using OAuth 2.0
  • Fetch emails with attachments based on filters (date, keywords, labels)
  • Download resume attachments (PDF, DOCX)
  • Track last fetch timestamp for incremental processing

Key Operations:

  • fetchNewEmails(afterDate, filters) → Returns array of email objects with attachments
  • downloadAttachment(messageId, attachmentId) → Returns file buffer
  • getLastFetchTime(jobId) → Returns timestamp of last successful fetch

External Dependencies: Gmail API, Google OAuth 2.0


Module 2: Resume Parser Module

Responsibility: Extract structured data from resume text

Parsing Strategy: Rule-based NLP with regex patterns

Name Extraction: First line heuristics, capitalization patterns
Email Extraction: Regex pattern [a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}
Phone Extraction: Regex for various formats (+1-XXX-XXX-XXXX, (XXX) XXX-XXXX)
Skills Extraction: Keyword matching against predefined skill database
Experience Parsing: Section detection (keywords: "experience", "work history") + date parsing
Education Parsing: Section detection + degree/institution extraction

Functions:

  • extractText(fileBuffer, format) → Returns plain text from PDF/DOCX
  • parseResume(resumeText) → Returns structured JSON object
  • validateParsedData(data) → Returns boolean + error messages
  • normalizeSkills(skillArray) → Returns deduplicated, lowercase skills

Output Format (JSON):

{
  "name": "John Doe",
  "email": "john@example.com",
  "phone": "+1-555-0123",
  "skills": ["javascript", "python", "sql"],
  "experience": [
    {
      "title": "Software Engineer",
      "company": "Tech Corp",
      "duration": "2020-2023",
      "description": "Built REST APIs..."
    }
  ],
  "education": [
    {
      "degree": "B.S. Computer Science",
      "institution": "MIT",
      "year": 2018
    }
  ],
  "total_experience_years": 5
}

Alternative AI Approach (Optional Enhancement)

Instead of rule-based parsing, use GPT-4o-mini with structured JSON output. The flow would be: extract plain text -> send it to GPT-4o-mini with a schema-enforced prompt -> receive structured JSON (name, email, phone, skills, experience, education, total years).

Pros:

  • Much higher accuracy across different resume formats and layouts
  • Handles inconsistent structures, missing sections, and varied wording
  • Reduces need for complex regex/heuristics

Cons:

  • Incur API usage costs
  • Slightly higher latency (~2–5 seconds per resume, depending on length)
  • Requires API reliability and internet access

Module 3: Embedding Module

Responsibility: Generate vector embeddings for semantic search

Embedding Strategy:

  • Use OpenAI text-embedding-3-small model (1536 dimensions)
  • Embed full resume text (truncated to 8000 chars if needed)
  • Embed JD text once per job creation
  • Store vectors in PostgreSQL pgvector columns

Functions:

  • generateEmbedding(text) -> Returns 1536-dimensional float array
  • batchGenerateEmbeddings(textArray) -> Returns array of vectors (for efficiency)
  • calculateCosineSimilarity(vec1, vec2) -> Returns similarity score (0-1)

Caching Strategy:

  • Cache JD embeddings in memory (job lifecycle)
  • Resume embeddings stored permanently in database

Module 4: Database Module

Responsibility: Data persistence and vector operations

Core Operations:

  • CRUD for all entities (users, jobs, candidates, communications)
  • Vector similarity search using pgvector
  • Transaction management for atomic operations
  • Query optimization with proper indexing

Key Functions:

  • storeCandidate(candidateData) -> Returns candidateId
  • findSimilarCandidates(jdEmbedding, topK) -> Returns top-K candidate IDs by cosine similarity
  • updateCandidateScore(candidateId, score) -> Updates match score
  • getCandidatesByJob(jobId, filters) -> Returns paginated candidate list

Vector Search Query (Conceptual):

  • Use pgvector's <=> operator for cosine distance
  • Order by 1 - (jd_embedding <=> resume_embedding) for similarity
  • Apply filters (status, score range) after vector search

Module 5: Job Description Module

Responsibility: JD lifecycle management and candidate retrieval

Functions:

  • createJob(jobData) -> Validates input, generates embedding, stores in DB
  • getActiveJobs() -> Returns list of jobs with status='active'
  • findTopCandidates(jobId, topK) -> Queries DB for top-K similar candidates
  • closeJob(jobId) -> Updates status, stops scheduled tasks

JD Processing Flow:

  • HR submits JD text via UI
  • Validate JD (length, required fields)
  • Generate embedding for JD text
  • Store job with embedding in database
  • Schedule resume ingestion task (cron)
  • Return job ID to frontend

Embedding Generation:

  • Extract key requirements from JD text
  • Generate single embedding vector (1536-dim)
  • Store in jobs.jd_embedding column

Module 6: Ranking Module

Responsibility: AI-powered re-ranking of top candidates

Two-Stage Ranking:

Stage 1: Vector Similarity (Fast)

  • Use pgvector to retrieve top-50 candidates by cosine similarity
  • Initial filter based on semantic matching
  • Execution time: <100ms

Stage 2: AI Re-Ranking (Precise)

  • Use GPT-4o-mini to re-rank top-50 -> top-10
  • Provide full context: JD text + parsed resume data
  • AI considers: skill match, experience relevance, education fit
  • Execution time: 2-3 seconds

Functions:

  • getTopKSimilar(jobId, k) -> Returns top-K candidates from vector search
  • reRankWithAI(candidates, jdText) -> Returns re-ranked list with scores and reasoning
  • calculateFinalScore(candidate, job) -> Combines vector similarity + AI score

AI Re-Ranking Prompt Structure:
System: You are an expert technical recruiter evaluating candidate fit.

Input:

  • Job Description: [JD text]
  • Candidates: [Array of parsed resume data]

Task:
Rank these candidates from best to worst fit. For each, provide:

  1. Rank position (1-N)
  2. Fit score (0-1)
  3. Brief reasoning (2-3 sentences)

Consider:

  • Skill alignment with required skills
  • Experience level match
  • Domain relevance
  • Education requirements

Output Format: JSON array ordered by rank

Scoring Algorithm:

  • Initial Vector Similarity: S_vec (from pgvector)
  • AI Re-Ranking Score: S_ai (from GPT-4o-mini)

Final Score: Final Score = 0.4 × S_vec + 0.6 × S_ai

Reasoning: Vector similarity provides broad semantic match, AI re-ranking adds nuanced understanding of requirements.


Module 7: Application Controller

Responsibility: Orchestrate workflow across modules

Core Workflows:

Resume Processing Workflow

  • Trigger: Cron job (every 4 hours)
  • Steps: Fetch emails → Extract text → Parse → Embed → Store

Candidate Ranking Workflow

  • Trigger: HR requests candidates for a job
  • Steps: Vector search → AI re-rank → Return ranked list

Deduplication Workflow

  • Trigger: Before storing new candidate
  • Steps: Hash resume text → Check DB → Skip if exists

Functions:

  • processNewResumes(jobId) -> Orchestrates resume processing
  • getRankedCandidates(jobId) -> Orchestrates ranking workflow
  • handleDuplication(resumeHash) -> Checks and logs duplicates

Error Handling:

  • Retry logic for API failures (3 attempts with exponential backoff)
  • Log all errors with context (job ID, resume ID, error message)
  • Continue processing remaining resumes on individual failures

Module 8: HR UI Module

Responsibility: Provide API endpoints for frontend dashboard

API Endpoints: Detailed in Section 3

Dashboard Views:

  • Job listing with candidate counts
  • Candidate pipeline (New, Contacted, Replied, Interviewed)
  • Candidate detail with parsed data and match score
  • Communication history per candidate

3. API Design

3.1 Authentication Endpoints

Method Endpoint Request Body Response Description
POST /api/auth/login {email, password} {user, token} User login, returns JWT
POST /api/auth/logout {token} {success: true} Invalidate token
GET /api/auth/me Headers: Authorization: Bearer {user} Get current user info

Authentication Flow:

  • User submits credentials
  • Server validates against database (bcrypt password comparison)
  • Generate JWT with 24-hour expiry
  • Return token to client
  • Client includes token in all subsequent requests

3.2 Job Management Endpoints

Method Endpoint Request Body Response Description
POST /api/jobs {title, jd_text, start_date, end_date, min_score} {job_id, jd_embedding, ...} Create job, generates embedding
GET /api/jobs Query: ?status=active&page=1&limit=20 {jobs[], total, page} List jobs with pagination
GET /api/jobs/:id - {job, candidate_count} Get job details
PUT /api/jobs/:id {title?, jd_text?, min_score?} {success: true} Update job (re-generates embedding if JD changed)
DELETE /api/jobs/:id - {success: true} Close job, stop cron tasks

Request Example (POST /api/jobs):

{
  "title": "Senior Full Stack Developer",
  "jd_text": "We are seeking an experienced full stack developer with 5+ years of experience in React, Node.js, and PostgreSQL. The ideal candidate will have strong problem-solving skills and experience with microservices architecture...",
  "start_date": "2025-10-01",
  "end_date": "2025-12-31",
  "min_score": 0.70
}

Response Example:

{
  "job_id": "uuid-123",
  "title": "Senior Full Stack Developer",
  "jd_embedding": [0.123, -0.456, ...], // 1536 dimensions
  "status": "active",
  "created_at": "2025-10-02T10:00:00Z"
}

3.3 Candidate Retrieval Endpoints

Method Endpoint Request Body Response Description
GET /api/jobs/:jobId/candidates Query: ?status=new&sort=score&page=1&limit=20 {candidates[], total} Get ranked candidates for job
GET /api/candidates/:id - {candidate, parsed_data, communications[]} Get candidate details
PUT /api/candidates/:id/status {status} {success: true} Update candidate status
POST /api/candidates/:id/notes {note_text} {note_id} Add HR notes

Request Example (GET /api/jobs/:jobId/candidates):

{
  "job_id": "uuid-123",
  "status": "new",
  "sort": "score",
  "page": 1,
  "limit": 20
}

Response Example:

{
  "candidates": [
    {
      "candidate_id": "uuid-456",
      "full_name": "John Doe",
      "email": "john@example.com",
      "match_score": 0.87,
      "status": "new",
      "parsed_data": {
        "skills": ["javascript", "react", "node.js", "postgresql"],
        "total_experience_years": 6
      },
      "ai_reasoning": "Strong technical alignment with 5/6 required skills. Experience exceeds minimum requirement.",
      "created_at": "2025-10-02T12:30:00Z"
    }
  ],
  "total": 45,
  "page": 1,
  "limit": 20
}

3.4 Resume Processing Endpoints (Internal)

Method Endpoint Request Body Response Description
POST /api/internal/process-resumes/:jobId - {processed_count, errors[]} Manually trigger resume processing (admin only)
GET /api/internal/processing-logs/:jobId Query: ?status=success&page=1 {logs[], total} View processing logs

3.5 Dashboard & Analytics Endpoints

Method Endpoint Request Body Response Description
GET /api/dashboard/stats/:jobId - {total, by_status{}, avg_score} Get job pipeline metrics
GET /api/dashboard/timeline/:jobId Query: ?start_date&end_date {timeline_data[]} Candidate activity over time
GET /api/export/candidates/:jobId Query: ?format=csv CSV file Export candidates

Response Example (GET /api/dashboard/stats/:jobId):

{
  "total_candidates": 120,
  "by_status": {
    "new": 45,
    "contacted": 50,
    "replied": 20,
    "interviewed": 3,
    "rejected": 2
  },
  "avg_match_score": 0.74,
  "top_skills": ["javascript", "python", "react"],
  "last_updated": "2025-10-02T14:00:00Z"
}

4. Database Design

4.1 Entity Relationship Diagram (ERD)

4.2 Table Specifications

Table: users

Purpose: Store HR user accounts

Column Type Constraints Description
user_id UUID PRIMARY KEY, DEFAULT gen_random_uuid() Unique user identifier
email VARCHAR(255) UNIQUE, NOT NULL User email (login credential)
password_hash VARCHAR(255) NOT NULL bcrypt hashed password
full_name VARCHAR(255) NOT NULL User's full name
role VARCHAR(50) NOT NULL, DEFAULT 'recruiter' User role (admin, recruiter)
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP Account creation time
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP Last update time

Indexes:

  • idx_users_email on email (for login queries)

Table: jobs

Purpose: Store job descriptions and automation settings

Column Type Constraints Description
job_id UUID PRIMARY KEY, DEFAULT gen_random_uuid() Unique job identifier
created_by UUID FOREIGN KEY REFERENCES users(user_id) Job creator
title VARCHAR(255) NOT NULL Job title
jd_text TEXT NOT NULL Full job description
jd_embedding vector(1536) NOT NULL Semantic embedding of JD
start_date DATE NOT NULL Job posting start date
end_date DATE NOT NULL Job posting end date
status VARCHAR(50) DEFAULT 'active', CHECK (status IN ('active', 'paused', 'closed')) Job status
min_score DECIMAL(3,2) DEFAULT 0.70, CHECK (min_score BETWEEN 0 AND 1) Minimum match score for contact
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP Job creation time
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP Last update time

Indexes:

  • idx_jobs_status on status (filter active jobs)
  • idx_jobs_dates on (start_date, end_date) (date range queries)
  • idx_jd_embedding on jd_embedding USING hnsw (vector_cosine_ops) (fast similarity search)

Constraints:

  • CHECK (end_date >= start_date) (valid date range)

Table: candidates

Purpose: Store candidate resumes and parsed data

Column Type Constraints Description
candidate_id UUID PRIMARY KEY, DEFAULT gen_random_uuid() Unique candidate identifier
job_id UUID FOREIGN KEY REFERENCES jobs(job_id) ON DELETE CASCADE Associated job
full_name VARCHAR(255) NOT NULL Candidate's full name
email VARCHAR(255) NOT NULL Candidate's email
phone VARCHAR(50) Candidate's phone number
resume_text TEXT NOT NULL Full resume text (plain)
resume_embedding vector(1536) NOT NULL Semantic embedding of resume
parsed_data JSONB Structured resume data (skills, experience, education)
match_score DECIMAL(5,4) Final match score (0-1)
ai_reasoning TEXT AI explanation for ranking
status VARCHAR(50) DEFAULT 'new', CHECK (status IN ('new', 'contacted', 'replied', 'interviewed', 'rejected', 'hired')) Candidate status
resume_hash VARCHAR(64) UNIQUE, NOT NULL SHA-256 hash for deduplication
source VARCHAR(50) DEFAULT 'email' Source of resume (email, upload)
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP Resume received time
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP Last update time

Indexes:

  • idx_candidates_job on job_id (fetch candidates by job)
  • idx_candidates_job_score on (job_id, match_score DESC) (ranked retrieval)
  • idx_candidates_status on status (filter by status)
  • idx_candidates_email on email (find by email)
  • idx_candidates_hash on resume_hash (deduplication check)
  • idx_resume_embedding on resume_embedding USING hnsw (vector_cosine_ops) (vector search)

JSONB Structure (parsed_data):

{
  "skills": ["javascript", "python", "react"],
  "experience": [
    {
      "title": "Software Engineer",
      "company": "Tech Corp",
      "duration": "2020-2023",
      "description": "Built microservices..."
    }
  ],
  "education": [
    {
      "degree": "B.S. Computer Science",
      "institution": "MIT",
      "year": 2018
    }
  ],
  "total_experience_years": 5
}

4.2 Table Specifications (continued)

Table: communication

Purpose: Log all email communications

Column Type Constraints Description
comm_id UUID PRIMARY KEY, DEFAULT gen_random_uuid() Unique communication ID
candidate_id UUID FOREIGN KEY REFERENCES candidates(candidate_id) ON DELETE CASCADE Associated candidate
type VARCHAR(50) NOT NULL, CHECK (type IN ('outreach', 'follow_up', 'reply', 'manual')) Communication type
subject VARCHAR(500) Email subject
body TEXT Email body
sent_at TIMESTAMP Email sent time
status VARCHAR(50) DEFAULT 'sent' Delivery status
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP Log creation time

Indexes:

  • idx_comm_candidate on candidate_id (fetch communication history)
  • idx_comm_type on type (filter by type)

Table: processing_logs

Purpose: Audit trail for resume processing

Column Type Constraints Description
log_id UUID PRIMARY KEY, DEFAULT gen_random_uuid() Unique log ID
job_id UUID FOREIGN KEY REFERENCES jobs(job_id) ON DELETE CASCADE Associated job
resume_hash VARCHAR(64) NOT NULL Hash of processed resume
status VARCHAR(50) NOT NULL, CHECK (status IN ('success', 'failed', 'duplicate', 'skipped')) Processing outcome
error_message TEXT Error details if failed
processing_time_ms INTEGER Time taken to process
processed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP Processing time

Indexes:

  • idx_logs_job on job_id (fetch logs by job)
  • idx_logs_hash on resume_hash (deduplication check)
  • idx_logs_status on status (filter by status)

Purpose of Deduplication:

  • Prevent reprocessing same resume across multiple jobs
  • Track which resumes have been processed
  • Maintain audit trail

Table: scheduled_tasks

Purpose: Manage cron jobs and scheduled tasks

Column Type Constraints Description
task_id UUID PRIMARY KEY, DEFAULT gen_random_uuid() Unique task ID
job_id UUID FOREIGN KEY REFERENCES jobs(job_id) ON DELETE CASCADE Associated job
task_type VARCHAR(50) NOT NULL, CHECK (task_type IN ('ingestion', 'ranking', 'response_tracking')) Type of task
scheduled_time TIMESTAMP NOT NULL When task should execute
status VARCHAR(50) DEFAULT 'pending', CHECK (status IN ('pending', 'executing', 'completed', 'failed')) Task status
executed_at TIMESTAMP Actual execution time
error_message TEXT Error details if failed

Indexes:

  • idx_tasks_job on job_id (fetch tasks by job)
  • idx_tasks_status on status (find pending tasks)
  • idx_tasks_scheduled on scheduled_time (time-based queries)

4.3 Database Relationships

Parent Table Child Table Relationship Type Foreign Key On Delete
users jobs One-to-Many jobs.created_by → users.user_id SET NULL
jobs candidates One-to-Many candidates.job_id → jobs.job_id CASCADE
candidates communication One-to-Many communication.candidate_id → candidates.candidate_id CASCADE
jobs processing_logs One-to-Many processing_logs.job_id → jobs.job_id CASCADE
jobs scheduled_tasks One-to-Many scheduled_tasks.job_id → jobs.job_id CASCADE

Cascade Deletion Logic:

  • When a job is deleted, all associated candidates, logs, and tasks are deleted
  • When a candidate is deleted, all associated communications are deleted
  • When a user is deleted, their jobs remain but created_by is set to NULL

4.4 pgvector Configuration

Extension Setup:

CREATE EXTENSION IF NOT EXISTS vector;

5. Business Workflow

PHASE 1: JOB CREATION

  • HR User opens Dashboard → Create Job Form
    • Input: Title, JD Text, Dates, Min Score
  • POST /api/jobs
    • Validate input (dates, text length)
    • Generate JD embedding (OpenAI API)
    • Store in database
    • Schedule cron job (resume ingestion every 4 hours)
  • Job Created ✅
    • Status: Active

PHASE 2: RESUME INGESTION (Automated - Every 4 hours)

  • Cron Trigger → Application Controller
  • Gmail Module:
    • Fetch emails with attachments
    • Filter: after last fetch, keywords (resume, application)
    • Download attachments (PDF/DOCX)
  • Resume Parser Module:
    • Extract text from PDF/DOCX
    • Generate SHA-256 hash
    • Check deduplication (processing_logs)
    • If duplicate → skip & log as 'duplicate'
    • If new → continue processing
  • Parse Resume (Rule-based NLP):
    • Extract: Name, Email, Phone
    • Extract: Skills
    • Extract: Experience
    • Extract: Education
    • Calculate total experience years
  • Structured JSON created: {name, email, skills[], experience[], education[], total_years}
  • Embedding Module:
    • Generate embedding (1536-dim vector)
  • Database Module:
    • Store candidate record:
      • parsed_data (JSONB)
      • resume_embedding
      • resume_hash
      • status: 'new'
    • Log processing in processing_logs:
      • status: 'success'
      • processing_time_ms
  • Candidate Stored ✅

PHASE 3: CANDIDATE RANKING (On-Demand)

  • HR User → Dashboard → View Job → Click "View Candidates"
  • GET /api/jobs/:jobId/candidates
  • Application Controller:
    • Ranking Module (Stage 1: Vector Search):
      • Fetch job.jd_embedding
      • Query top-50 candidates by cosine similarity
    • Ranking Module (Stage 2: AI Re-Ranking):
      • Prepare AI context (JD text + top-50 candidates)
      • Call GPT-4o-mini:
        • Rank candidates (1-50)
        • Fit score (0-1)
        • Brief reasoning
      • Calculate final_score = 0.4 × vector_sim + 0.6 × AI_score
      • Update candidates.match_score and ai_reasoning in DB
  • Return Ranked Candidates → Frontend
  • HR Dashboard displays Top-10 candidates:
    • Name, Email, Match Score
    • AI reasoning
    • Parsed Data (skills, experience)
    • Actions: View Details, Contact, Reject

PHASE 4: CANDIDATE INTERACTION (Manual)

  • HR selects candidate → Actions:
    • View full resume
    • Update status ('contacted', 'replied', 'interviewed')
    • Add notes (stored in notes table)
    • Send email (logged in communication table)
  • Status updated in database
  • Dashboard reflects new status

PHASE 5: JOB CLOSURE

  • HR closes job → DELETE /api/jobs/:jobId
    • Update job.status = 'closed'
    • Stop all scheduled tasks
    • Archive candidates (records remain in DB)
  • Job Closed ✅

6. System Architecture

7. Sequence Diagram

8. Flow Diagram