Step 8: Deployment (Optional)
The final step in our Resume Parser AI project is to deploy our model as a web application. This will allow recruiters to easily upload resumes and get instant analysis and scoring.
Why Deploy Your Model?
Deploying your model transforms it from a script running on your computer to a usable application that others can access. Benefits include:
- Making your model accessible to non-technical users
- Providing a user-friendly interface for resume analysis
- Enabling multiple recruiters to use the system simultaneously
- Creating a complete end-to-end solution
Building a Simple Web App with Flask
We'll use Flask, a lightweight web framework for Python, to create our application:
# Install required packages
# pip install flask pandas scikit-learn nltk spacy
# Create a new file called app.py
import os
import pickle
import pandas as pd
from flask import Flask, request, render_template, jsonify
import nltk
import spacy
from werkzeug.utils import secure_filename
# Download necessary NLTK data (if not already downloaded)
nltk.download('punkt')
nltk.download('stopwords')
nltk.download('wordnet')
# Load spaCy model
nlp = spacy.load("en_core_web_sm")
# Create Flask app
app = Flask(__name__)
app.config['UPLOAD_FOLDER'] = 'uploads/'
app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024 # 16MB max upload size
# Create uploads folder if it doesn't exist
os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)
# Load our preprocessing functions
# For simplicity, we'll redefine them here
# In a real application, you would import them from your modules
def preprocess_resume(resume_text):
# Simplified preprocessing function
# In a real app, you would include all the preprocessing steps from Step 2
tokens = nltk.word_tokenize(resume_text.lower())
return ' '.join(tokens)
def extract_skills(text, skill_list):
text_lower = text.lower()
found_skills = []
for skill in skill_list:
if skill.lower() in text_lower:
found_skills.append(skill)
return found_skills
def extract_experience(text):
# Simplified experience extraction
# In a real app, you would use the more sophisticated function from Step 5
import re
years = []
pattern = r'(\d+)\s+years?\s+(?:of\s+)?experience'
matches = re.finditer(pattern, text.lower())
for match in matches:
years.append(int(match.group(1)))
return max(years) if years else 0
def extract_education(text):
# Simplified education extraction
education_levels = {
'phd': ['phd', 'ph.d', 'doctor of philosophy'],
'masters': ['masters', 'master of', 'ms ', 'msc', 'm.sc', 'ma ', 'm.a'],
'bachelors': ['bachelor', 'bs ', 'b.s', 'ba ', 'b.a', 'undergraduate'],
'associate': ['associate', 'a.s', 'a.a'],
'high school': ['high school', 'secondary school']
}
text_lower = text.lower()
found_levels = []
for level, keywords in education_levels.items():
for keyword in keywords:
if keyword in text_lower:
found_levels.append(level)
break
return found_levels
# Load our scoring model
try:
with open('models/optimized_scoring_model.pkl', 'rb') as f:
scoring_model = pickle.load(f)
score_resume = scoring_model['score_resume']
job_requirements = scoring_model['job_requirements']
best_weights = scoring_model['best_weights']
except FileNotFoundError:
# Fallback if model file doesn't exist
print("Model file not found. Using default scoring function.")
def score_resume(resume_row, job_title, requirements, weights):
# Simplified scoring function
score = 0
# Score based on skills match
req_skills = requirements['required_skills']
candidate_skills = resume_row['skills']
skill_match = sum(1 for skill in req_skills if skill in candidate_skills) / len(req_skills)
score += skill_match * 5 # Up to 5 points for skills
# Score based on experience
min_exp = requirements['min_experience']
candidate_exp = resume_row['years_experience']
if candidate_exp >= min_exp:
score += min(3, (candidate_exp - min_exp) * 0.5 + 2) # Up to 3 points for experience
else:
score += max(0, 2 * candidate_exp / min_exp)
# Score based on education
edu_levels = {'high school': 1, 'associate': 2, 'bachelors': 3, 'masters': 4, 'phd': 5}
req_edu = requirements['education_level']
req_edu_level = edu_levels.get(req_edu.lower(), 0)
candidate_edu = resume_row['education']
if candidate_edu:
candidate_edu_level = max(edu_levels.get(edu.lower(), 0) for edu in candidate_edu)
if candidate_edu_level >= req_edu_level:
score += 2 # Up to 2 points for education
else:
score += 1 * candidate_edu_level / req_edu_level
return min(10, score)
# Define default job requirements
job_requirements = {
'Data Scientist': {
'required_skills': ['Python', 'Machine Learning', 'SQL', 'Data Analysis'],
'preferred_skills': ['Deep Learning', 'AI', 'Statistics', 'Data Visualization'],
'min_experience': 2,
'education_level': 'masters'
},
'Software Engineer': {
'required_skills': ['Java', 'JavaScript', 'SQL', 'HTML', 'CSS'],
'preferred_skills': ['React', 'Node.js', 'Docker', 'AWS'],
'min_experience': 3,
'education_level': 'bachelors'
}
}
best_weights = {
'skills': 0.4,
'experience': 0.3,
'education': 0.2,
'relevance': 0.05,
'keywords': 0.05
}
# Define a common skill list
common_skills = [
'Python', 'Java', 'JavaScript', 'C++', 'C#', 'SQL', 'HTML', 'CSS',
'React', 'Angular', 'Vue', 'Node.js', 'Django', 'Flask',
'Machine Learning', 'Data Analysis', 'Data Science', 'AI',
'Project Management', 'Agile', 'Scrum', 'Kanban',
'Microsoft Office', 'Excel', 'PowerPoint', 'Word',
'Photoshop', 'Illustrator', 'InDesign', 'Figma',
'Communication', 'Leadership', 'Teamwork', 'Problem Solving'
]
# Define routes
@app.route('/')
def home():
return render_template('index.html', job_titles=list(job_requirements.keys()))
@app.route('/upload', methods=['POST'])
def upload_resume():
if 'resume' not in request.files:
return jsonify({'error': 'No file part'})
file = request.files['resume']
if file.filename == '':
return jsonify({'error': 'No selected file'})
if file:
# Save the uploaded file
filename = secure_filename(file.filename)
file_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
file.save(file_path)
# Extract text from the resume (assuming it's a text file for simplicity)
# In a real app, you would handle different file formats (PDF, DOCX, etc.)
try:
with open(file_path, 'r', encoding='utf-8') as f:
resume_text = f.read()
except UnicodeDecodeError:
# Try another encoding if UTF-8 fails
with open(file_path, 'r', encoding='latin-1') as f:
resume_text = f.read()
# Process the resume
processed_text = preprocess_resume(resume_text)
skills = extract_skills(resume_text, common_skills)
years_experience = extract_experience(resume_text)
education = extract_education(resume_text)
# Create a row object for scoring
resume_row = {
'Resume': resume_text,
'processed_resume': processed_text,
'skills': skills,
'years_experience': years_experience,
'education': education
}
# Score for each job title
job_title = request.form.get('job_title', list(job_requirements.keys())[0])
requirements = job_requirements[job_title]
score = score_resume(resume_row, job_title, requirements, best_weights)
# Prepare the result
result = {
'score': score,
'skills': skills,
'missing_skills': [skill for skill in requirements['required_skills'] if skill not in skills],
'years_experience': years_experience,
'education': education,
'job_title': job_title
}
return jsonify(result)
return jsonify({'error': 'Failed to process file'})
# Run the app
if __name__ == '__main__':
app.run(debug=True, host='0.0.0.0', port=5000)
Creating HTML Templates
We need to create an HTML template for our web interface. Create a folder named templates and add an index.html file:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Resume Parser AI</title>
<style>
body {
font-family: Arial, sans-serif;
line-height: 1.6;
margin: 0;
padding: 20px;
background-color: #f5f5f5;
}
.container {
max-width: 800px;
margin: 0 auto;
background-color: white;
padding: 20px;
border-radius: 5px;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}
h1 {
color: #333;
text-align: center;
}
.form-group {
margin-bottom: 15px;
}
label {
display: block;
margin-bottom: 5px;
font-weight: bold;
}
select, input[type="file"] {
width: 100%;
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
}
button {
background-color: #4CAF50;
color: white;
padding: 10px 15px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
}
button:hover {
background-color: #45a049;
}
#result {
margin-top: 20px;
padding: 15px;
border: 1px solid #ddd;
border-radius: 4px;
display: none;
}
.score {
font-size: 24px;
font-weight: bold;
text-align: center;
margin-bottom: 15px;
}
.score-high {
color: #4CAF50;
}
.score-medium {
color: #FFC107;
}
.score-low {
color: #F44336;
}
.details {
margin-top: 15px;
}
.skills-list {
display: flex;
flex-wrap: wrap;
gap: 5px;
}
.skill {
background-color: #e1f5fe;
padding: 5px 10px;
border-radius: 15px;
font-size: 14px;
}
.missing-skill {
background-color: #ffebee;
}
.loader {
border: 5px solid #f3f3f3;
border-top: 5px solid #3498db;
border-radius: 50%;
width: 30px;
height: 30px;
animation: spin 2s linear infinite;
margin: 20px auto;
display: none;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
</style>
</head>
<body>
<div class="container">
<h1>Resume Parser AI</h1>
<p>Upload a resume to analyze its match for a specific job position.</p>
<form id="resumeForm" enctype="multipart/form-data">
<div class="form-group">
<label for="jobTitle">Job Title:</label>
<select id="jobTitle" name="job_title">
{% for job_title in job_titles %}
<option value="{{ job_title }}">{{ job_title }}</option>
{% endfor %}
</select>
</div>
<div class="form-group">
<label for="resumeFile">Upload Resume (TXT format):</label>
<input type="file" id="resumeFile" name="resume" accept=".txt">
</div>
<button type="submit">Analyze Resume</button>
</form>
<div class="loader" id="loader"></div>
<div id="result">
<div class="score" id="scoreDisplay"></div>
<div class="details">
<h3>Skills Identified:</h3>
<div class="skills-list" id="skillsList"></div>
<h3>Missing Required Skills:</h3>
<div class="skills-list" id="missingSkillsList"></div>
<h3>Other Information:</h3>
<p>Years of Experience: <span id="experienceYears"></span></p>
<p>Education: <span id="educationLevel"></span></p>
</div>
</div>
</div>
<script>
document.getElementById('resumeForm').addEventListener('submit', function(e) {
e.preventDefault();
const formData = new FormData(this);
const resultDiv = document.getElementById('result');
const loader = document.getElementById('loader');
// Show loader
loader.style.display = 'block';
resultDiv.style.display = 'none';
fetch('/upload', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
// Hide loader
loader.style.display = 'none';
if (data.error) {
alert(data.error);
return;
}
// Display score
const scoreDisplay = document.getElementById('scoreDisplay');
scoreDisplay.textContent = `Match Score: ${data.score.toFixed(1)}/10`;
// Set score color based on value
if (data.score >= 7) {
scoreDisplay.className = 'score score-high';
} else if (data.score >= 5) {
scoreDisplay.className = 'score score-medium';
} else {
scoreDisplay.className = 'score score-low';
}
// Display skills
const skillsList = document.getElementById('skillsList');
skillsList.innerHTML = '';
data.skills.forEach(skill => {
const skillSpan = document.createElement('span');
skillSpan.className = 'skill';
skillSpan.textContent = skill;
skillsList.appendChild(skillSpan);
});
// Display missing skills
const missingSkillsList = document.getElementById('missingSkillsList');
missingSkillsList.innerHTML = '';
if (data.missing_skills.length > 0) {
data.missing_skills.forEach(skill => {
const skillSpan = document.createElement('span');
skillSpan.className = 'skill missing-skill';
skillSpan.textContent = skill;
missingSkillsList.appendChild(skillSpan);
});
} else {
missingSkillsList.textContent = 'None - All required skills found!';
}
// Display other information
document.getElementById('experienceYears').textContent = data.years_experience;
document.getElementById('educationLevel').textContent = data.education.join(', ') || 'Not specified';
// Show result
resultDiv.style.display = 'block';
})
.catch(error => {
loader.style.display = 'none';
alert('Error: ' + error);
});
});
</script>
</body>
</html>
Running the Web Application
To run the web application:
- Make sure you have all the required files:
app.py(the Flask application)templates/index.html(the HTML template)models/optimized_scoring_model.pkl(your trained model)uploads/directory (for storing uploaded resumes)
- Install the required packages:
pip install flask pandas scikit-learn nltk spacy python -m spacy download en_core_web_sm - Run the application:
python app.py - Open your web browser and navigate to
http://localhost:5000
Enhancing the Web Application
For a more complete application, you might want to add:
- Support for different file formats (PDF, DOCX)
# Add to app.py from PyPDF2 import PdfReader import docx def extract_text_from_pdf(file_path): text = "" with open(file_path, 'rb') as file: pdf_reader = PdfReader(file) for page in pdf_reader.pages: text += page.extract_text() return text def extract_text_from_docx(file_path): doc = docx.Document(file_path) text = "" for paragraph in doc.paragraphs: text += paragraph.text + "\n" return text # Then modify the upload_resume function to handle different file types if file.filename.endswith('.pdf'): resume_text = extract_text_from_pdf(file_path) elif file.filename.endswith('.docx'): resume_text = extract_text_from_docx(file_path) else: # Text file handling (as before) - User authentication to restrict access to authorized recruiters
- Resume storage and management to keep track of candidates
- Batch processing to analyze multiple resumes at once
- Detailed reports with visualizations of candidate strengths and weaknesses
Deploying to Production
For a production environment, you would need to:
- Use a production WSGI server like Gunicorn instead of Flask's development server
- Set up a proper database for storing user data and resume information
- Implement security measures like HTTPS, input validation, and protection against common web vulnerabilities
- Deploy to a cloud platform like AWS, Google Cloud, or Heroku
Here's a simple example of deploying with Gunicorn:
# Install Gunicorn
pip install gunicorn
# Run with Gunicorn
gunicorn -w 4 -b 0.0.0.0:5000 app:app
When deploying to production, make sure to set debug=False in your Flask application to avoid exposing sensitive information.
Conclusion
Congratulations! You've now completed all the steps to build a Resume Parser AI, from data collection to deployment. This project has introduced you to various concepts in machine learning, natural language processing, and web development.
Remember that this is just the beginning. There are many ways to improve and expand your Resume Parser AI:
- Train on larger and more diverse datasets
- Implement more sophisticated NLP techniques
- Add features like resume summarization or job recommendation
- Integrate with applicant tracking systems
The skills you've learned in this project can be applied to many other text analysis and machine learning tasks. Keep exploring and building!