Technical Document (LLD/HLD)
HireGenius- AI-Powered Recruitment Automation System
📘 Table of Contents
| # | Section |
|---|---|
| 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 attachmentsdownloadAttachment(messageId, attachmentId)→ Returns file buffergetLastFetchTime(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/DOCXparseResume(resumeText)→ Returns structured JSON objectvalidateParsedData(data)→ Returns boolean + error messagesnormalizeSkills(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 arraybatchGenerateEmbeddings(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 candidateIdfindSimilarCandidates(jdEmbedding, topK)-> Returns top-K candidate IDs by cosine similarityupdateCandidateScore(candidateId, score)-> Updates match scoregetCandidatesByJob(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 DBgetActiveJobs()-> Returns list of jobs with status='active'findTopCandidates(jobId, topK)-> Queries DB for top-K similar candidatescloseJob(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_embeddingcolumn
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 searchreRankWithAI(candidates, jdText)-> Returns re-ranked list with scores and reasoningcalculateFinalScore(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:
- Rank position (1-N)
- Fit score (0-1)
- 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 processinggetRankedCandidates(jobId)-> Orchestrates ranking workflowhandleDuplication(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 |
| VARCHAR(255) | UNIQUE, NOT NULL | User email (login credential) | |
| refresh_token | VARCHAR(255) | Refresh token for session | |
| access_token | VARCHAR(255) | Access token for session | |
| 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_emailonemail(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 |
| created_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP | Job creation time |
| updated_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP | Last update time |
Indexes:
idx_jobs_statusonstatus(filter active jobs)idx_jobs_dateson(start_date, end_date)(date range queries)idx_jd_embeddingonjd_embedding USING hnsw (vector_cosine_ops)(fast similarity search)
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 |
| full_name | VARCHAR(255) | NOT NULL | Candidate's full name |
| 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) | |
| 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_emailonemail(find by email)idx_candidates_hashonresume_hash(deduplication check)idx_resume_embeddingonresume_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 }
Table: job_candidate_matches
Purpose: Store matches between jobs and candidates (many-to-many relationship)
| Column | Type | Constraints | Description |
|---|---|---|---|
| match_id | UUID | PRIMARY KEY, DEFAULT gen_random_uuid() | Unique match identifier |
| job_id | UUID | FOREIGN KEY REFERENCES jobs(job_id) | Associated job |
| candidate_id | UUID | FOREIGN KEY REFERENCES candidates(candidate_id) | Associated candidate |
| match_score | DECIMAL(5,4) | AI-generated match score (0-1) | |
| ai_reasoning | TEXT | AI explanation for match | |
| status | VARCHAR(50) | DEFAULT 'new', CHECK (status IN ('new','contacted','replied','interviewed','rejected','hired')) | Candidate-job status |
| created_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP | Match creation time |
| updated_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP | Last update time |
Indexes:
idx_matches_jobonjob_ididx_matches_candidateoncandidate_ididx_matches_scoreon(job_id, match_score DESC)(ranked retrieval)
Table: communication
Purpose: Log all email communications with candidates
| 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_candidateoncandidate_id(fetch communication history)idx_comm_typeontype(filter by type)
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 | job_candidate_matches | One-to-Many | job_candidate_matches.job_id → jobs.job_id | CASCADE |
| candidates | job_candidate_matches | One-to-Many | job_candidate_matches.candidate_id → candidates.candidate_id | CASCADE |
| candidates | communication | One-to-Many | communication.candidate_id → candidates.candidate_id | CASCADE |
Cascade Deletion Logic:
- When a job is deleted, all associated job-candidate matches are deleted
- When a candidate is deleted, all associated job-candidate matches and communications are deleted
- When a user is deleted, their jobs remain but
created_byis 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
- Store candidate record:
- 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
- Ranking Module (Stage 1: Vector Search):
- 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 ✅
)/./image-1759931553297.png)
)/./image-1759857600801.drawio.png)
)/./image-1759857739651.-2025-10-07-172157.png)
)/./image-1759914896125.drawio.png)