The Interview Report feature uses Google’s Gemini AI to analyze job descriptions against user profiles (resume or self-description) and generate comprehensive interview preparation reports. Each report includes a match score, tailored interview questions with model answers, skill gap analysis, and a day-by-day preparation roadmap.
The system supports PDF and DOCX files with intelligent format detection:
interview.controller.js:40-84
let resumeText = ""if (req.file?.buffer) { const mime = (req.file.mimetype || "").toLowerCase() const name = (req.file.originalname || "").toLowerCase() // Prefer "magic bytes" over mimetype/extension const b = req.file.buffer const isPdfMagic = b.length >= 4 && b[0] === 0x25 && b[1] === 0x50 && b[2] === 0x44 && b[3] === 0x46 // %PDF const isZipMagic = b.length >= 4 && b[0] === 0x50 && b[1] === 0x4b && b[2] === 0x03 && b[3] === 0x04 // PK.. const isPdf = isPdfMagic || mime === "application/pdf" || name.endsWith(".pdf") const isDocx = isZipMagic || mime === "application/vnd.openxmlformats-officedocument.wordprocessingml.document" || name.endsWith(".docx") try { if (isPdf) { const parsed = await pdfParse(req.file.buffer) resumeText = parsed.text || "" } else if (isDocx) { const result = await mammoth.extractRawText({ buffer: req.file.buffer }) resumeText = result.value || "" } else { return res.status(400).json({ message: "Unsupported resume file type. Please upload a PDF or DOCX." }) } } catch (err) { // If local parsing fails (scanned PDF / encrypted / corrupted), // fall back to letting Gemini read the original file directly. if (aiMimeType) { resumeFileForAi = { mimeType: aiMimeType, data: req.file.buffer.toString("base64") } resumeText = "" } }}
The system uses “magic bytes” detection for reliable format identification, and can fall back to sending the raw file to Gemini AI if text extraction fails (e.g., scanned PDFs).
The system uses Zod schemas to enforce structured AI responses:
ai.service.js:11-33
const interviewReportSchema = z.object({ matchScore: z.number().describe("A score between 0 and 100 indicating how well the candidate's profile matches the job describe"), technicalQuestions: z.array(z.object({ question: z.string().describe("The technical question can be asked in the interview"), intention: z.string().describe("The intention of interviewer behind asking this question"), answer: z.string().describe("How to answer this question, what points to cover, what approach to take etc.") })).describe("Technical questions that can be asked in the interview along with their intention and how to answer them"), behavioralQuestions: z.array(z.object({ question: z.string().describe("The technical question can be asked in the interview"), intention: z.string().describe("The intention of interviewer behind asking this question"), answer: z.string().describe("How to answer this question, what points to cover, what approach to take etc.") })).describe("Behavioral questions that can be asked in the interview along with their intention and how to answer them"), skillGaps: z.array(z.object({ skill: z.string().describe("The skill which the candidate is lacking"), severity: z.enum([ "low", "medium", "high" ]).describe("The severity of this skill gap") })).describe("List of skill gaps in the candidate's profile along with their severity"), preparationPlan: z.array(z.object({ day: z.number().describe("The day number in the preparation plan, starting from 1"), focus: z.string().describe("The main focus of this day in the preparation plan"), tasks: z.array(z.string()).describe("List of tasks to be done on this day") })).describe("A day-wise preparation plan for the candidate"), title: z.string().describe("The title of the job for which the interview report is generated"),})
Users can view all their interview reports on the home page:API Endpoint:GET /api/interview/Response: Returns summary data (excludes full question/plan details for performance)