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:

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:

  1. 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)
  2. Install the required packages:
    pip install flask pandas scikit-learn nltk spacy
    python -m spacy download en_core_web_sm
  3. Run the application:
    python app.py
  4. Open your web browser and navigate to http://localhost:5000

Enhancing the Web Application

For a more complete application, you might want to add:

  1. 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)
                        
  2. User authentication to restrict access to authorized recruiters
  3. Resume storage and management to keep track of candidates
  4. Batch processing to analyze multiple resumes at once
  5. Detailed reports with visualizations of candidate strengths and weaknesses

Deploying to Production

For a production environment, you would need to:

  1. Use a production WSGI server like Gunicorn instead of Flask's development server
  2. Set up a proper database for storing user data and resume information
  3. Implement security measures like HTTPS, input validation, and protection against common web vulnerabilities
  4. 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:

The skills you've learned in this project can be applied to many other text analysis and machine learning tasks. Keep exploring and building!