|
import streamlit as st |
|
import pdfplumber |
|
import io |
|
import spacy |
|
import re |
|
import pandas as pd |
|
import matplotlib.pyplot as plt |
|
from transformers import pipeline |
|
|
|
try: |
|
from sentence_transformers import SentenceTransformer |
|
|
|
try: |
|
from sentence_transformers import util |
|
except ImportError: |
|
|
|
class util: |
|
@staticmethod |
|
def pytorch_cos_sim(a, b): |
|
""" |
|
Compute cosine similarity between two PyTorch tensors |
|
""" |
|
import torch |
|
if not isinstance(a, torch.Tensor): |
|
a = torch.tensor(a) |
|
if not isinstance(b, torch.Tensor): |
|
b = torch.tensor(b) |
|
|
|
if len(a.shape) == 1: |
|
a = a.unsqueeze(0) |
|
if len(b.shape) == 1: |
|
b = b.unsqueeze(0) |
|
|
|
a_norm = torch.nn.functional.normalize(a, p=2, dim=1) |
|
b_norm = torch.nn.functional.normalize(b, p=2, dim=1) |
|
return torch.mm(a_norm, b_norm.transpose(0, 1)) |
|
except ImportError: |
|
st.error("Failed to import SentenceTransformer. Semantic matching will be disabled.") |
|
SentenceTransformer = None |
|
class util: |
|
@staticmethod |
|
def pytorch_cos_sim(*args, **kwargs): |
|
return 0 |
|
import subprocess |
|
import sys |
|
import torch |
|
import nltk |
|
from nltk.tokenize import word_tokenize |
|
from datetime import datetime |
|
import plotly.express as px |
|
import plotly.graph_objects as go |
|
import numpy as np |
|
from collections import defaultdict |
|
|
|
|
|
try: |
|
|
|
from huggingface_hub import hf_hub_download |
|
except ImportError: |
|
try: |
|
|
|
from huggingface_hub import cached_download as hf_hub_download |
|
except ImportError: |
|
st.error("Could not import required functions from huggingface_hub. Please check your installation.") |
|
hf_hub_download = None |
|
|
|
|
|
@st.cache_resource |
|
def download_nltk_resources(): |
|
try: |
|
nltk.data.find('tokenizers/punkt') |
|
except LookupError: |
|
nltk.download('punkt') |
|
|
|
download_nltk_resources() |
|
|
|
st.set_page_config( |
|
page_title="Comprehensive Resume Screener & Skill Extractor", |
|
page_icon="π", |
|
layout="wide" |
|
) |
|
|
|
|
|
@st.cache_resource |
|
def download_spacy_model(): |
|
try: |
|
nlp = spacy.load("en_core_web_sm") |
|
except OSError: |
|
subprocess.check_call([sys.executable, "-m", "spacy", "download", "en_core_web_sm"]) |
|
nlp = spacy.load("en_core_web_sm") |
|
return nlp |
|
|
|
|
|
@st.cache_resource |
|
def load_models(): |
|
try: |
|
summarizer = pipeline("summarization", model="facebook/bart-large-cnn") |
|
except Exception as e: |
|
st.error(f"Failed to load summarization model: {str(e)}") |
|
|
|
summarizer = lambda text, **kwargs: [{"summary_text": ". ".join(text.split(". ")[:3]) + "."}] |
|
|
|
try: |
|
nlp = download_spacy_model() |
|
except Exception as e: |
|
st.error(f"Failed to load spaCy model: {str(e)}") |
|
nlp = None |
|
|
|
|
|
sentence_model = None |
|
if SentenceTransformer is not None: |
|
try: |
|
sentence_model = SentenceTransformer('all-MiniLM-L6-v2') |
|
except Exception as e: |
|
st.error(f"Failed to load sentence transformer: {str(e)}") |
|
|
|
return summarizer, nlp, sentence_model |
|
|
|
|
|
summarizer, nlp, sentence_model = load_models() |
|
|
|
|
|
job_descriptions = { |
|
"Software Engineer": { |
|
"skills": ["python", "java", "javascript", "sql", "algorithms", "data structures", |
|
"git", "cloud", "web development", "software development", "coding"], |
|
"description": "Looking for software engineers with strong programming skills and experience in software development.", |
|
"semantic_description": """ |
|
We're seeking a talented Software Engineer to design, develop, and maintain high-quality software solutions. |
|
The ideal candidate has strong programming skills in languages like Python, Java, or JavaScript, and experience with |
|
SQL databases. You should be proficient in algorithms, data structures, and version control systems like Git. |
|
Experience with cloud platforms and web development frameworks is a plus. You'll be responsible for the full |
|
software development lifecycle, from requirements gathering to deployment and maintenance. |
|
""" |
|
}, |
|
"Interaction Designer": { |
|
"skills": ["ui", "ux", "user research", "wireframing", "prototyping", "figma", |
|
"sketch", "adobe", "design thinking", "interaction design"], |
|
"description": "Seeking interaction designers with expertise in user experience and interface design.", |
|
"semantic_description": """ |
|
We're looking for a creative Interaction Designer to craft intuitive and engaging user experiences. |
|
You should have expertise in UI/UX design principles and methods, with a portfolio demonstrating your |
|
ability to conduct user research, create wireframes, and develop interactive prototypes. Proficiency |
|
with design tools like Figma, Sketch, and Adobe Creative Suite is required. You'll collaborate with |
|
product managers and developers to iterate on designs based on user feedback and business requirements. |
|
""" |
|
}, |
|
"Data Scientist": { |
|
"skills": ["python", "r", "statistics", "machine learning", "data analysis", |
|
"sql", "tensorflow", "pytorch", "pandas", "numpy"], |
|
"description": "Looking for data scientists with strong analytical and machine learning skills.", |
|
"semantic_description": """ |
|
We're seeking a skilled Data Scientist to extract insights from complex datasets and build predictive models. |
|
The ideal candidate has strong programming skills in Python or R, expertise in statistical analysis, and |
|
experience with machine learning algorithms. You should be proficient in SQL for data extraction and tools |
|
like TensorFlow or PyTorch for deep learning. Experience with data manipulation libraries like Pandas and NumPy |
|
is essential. You'll work on projects from exploratory data analysis to model deployment, collaborating with |
|
stakeholders to solve business problems through data-driven approaches. |
|
""" |
|
} |
|
} |
|
|
|
def extract_text_from_pdf(pdf_file): |
|
text = "" |
|
with pdfplumber.open(pdf_file) as pdf: |
|
for page in pdf.pages: |
|
text += page.extract_text() or "" |
|
return text |
|
|
|
def extract_work_experience(text): |
|
"""Extract work experience details including company names, job titles, and dates""" |
|
|
|
work_exp_patterns = [ |
|
r"(?i)WORK EXPERIENCE|PROFESSIONAL EXPERIENCE|EMPLOYMENT HISTORY|EXPERIENCE", |
|
r"(?i)EDUCATION|ACADEMIC|QUALIFICATIONS" |
|
] |
|
|
|
|
|
work_exp_start = None |
|
for pattern in work_exp_patterns[:1]: |
|
match = re.search(pattern, text) |
|
if match: |
|
work_exp_start = match.end() |
|
break |
|
|
|
if work_exp_start is None: |
|
return [] |
|
|
|
|
|
work_exp_end = len(text) |
|
for pattern in work_exp_patterns[1:]: |
|
match = re.search(pattern, text) |
|
if match and match.start() > work_exp_start: |
|
work_exp_end = match.start() |
|
break |
|
|
|
work_exp_text = text[work_exp_start:work_exp_end] |
|
|
|
|
|
|
|
job_entries = [] |
|
|
|
|
|
date_pattern = r"(?i)(Jan(?:uary)?|Feb(?:ruary)?|Mar(?:ch)?|Apr(?:il)?|May|Jun(?:e)?|Jul(?:y)?|Aug(?:ust)?|Sep(?:tember)?|Oct(?:ober)?|Nov(?:ember)?|Dec(?:ember)?)[,\s]+\d{4}|\d{1,2}/\d{4}|\d{4}" |
|
|
|
|
|
job_title_pattern = r"(?i)(Senior|Lead|Principal|Junior|Associate)?\s*(Software Engineer|Developer|Designer|Analyst|Manager|Director|Consultant|Specialist|Coordinator|Administrator)" |
|
|
|
|
|
paragraphs = re.split(r'\n\s*\n', work_exp_text) |
|
|
|
for paragraph in paragraphs: |
|
|
|
if len(paragraph.strip()) < 30: |
|
continue |
|
|
|
|
|
dates = re.findall(date_pattern, paragraph) |
|
start_date = dates[0] if dates else "Unknown" |
|
end_date = dates[-1] if len(dates) > 1 else "Present" |
|
|
|
|
|
title_match = re.search(job_title_pattern, paragraph) |
|
job_title = title_match.group(0) if title_match else "Unknown Position" |
|
|
|
|
|
lines = paragraph.split('\n') |
|
company = lines[0].strip() if lines else "Unknown Company" |
|
if job_title in company: |
|
company = company.replace(job_title, "").strip() |
|
|
|
|
|
for date in dates: |
|
company = company.replace(date, "").strip() |
|
company = re.sub(r'[,\.\|\-]', ' ', company).strip() |
|
|
|
job_entries.append({ |
|
"company": company, |
|
"title": job_title, |
|
"start_date": start_date, |
|
"end_date": end_date, |
|
"description": paragraph |
|
}) |
|
|
|
return job_entries |
|
|
|
def estimate_skill_proficiency(text, skill): |
|
"""Estimate proficiency level for a skill""" |
|
|
|
basic_indicators = ["familiar with", "basic knowledge", "understanding of", "exposure to"] |
|
intermediate_indicators = ["experience with", "proficient in", "worked with", "2-3 years", "2 years", "3 years"] |
|
advanced_indicators = ["expert in", "advanced", "extensive experience", "lead", "architected", "designed", "5+ years", "4+ years"] |
|
|
|
|
|
text_lower = text.lower() |
|
|
|
|
|
skill_lower = skill.lower() |
|
skill_index = text_lower.find(skill_lower) |
|
|
|
if skill_index == -1: |
|
return None |
|
|
|
|
|
start = max(0, skill_index - 100) |
|
end = min(len(text_lower), skill_index + len(skill_lower) + 100) |
|
context = text_lower[start:end] |
|
|
|
|
|
for indicator in advanced_indicators: |
|
if indicator in context: |
|
return "Advanced" |
|
|
|
for indicator in intermediate_indicators: |
|
if indicator in context: |
|
return "Intermediate" |
|
|
|
for indicator in basic_indicators: |
|
if indicator in context: |
|
return "Basic" |
|
|
|
|
|
return "Basic" |
|
|
|
def calculate_seniority_score(job_entries): |
|
"""Calculate a seniority score based on job titles and years of experience""" |
|
|
|
seniority_levels = { |
|
"intern": 1, |
|
"junior": 2, |
|
"associate": 3, |
|
"developer": 4, |
|
"engineer": 4, |
|
"designer": 4, |
|
"analyst": 4, |
|
"senior": 6, |
|
"lead": 7, |
|
"manager": 7, |
|
"principal": 8, |
|
"director": 9, |
|
"vp": 10, |
|
"cto": 10, |
|
"cio": 10, |
|
"ceo": 10 |
|
} |
|
|
|
|
|
total_years = 0 |
|
|
|
for job in job_entries: |
|
|
|
try: |
|
start_year = re.search(r'\d{4}', job["start_date"]) |
|
end_year = re.search(r'\d{4}', job["end_date"]) if job["end_date"] != "Present" else None |
|
|
|
if start_year: |
|
start_year = int(start_year.group(0)) |
|
end_year = int(end_year.group(0)) if end_year else datetime.now().year |
|
years = end_year - start_year |
|
if 0 <= years <= 30: |
|
total_years += years |
|
except Exception: |
|
|
|
pass |
|
|
|
|
|
highest_seniority = 0 |
|
|
|
for job in job_entries: |
|
title_lower = job["title"].lower() |
|
for level_title, score in seniority_levels.items(): |
|
if level_title in title_lower and score > highest_seniority: |
|
highest_seniority = score |
|
|
|
|
|
|
|
years_factor = 1 |
|
if total_years >= 3: |
|
years_factor = 2 |
|
if total_years >= 6: |
|
years_factor = 3 |
|
if total_years >= 11: |
|
years_factor = 4 |
|
|
|
|
|
seniority_score = min(10, max(1, (highest_seniority * 0.6) + (years_factor * 1.0))) |
|
|
|
return round(seniority_score, 1), total_years |
|
|
|
def detect_fraud_signals(text, job_entries): |
|
"""Detect potential fraud signals in the resume""" |
|
fraud_signals = [] |
|
|
|
|
|
if len(job_entries) >= 2: |
|
for i in range(len(job_entries) - 1): |
|
for j in range(i+1, len(job_entries)): |
|
|
|
if (job_entries[i]["start_date"] != "Unknown" and |
|
job_entries[i]["end_date"] != "Unknown" and |
|
job_entries[j]["start_date"] != "Unknown" and |
|
job_entries[j]["end_date"] != "Unknown"): |
|
|
|
|
|
i_start = re.search(r'\d{4}', job_entries[i]["start_date"]) |
|
i_end = re.search(r'\d{4}', job_entries[i]["end_date"]) if job_entries[i]["end_date"] != "Present" else None |
|
j_start = re.search(r'\d{4}', job_entries[j]["start_date"]) |
|
j_end = re.search(r'\d{4}', job_entries[j]["end_date"]) if job_entries[j]["end_date"] != "Present" else None |
|
|
|
|
|
if i_start and j_start: |
|
i_start = int(i_start.group(0)) |
|
i_end = int(i_end.group(0)) if i_end else datetime.now().year |
|
j_start = int(j_start.group(0)) |
|
j_end = int(j_end.group(0)) if j_end else datetime.now().year |
|
|
|
|
|
if ((i_start <= j_start < i_end) or (j_start <= i_start < j_end)) and job_entries[i]["company"] != job_entries[j]["company"]: |
|
overlap_years = min(i_end, j_end) - max(i_start, j_start) |
|
if overlap_years > 0.5: |
|
fraud_signals.append(f"Potential timeline inconsistency: Overlapping roles at {job_entries[i]['company']} and {job_entries[j]['company']} for {overlap_years:.1f} years") |
|
|
|
|
|
suspicious_phrases = [ |
|
"self-employed", |
|
"freelance", |
|
"consultant", |
|
"entrepreneur", |
|
"founder", |
|
"ceo of own company" |
|
] |
|
|
|
|
|
for phrase in suspicious_phrases: |
|
if phrase in text.lower(): |
|
|
|
fraud_signals.append(f"Verification recommended: Contains '{phrase}' which may need additional verification") |
|
|
|
|
|
if len(job_entries) >= 2: |
|
for i in range(len(job_entries) - 1): |
|
|
|
if "Unknown" not in job_entries[i]["end_date"] and "Unknown" not in job_entries[i+1]["start_date"]: |
|
end_match = re.search(r'\d{4}', job_entries[i]["end_date"]) |
|
start_match = re.search(r'\d{4}', job_entries[i+1]["start_date"]) |
|
|
|
if end_match and start_match: |
|
end_year = int(end_match.group(0)) |
|
start_year = int(start_match.group(0)) |
|
|
|
|
|
if start_year - end_year > 1: |
|
fraud_signals.append(f"Employment gap: {end_year} to {start_year} ({start_year - end_year} years)") |
|
|
|
return fraud_signals |
|
|
|
def predict_career_trajectory(job_entries, current_skills): |
|
"""Predict logical next roles based on career progression""" |
|
|
|
career_paths = { |
|
"software engineer": ["Senior Software Engineer", "Lead Developer", "Software Architect", "Engineering Manager", "CTO"], |
|
"developer": ["Senior Developer", "Technical Lead", "Software Architect", "Development Manager", "CTO"], |
|
"designer": ["Senior Designer", "Lead Designer", "Design Manager", "Creative Director", "VP of Design"], |
|
"data scientist": ["Senior Data Scientist", "Lead Data Scientist", "Data Science Manager", "Director of Analytics", "Chief Data Officer"] |
|
} |
|
|
|
|
|
current_role = job_entries[0]["title"].lower() if job_entries else "unknown" |
|
|
|
|
|
best_match = None |
|
for role_key in career_paths: |
|
if role_key in current_role: |
|
best_match = role_key |
|
break |
|
|
|
if not best_match: |
|
return ["Career path prediction requires more information"] |
|
|
|
|
|
current_index = 0 |
|
for i, role in enumerate(career_paths[best_match]): |
|
if any(indicator in current_role for indicator in ["senior", "lead", "manager", "director", "vp", "chief"]): |
|
|
|
if "senior" in current_role and "senior" in role.lower(): |
|
current_index = i |
|
break |
|
elif "lead" in current_role and "lead" in role.lower(): |
|
current_index = i |
|
break |
|
elif "manager" in current_role and "manager" in role.lower(): |
|
current_index = i |
|
break |
|
elif "director" in current_role and "director" in role.lower(): |
|
current_index = i |
|
break |
|
|
|
|
|
next_roles = [] |
|
for i in range(current_index + 1, min(current_index + 4, len(career_paths[best_match]))): |
|
next_roles.append(career_paths[best_match][i]) |
|
|
|
if not next_roles: |
|
next_roles = ["You're at a senior level in your career path. Consider lateral moves or industry specialization."] |
|
|
|
return next_roles |
|
|
|
def analyze_resume(text, job_title, sentence_model): |
|
|
|
job_entries = extract_work_experience(text) |
|
|
|
|
|
job_entries.sort(key=lambda x: "9999" if x["start_date"] == "Unknown" else x["start_date"], reverse=True) |
|
|
|
|
|
doc = nlp(text.lower()) |
|
found_skills = [] |
|
required_skills = job_descriptions[job_title]["skills"] |
|
|
|
for skill in required_skills: |
|
if skill in text.lower(): |
|
found_skills.append(skill) |
|
|
|
|
|
skill_proficiencies = {} |
|
for skill in found_skills: |
|
proficiency = estimate_skill_proficiency(text, skill) |
|
if proficiency: |
|
skill_proficiencies[skill] = proficiency |
|
|
|
|
|
seniority_score, years_experience = calculate_seniority_score(job_entries) |
|
|
|
|
|
fraud_signals = detect_fraud_signals(text, job_entries) |
|
|
|
|
|
next_roles = predict_career_trajectory(job_entries, found_skills) |
|
|
|
|
|
chunks = [text[i:i + 1000] for i in range(0, len(text), 1000)] |
|
summaries = [] |
|
for chunk in chunks[:3]: |
|
summary = summarizer(chunk, max_length=150, min_length=50, do_sample=False)[0]["summary_text"] |
|
summaries.append(summary) |
|
|
|
|
|
semantic_score = 0 |
|
if sentence_model is not None and SentenceTransformer is not None: |
|
try: |
|
resume_embedding = sentence_model.encode(text[:5000]) |
|
job_embedding = sentence_model.encode(job_descriptions[job_title]["semantic_description"]) |
|
semantic_score = float(util.pytorch_cos_sim(resume_embedding, job_embedding)[0][0]) |
|
except Exception as e: |
|
st.error(f"Error in semantic matching: {str(e)}") |
|
|
|
return { |
|
"found_skills": found_skills, |
|
"skill_proficiencies": skill_proficiencies, |
|
"summary": " ".join(summaries), |
|
"job_entries": job_entries, |
|
"seniority_score": seniority_score, |
|
"years_experience": years_experience, |
|
"fraud_signals": fraud_signals, |
|
"next_roles": next_roles, |
|
"semantic_score": semantic_score |
|
} |
|
|
|
def generate_career_advice(resume_text, job_title, found_skills, missing_skills): |
|
""" |
|
Generate career advice using a template-based approach instead of Qwen3-8B |
|
to avoid dependency issues |
|
""" |
|
|
|
advice = f"""## Career Development Plan for {job_title} Position |
|
|
|
### Skills to Develop |
|
|
|
The following skills would strengthen your resume for this position: |
|
|
|
""" |
|
|
|
|
|
for skill in missing_skills: |
|
if skill == "python": |
|
advice += f"""#### Python |
|
- **How to develop**: Take online courses focused on Python for {job_title.lower()} applications |
|
- **Project idea**: Build a data analysis tool or web application using Python and popular frameworks |
|
- **Resources**: Coursera's Python for Everybody, Python.org tutorials, Real Python website |
|
|
|
""" |
|
elif skill == "java": |
|
advice += f"""#### Java |
|
- **How to develop**: Complete a comprehensive Java course with practical exercises |
|
- **Project idea**: Develop a backend service with Spring Boot |
|
- **Resources**: Oracle's Java tutorials, Udemy courses on Java, "Effective Java" by Joshua Bloch |
|
|
|
""" |
|
elif skill == "javascript": |
|
advice += f"""#### JavaScript |
|
- **How to develop**: Practice with modern JavaScript frameworks |
|
- **Project idea**: Create an interactive web application with React or Vue.js |
|
- **Resources**: MDN Web Docs, freeCodeCamp, "Eloquent JavaScript" by Marijn Haverbeke |
|
|
|
""" |
|
elif skill == "sql": |
|
advice += f"""#### SQL |
|
- **How to develop**: Practice with database design and complex queries |
|
- **Project idea**: Design a database system for a small business with reports and analytics |
|
- **Resources**: SQLZoo, Mode Analytics SQL tutorial, W3Schools SQL course |
|
|
|
""" |
|
elif "algorithms" in skill or "data structures" in skill: |
|
advice += f"""#### Algorithms & Data Structures |
|
- **How to develop**: Solve coding problems regularly on platforms like LeetCode |
|
- **Project idea**: Implement classic algorithms and optimize them for specific use cases |
|
- **Resources**: "Cracking the Coding Interview" book, AlgoExpert, Coursera Algorithms specialization |
|
|
|
""" |
|
elif "git" in skill: |
|
advice += f"""#### Git & Version Control |
|
- **How to develop**: Contribute to open source projects to practice Git workflows |
|
- **Project idea**: Set up a personal project with proper branching strategies and CI/CD |
|
- **Resources**: Git documentation, GitHub Learning Lab, Atlassian Git tutorials |
|
|
|
""" |
|
elif "cloud" in skill: |
|
advice += f"""#### Cloud Technologies |
|
- **How to develop**: Get hands-on experience with a major cloud provider (AWS, Azure, GCP) |
|
- **Project idea**: Deploy an application to the cloud with proper infrastructure as code |
|
- **Resources**: Cloud provider documentation, A Cloud Guru courses, free tier accounts |
|
|
|
""" |
|
elif "ui" in skill or "ux" in skill: |
|
advice += f"""#### UI/UX Design |
|
- **How to develop**: Study design principles and practice creating user interfaces |
|
- **Project idea**: Redesign an existing website or app with focus on user experience |
|
- **Resources**: Nielsen Norman Group articles, Interaction Design Foundation, Figma tutorials |
|
|
|
""" |
|
elif "machine learning" in skill: |
|
advice += f"""#### Machine Learning |
|
- **How to develop**: Take courses on ML fundamentals and practice with datasets |
|
- **Project idea**: Build a predictive model to solve a real-world problem |
|
- **Resources**: Andrew Ng's Coursera courses, Kaggle competitions, "Hands-On Machine Learning" book |
|
|
|
""" |
|
elif "data analysis" in skill: |
|
advice += f"""#### Data Analysis |
|
- **How to develop**: Practice analyzing datasets and creating visualizations |
|
- **Project idea**: Perform an exploratory data analysis on a public dataset |
|
- **Resources**: DataCamp courses, Kaggle datasets, "Python for Data Analysis" by Wes McKinney |
|
|
|
""" |
|
else: |
|
advice += f"""#### {skill.title()} |
|
- **How to develop**: Research industry best practices and take relevant courses |
|
- **Project idea**: Create a portfolio piece that showcases this skill |
|
- **Resources**: Online courses, industry blogs, and practice projects |
|
|
|
""" |
|
|
|
|
|
advice += f""" |
|
### Recommended Projects for {job_title} |
|
|
|
Based on the target position and the skills needed, here are some project ideas: |
|
|
|
""" |
|
if job_title == "Software Engineer": |
|
advice += """ |
|
1. **Full-Stack Web Application**: Build a complete web app with frontend, backend, and database |
|
2. **API Service**: Create a RESTful or GraphQL API with proper authentication and documentation |
|
3. **Mobile Application**: Develop a cross-platform mobile app using React Native or Flutter |
|
4. **Automation Tools**: Build scripts or applications that automate repetitive tasks |
|
5. **Contribution to Open Source**: Find a project aligned with your skills and contribute meaningfully |
|
|
|
""" |
|
elif job_title == "Interaction Designer": |
|
advice += """ |
|
1. **Design System**: Create a comprehensive design system with components and usage guidelines |
|
2. **Website Redesign**: Redesign an existing website with focus on improved UX |
|
3. **Mobile App Prototype**: Design a fully interactive mobile app prototype |
|
4. **User Research Project**: Conduct user research and create a report with insights and recommendations |
|
5. **Design Case Study**: Document your design process for solving a specific problem |
|
|
|
""" |
|
elif job_title == "Data Scientist": |
|
advice += """ |
|
1. **Predictive Model**: Build a machine learning model that solves a real-world problem |
|
2. **Data Visualization Dashboard**: Create an interactive dashboard to visualize complex data |
|
3. **Natural Language Processing**: Develop a text analysis or sentiment analysis project |
|
4. **Time Series Analysis**: Analyze time-based data and build forecasting models |
|
5. **A/B Testing Framework**: Design and implement a framework for testing hypotheses |
|
|
|
""" |
|
|
|
|
|
advice += """ |
|
### Learning Resources |
|
|
|
- **Online Platforms**: Coursera, Udemy, Pluralsight, LinkedIn Learning |
|
- **Documentation**: Official language and framework documentation |
|
- **Communities**: Stack Overflow, GitHub, Reddit programming communities |
|
- **Books**: O'Reilly publications specific to your target technologies |
|
- **YouTube Channels**: Traversy Media, Tech With Tim, freeCodeCamp |
|
|
|
### Positioning Your Experience |
|
|
|
- Highlight transferable skills from your current experience |
|
- Quantify achievements with metrics where possible |
|
- Frame previous work in terms relevant to the target position |
|
- Create a tailored resume that emphasizes relevant projects and responsibilities |
|
""" |
|
|
|
return advice |
|
|
|
|
|
st.title("π Comprehensive Resume Analyzer") |
|
|
|
|
|
st.markdown(""" |
|
This app helps recruiters and job seekers analyze resumes with advanced features: |
|
|
|
- **Semantic Job Matching**: Uses AI to match resumes to job descriptions beyond keywords |
|
- **Skill Proficiency Detection**: Identifies skill levels from context |
|
- **Career Progression Analysis**: Visualizes job history and seniority |
|
- **Fraud Detection**: Flags potential inconsistencies for verification |
|
- **Career Path Prediction**: Suggests logical next roles based on experience |
|
- **Personalized Development Advice**: Recommends skills, projects, and resources |
|
""") |
|
|
|
|
|
col1, col2 = st.columns([2, 1]) |
|
|
|
with col1: |
|
|
|
uploaded_file = st.file_uploader("Upload Resume (PDF)", type=["pdf"]) |
|
|
|
with col2: |
|
|
|
job_title = st.selectbox("Select Job Position", list(job_descriptions.keys())) |
|
|
|
|
|
if job_title: |
|
st.info(f"**Job Description:**\n{job_descriptions[job_title]['description']}\n\n**Required Skills:**\n" + |
|
"\n".join([f"- {skill.title()}" for skill in job_descriptions[job_title]["skills"]])) |
|
|
|
if uploaded_file and job_title: |
|
try: |
|
|
|
with st.spinner("Analyzing resume with advanced AI..."): |
|
|
|
text = extract_text_from_pdf(uploaded_file) |
|
|
|
|
|
analysis_results = analyze_resume(text, job_title, sentence_model) |
|
|
|
|
|
missing_skills = [skill for skill in job_descriptions[job_title]["skills"] |
|
if skill not in analysis_results["found_skills"]] |
|
|
|
|
|
tab1, tab2, tab3, tab4, tab5, tab6 = st.tabs([ |
|
"π Match Score", |
|
"π― Skills Analysis", |
|
"π¨βπΌ Experience", |
|
"π Career Path", |
|
"π© Verification", |
|
"π Career Advice" |
|
]) |
|
|
|
with tab1: |
|
|
|
st.subheader("π Job Match Analysis") |
|
|
|
|
|
keyword_match = len(analysis_results["found_skills"]) / len(job_descriptions[job_title]["skills"]) * 100 |
|
semantic_match = analysis_results["semantic_score"] * 100 |
|
|
|
|
|
col1, col2 = st.columns(2) |
|
|
|
with col1: |
|
|
|
fig = go.Figure(go.Indicator( |
|
mode = "gauge+number", |
|
value = keyword_match, |
|
title = {'text': "Keyword Match"}, |
|
gauge = { |
|
'axis': {'range': [0, 100]}, |
|
'bar': {'color': "darkblue"}, |
|
'steps': [ |
|
{'range': [0, 30], 'color': "lightgray"}, |
|
{'range': [30, 70], 'color': "gray"}, |
|
{'range': [70, 100], 'color': "lightblue"} |
|
], |
|
'threshold': { |
|
'line': {'color': "red", 'width': 4}, |
|
'thickness': 0.75, |
|
'value': 70 |
|
} |
|
} |
|
)) |
|
st.plotly_chart(fig, use_container_width=True) |
|
|
|
with col2: |
|
|
|
fig = go.Figure(go.Indicator( |
|
mode = "gauge+number", |
|
value = semantic_match, |
|
title = {'text': "Semantic Match"}, |
|
gauge = { |
|
'axis': {'range': [0, 100]}, |
|
'bar': {'color': "darkgreen"}, |
|
'steps': [ |
|
{'range': [0, 30], 'color': "lightgray"}, |
|
{'range': [30, 70], 'color': "gray"}, |
|
{'range': [70, 100], 'color': "lightgreen"} |
|
], |
|
'threshold': { |
|
'line': {'color': "red", 'width': 4}, |
|
'thickness': 0.75, |
|
'value': 70 |
|
} |
|
} |
|
)) |
|
st.plotly_chart(fig, use_container_width=True) |
|
|
|
|
|
overall_match = (keyword_match * 0.4) + (semantic_match * 0.6) |
|
|
|
|
|
fig = go.Figure(go.Indicator( |
|
mode = "gauge+number+delta", |
|
value = overall_match, |
|
title = {'text': "Overall Match Score"}, |
|
delta = {'reference': 75, 'increasing': {'color': "green"}}, |
|
gauge = { |
|
'axis': {'range': [0, 100]}, |
|
'bar': {'color': "darkblue"}, |
|
'steps': [ |
|
{'range': [0, 50], 'color': "lightgray"}, |
|
{'range': [50, 75], 'color': "gray"}, |
|
{'range': [75, 100], 'color': "darkblue"} |
|
], |
|
'threshold': { |
|
'line': {'color': "red", 'width': 4}, |
|
'thickness': 0.75, |
|
'value': 75 |
|
} |
|
} |
|
)) |
|
|
|
st.plotly_chart(fig, use_container_width=True) |
|
|
|
|
|
st.subheader("π Resume Summary") |
|
st.write(analysis_results["summary"]) |
|
|
|
with tab2: |
|
|
|
st.subheader("π― Skills Analysis") |
|
|
|
|
|
col1, col2 = st.columns(2) |
|
|
|
with col1: |
|
|
|
st.subheader("π’ Skills Present") |
|
|
|
|
|
skills_data = [] |
|
for skill in analysis_results["found_skills"]: |
|
proficiency = analysis_results["skill_proficiencies"].get(skill, "Basic") |
|
skills_data.append({ |
|
"Skill": skill.title(), |
|
"Proficiency": proficiency |
|
}) |
|
|
|
if skills_data: |
|
skills_df = pd.DataFrame(skills_data) |
|
|
|
|
|
def color_proficiency(val): |
|
if val == "Advanced": |
|
return 'background-color: #d4f7d4' |
|
elif val == "Intermediate": |
|
return 'background-color: #fff2cc' |
|
else: |
|
return 'background-color: #f2f2f2' |
|
|
|
st.dataframe(skills_df.style.applymap(color_proficiency, subset=['Proficiency']), |
|
use_container_width=True) |
|
else: |
|
st.warning("No direct skill matches found.") |
|
|
|
with col2: |
|
|
|
st.subheader("π΄ Skills to Develop") |
|
if missing_skills: |
|
missing_df = pd.DataFrame({"Skill": [skill.title() for skill in missing_skills]}) |
|
st.dataframe(missing_df, use_container_width=True) |
|
else: |
|
st.success("Great! The candidate has all the required skills!") |
|
|
|
|
|
st.subheader("Skills Coverage") |
|
|
|
|
|
categories = job_descriptions[job_title]["skills"] |
|
values = [1 if skill in analysis_results["found_skills"] else 0 for skill in categories] |
|
|
|
|
|
fig = go.Figure() |
|
|
|
fig.add_trace(go.Scatterpolar( |
|
r=values, |
|
theta=categories, |
|
fill='toself', |
|
name='Present Skills' |
|
)) |
|
|
|
fig.add_trace(go.Scatterpolar( |
|
r=[1] * len(categories), |
|
theta=categories, |
|
fill='toself', |
|
name='Required Skills', |
|
opacity=0.3 |
|
)) |
|
|
|
fig.update_layout( |
|
polar=dict( |
|
radialaxis=dict( |
|
visible=True, |
|
range=[0, 1] |
|
)), |
|
showlegend=True |
|
) |
|
|
|
st.plotly_chart(fig, use_container_width=True) |
|
|
|
with tab3: |
|
|
|
st.subheader("π¨βπΌ Experience Analysis") |
|
|
|
|
|
col1, col2 = st.columns(2) |
|
|
|
with col1: |
|
|
|
fig = go.Figure(go.Indicator( |
|
mode="gauge+number", |
|
value=analysis_results["seniority_score"], |
|
title={'text': "Seniority Score"}, |
|
gauge={ |
|
'axis': {'range': [0, 10]}, |
|
'bar': {'color': "darkblue"}, |
|
'steps': [ |
|
{'range': [0, 3], 'color': "lightgray"}, |
|
{'range': [3, 7], 'color': "gray"}, |
|
{'range': [7, 10], 'color': "lightblue"} |
|
], |
|
'threshold': { |
|
'line': {'color': "red", 'width': 4}, |
|
'thickness': 0.75, |
|
'value': 7 |
|
} |
|
} |
|
)) |
|
st.plotly_chart(fig, use_container_width=True) |
|
|
|
with col2: |
|
|
|
fig = go.Figure(go.Indicator( |
|
mode="number+delta", |
|
value=analysis_results["years_experience"], |
|
number={'suffix': " years"}, |
|
title={"text": "Years of Experience"}, |
|
delta={'reference': 5, 'relative': False} |
|
)) |
|
st.plotly_chart(fig, use_container_width=True) |
|
|
|
|
|
st.subheader("Career Progression Timeline") |
|
|
|
if analysis_results["job_entries"]: |
|
|
|
timeline_data = [] |
|
|
|
for job in analysis_results["job_entries"]: |
|
|
|
start_year = re.search(r'\d{4}', job["start_date"]) |
|
end_year = re.search(r'\d{4}', job["end_date"]) if job["end_date"] != "Present" else None |
|
|
|
if start_year: |
|
start_year = int(start_year.group(0)) |
|
end_year = int(end_year.group(0)) if end_year else datetime.now().year |
|
|
|
timeline_data.append({ |
|
"Role": job["title"], |
|
"Company": job["company"], |
|
"Start": start_year, |
|
"End": end_year, |
|
"Duration": end_year - start_year |
|
}) |
|
|
|
if timeline_data: |
|
|
|
timeline_df = pd.DataFrame(timeline_data) |
|
|
|
|
|
timeline_df = timeline_df.sort_values(by="Start") |
|
|
|
|
|
fig = px.timeline( |
|
timeline_df, |
|
x_start="Start", |
|
x_end="End", |
|
y="Company", |
|
color="Role", |
|
hover_data=["Duration"], |
|
labels={"Company": "Employer"} |
|
) |
|
|
|
fig.update_layout( |
|
xaxis_title="Year", |
|
yaxis_title="Employer", |
|
title="Career Progression" |
|
) |
|
|
|
st.plotly_chart(fig, use_container_width=True) |
|
else: |
|
st.warning("Couldn't extract timeline data from the resume.") |
|
else: |
|
st.warning("No work experience entries found in the resume.") |
|
|
|
with tab4: |
|
|
|
st.subheader("π Career Path Analysis") |
|
|
|
|
|
st.subheader("Suggested Next Roles") |
|
|
|
for i, role in enumerate(analysis_results["next_roles"]): |
|
st.info(f"**Option {i+1}:** {role}") |
|
|
|
|
|
st.subheader("Career Progression Path") |
|
|
|
|
|
current_role = analysis_results["job_entries"][0]["title"] if analysis_results["job_entries"] else "Current Position" |
|
|
|
|
|
career_nodes = [current_role] + analysis_results["next_roles"] |
|
|
|
|
|
career_df = pd.DataFrame({ |
|
"From": [career_nodes[i] for i in range(len(career_nodes)-1)], |
|
"To": [career_nodes[i+1] for i in range(len(career_nodes)-1)], |
|
"Value": [10 for _ in range(len(career_nodes)-1)] |
|
}) |
|
|
|
|
|
fig = go.Figure(data=[go.Sankey( |
|
node=dict( |
|
pad=15, |
|
thickness=20, |
|
line=dict(color="black", width=0.5), |
|
label=career_nodes, |
|
color="blue" |
|
), |
|
link=dict( |
|
source=[i for i in range(len(career_nodes)-1)], |
|
target=[i+1 for i in range(len(career_nodes)-1)], |
|
value=[1 for _ in range(len(career_nodes)-1)] |
|
) |
|
)]) |
|
|
|
fig.update_layout(title_text="Potential Career Path", font_size=12) |
|
st.plotly_chart(fig, use_container_width=True) |
|
|
|
with tab5: |
|
|
|
st.subheader("π© Verification Points") |
|
|
|
if analysis_results["fraud_signals"]: |
|
st.warning("The following points may require verification:") |
|
for signal in analysis_results["fraud_signals"]: |
|
st.markdown(f"- {signal}") |
|
else: |
|
st.success("No significant inconsistencies detected in the resume.") |
|
|
|
|
|
st.subheader("Recommended Verification Steps") |
|
st.markdown(""" |
|
Even when no inconsistencies are detected, consider these verification steps: |
|
|
|
1. **Reference Checks**: Contact previous employers to confirm employment dates and responsibilities |
|
2. **Skills Assessment**: Use technical interviews or tests to verify claimed skills |
|
3. **Education Verification**: Confirm degrees and certifications with educational institutions |
|
4. **Portfolio Review**: Examine work samples or project contributions |
|
5. **Online Presence**: Check LinkedIn, GitHub, or other professional profiles for consistency |
|
""") |
|
|
|
with tab6: |
|
|
|
st.subheader("π Career Advice and Development Plan") |
|
|
|
if st.button("Generate Personalized Career Advice"): |
|
with st.spinner("Generating detailed career advice and development plan..."): |
|
advice = generate_career_advice(text, job_title, analysis_results["found_skills"], missing_skills) |
|
st.markdown(advice) |
|
|
|
except Exception as e: |
|
st.error(f"An error occurred while processing the resume: {str(e)}") |
|
st.exception(e) |
|
|
|
|
|
st.markdown("---") |
|
st.markdown("Made with β€οΈ using Streamlit, Hugging Face, and Advanced AI") |