Skip to content

Commit 0c49af0

Browse files
committed
added parsing cv and auto fill
1 parent 9978456 commit 0c49af0

4 files changed

Lines changed: 383 additions & 186 deletions

File tree

nextstep-backend/src/controllers/resume_controller.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { Request, Response } from 'express';
22
import { config } from '../config/config';
33
import fs from 'fs';
44
import path from 'path';
5-
import { scoreResume, streamScoreResume, getResumeTemplates, generateImprovedResume } from '../services/resume_service';
5+
import { scoreResume, streamScoreResume, getResumeTemplates, generateImprovedResume, parseResumeFields } from '../services/resume_service';
66
import multer from 'multer';
77
import { CustomRequest } from "types/customRequest";
88
import { handleError } from "../utils/handle_error";
@@ -93,4 +93,18 @@ const generateResume = async (req: Request, res: Response) => {
9393
}
9494
};
9595

96-
export default { getResumeScore, getStreamResumeScore, getTemplates, generateResume };
96+
97+
const parseResume = async (req: Request, res: Response) => {
98+
try {
99+
if (!req.file) {
100+
return res.status(400).json({ error: 'No resume file uploaded' });
101+
}
102+
const parsed = await parseResumeFields(req.file.buffer, req.file.originalname);
103+
return res.status(200).json(parsed);
104+
} catch (err: any) {
105+
console.error('Error parsing resume:', err);
106+
return handleError(err, res);
107+
}
108+
};
109+
110+
export default { parseResume, getResumeScore, getStreamResumeScore, getTemplates, generateResume };

nextstep-backend/src/routes/resume_routes.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import express, { Request, Response } from 'express';
22
import Resume from '../controllers/resume_controller';
33
import { CustomRequest } from "types/customRequest";
4+
import multer from 'multer';
5+
6+
const upload = multer();
47

58
const router = express.Router();
69

@@ -12,4 +15,6 @@ router.get('/templates', Resume.getTemplates);
1215

1316
router.post('/generate', Resume.generateResume);
1417

18+
router.post('/parseResume', upload.single('resume'), Resume.parseResume);
19+
1520
export default router;

nextstep-backend/src/services/resume_service.ts

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,14 @@ import pdfParse from 'pdf-parse';
88
import AdmZip from 'adm-zip';
99
import { DOMParser, XMLSerializer } from 'xmldom';
1010

11+
export interface ParsedResume {
12+
aboutMe: string;
13+
skills: string[];
14+
roleMatch: string;
15+
experience:string[];
16+
education?: string[];
17+
}
18+
1119
const SYSTEM_TEMPLATE = `You are a very experienced ATS (Application Tracking System) bot with a deep understanding named Bob the Resume builder.
1220
You will review resumes with or without job descriptions.
1321
You are an expert in resume evaluation and provide constructive feedback with dynamic evaluation.
@@ -386,4 +394,51 @@ Rules:
386394
}
387395
};
388396

389-
export { scoreResume, streamScoreResume, getResumeTemplates, generateImprovedResume };
397+
398+
/**
399+
* Extracts raw text from the uploaded resume buffer,
400+
* prompts the AI to return { aboutMe, skills[], roleMatch, experience[] } as JSON.
401+
*/
402+
const parseResumeFields = async (
403+
fileBuffer: Buffer,
404+
originalName: string
405+
): Promise<ParsedResume> => {
406+
// 1) Extract text
407+
const ext = path.extname(originalName).toLowerCase();
408+
let text: string;
409+
if (ext === '.pdf') {
410+
const data = await pdfParse(fileBuffer);
411+
text = data.text;
412+
} else {
413+
// mammoth supports buffer input
414+
const { value } = await mammoth.extractRawText({ buffer: fileBuffer });
415+
text = value;
416+
}
417+
418+
// 2) Build the extraction prompt
419+
const prompt = `
420+
Extract from this resume the following fields as JSON:
421+
• "aboutMe": a 1–2 sentence self-summary.
422+
• "skills": an array of technical skills.
423+
• "roleMatch": one-sentence best-fit role suggestion.
424+
• "experience": an array of 3–5 bullet points of key achievements.
425+
426+
Resume text:
427+
---
428+
${text}
429+
---
430+
Respond with a single JSON object and nothing else. The json object should begin directly with parentheses and have the following structure: {"a": "value", "b": "value", ...}
431+
`;
432+
433+
// 3) Call your Chat AI
434+
const aiResponse = await chatWithAI(
435+
SYSTEM_TEMPLATE, // you can reuse your existing SYSTEM_TEMPLATE or define a new one
436+
[prompt]
437+
);
438+
439+
// 4) Parse & return
440+
const parsed = JSON.parse(aiResponse.trim().replace("```json", "").replace("```", "")) as ParsedResume;
441+
return parsed;
442+
};
443+
444+
export { scoreResume, streamScoreResume, getResumeTemplates, generateImprovedResume, parseResumeFields };

0 commit comments

Comments
 (0)