|
import streamlit as st |
|
import pdfplumber |
|
import io |
|
import spacy |
|
from transformers import pipeline, AutoModelForCausalLM, AutoTokenizer |
|
import subprocess |
|
import sys |
|
import torch |
|
import re |
|
import pandas as pd |
|
import numpy as np |
|
import plotly.express as px |
|
import plotly.graph_objects as go |
|
from datetime import datetime |
|
import dateparser |
|
from sentence_transformers import SentenceTransformer |
|
import nltk |
|
from nltk.tokenize import word_tokenize |
|
from nltk.corpus import stopwords |
|
from sklearn.metrics.pairwise import cosine_similarity |
|
import faiss |
|
import requests |
|
from bs4 import BeautifulSoup |
|
import networkx as nx |
|
import Levenshtein |
|
import json |
|
import matplotlib.pyplot as plt |
|
from io import BytesIO |
|
import base64 |
|
from sentence_transformers import util |
|
|
|
|
|
@st.cache_resource |
|
def download_nltk_resources(): |
|
nltk.download('punkt') |
|
nltk.download('stopwords') |
|
nltk.download('wordnet') |
|
nltk.download('averaged_perceptron_tagger') |
|
|
|
download_nltk_resources() |
|
|
|
st.set_page_config( |
|
page_title="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(): |
|
summarizer = pipeline("summarization", model="facebook/bart-large-cnn") |
|
nlp = download_spacy_model() |
|
|
|
|
|
sentence_model = SentenceTransformer('paraphrase-MiniLM-L6-v2') |
|
|
|
|
|
try: |
|
device = "cuda" if torch.cuda.is_available() else "cpu" |
|
qwen_tokenizer = AutoTokenizer.from_pretrained("Qwen/Qwen3-8B") |
|
qwen_model = AutoModelForCausalLM.from_pretrained( |
|
"Qwen/Qwen3-8B", |
|
torch_dtype=torch.float16 if device == "cuda" else torch.float32, |
|
device_map="auto" |
|
) |
|
except Exception as e: |
|
st.error(f"Failed to load Qwen3-8B model: {str(e)}") |
|
qwen_tokenizer = None |
|
qwen_model = None |
|
|
|
return summarizer, nlp, qwen_tokenizer, qwen_model, sentence_model |
|
|
|
|
|
summarizer, nlp, qwen_tokenizer, qwen_model, 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.", |
|
"must_have": ["python", "git", "algorithms"], |
|
"nice_to_have": ["cloud", "java", "javascript"], |
|
"seniority_levels": { |
|
"Junior": "0-2 years of experience, familiar with basic programming concepts", |
|
"Mid-level": "3-5 years of experience, proficient in multiple languages, experience with system design", |
|
"Senior": "6+ years of experience, expert in software architecture, mentoring, and leading projects" |
|
} |
|
}, |
|
"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.", |
|
"must_have": ["ui", "ux", "prototyping"], |
|
"nice_to_have": ["figma", "sketch", "user research"], |
|
"seniority_levels": { |
|
"Junior": "0-2 years of experience, basic design skills, understanding of UX principles", |
|
"Mid-level": "3-5 years of experience, strong portfolio, experience with user research", |
|
"Senior": "6+ years of experience, leadership in design systems, driving design strategy" |
|
} |
|
}, |
|
"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.", |
|
"must_have": ["python", "statistics", "machine learning"], |
|
"nice_to_have": ["tensorflow", "pytorch", "r"], |
|
"seniority_levels": { |
|
"Junior": "0-2 years of experience, basic knowledge of statistics and ML algorithms", |
|
"Mid-level": "3-5 years of experience, model development, feature engineering", |
|
"Senior": "6+ years of experience, advanced ML techniques, research experience" |
|
} |
|
}, |
|
"Product Manager": { |
|
"skills": ["product strategy", "roadmap planning", "user stories", "agile", "market research", |
|
"stakeholder management", "analytics", "user experience", "a/b testing", "prioritization"], |
|
"description": "Seeking product managers who can drive product vision, strategy, and execution.", |
|
"must_have": ["product strategy", "roadmap planning", "stakeholder management"], |
|
"nice_to_have": ["agile", "analytics", "a/b testing"], |
|
"seniority_levels": { |
|
"Junior": "0-2 years of experience, assisting with feature definition and user stories", |
|
"Mid-level": "3-5 years of experience, owning products/features, market research", |
|
"Senior": "6+ years of experience, defining product vision, managing teams, strategic planning" |
|
} |
|
}, |
|
"DevOps Engineer": { |
|
"skills": ["linux", "aws", "docker", "kubernetes", "ci/cd", "terraform", |
|
"ansible", "monitoring", "scripting", "automation", "security"], |
|
"description": "Looking for DevOps engineers to build and maintain infrastructure and deployment pipelines.", |
|
"must_have": ["linux", "docker", "ci/cd"], |
|
"nice_to_have": ["kubernetes", "terraform", "aws"], |
|
"seniority_levels": { |
|
"Junior": "0-2 years of experience, basic system administration, scripting", |
|
"Mid-level": "3-5 years of experience, container orchestration, infrastructure as code", |
|
"Senior": "6+ years of experience, architecture design, security, team leadership" |
|
} |
|
} |
|
} |
|
|
|
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 analyze_resume(text, job_title): |
|
|
|
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) |
|
|
|
|
|
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) |
|
|
|
|
|
experiences = extract_experience(text) |
|
|
|
|
|
match_score = semantic_matching(text, job_title) |
|
|
|
|
|
seniority, years_experience, leadership_count, must_have_percentage = estimate_seniority(experiences, found_skills, job_title) |
|
|
|
|
|
skill_levels = extract_skill_levels(text, found_skills) |
|
|
|
|
|
inconsistencies = check_timeline_inconsistencies(experiences) |
|
|
|
|
|
company_verification = verify_companies(experiences) |
|
|
|
|
|
career_prediction = predict_career_trajectory(experiences, seniority, job_title) |
|
|
|
return { |
|
'found_skills': found_skills, |
|
'summary': " ".join(summaries), |
|
'experiences': experiences, |
|
'match_score': match_score, |
|
'seniority': seniority, |
|
'years_experience': years_experience, |
|
'skill_levels': skill_levels, |
|
'inconsistencies': inconsistencies, |
|
'company_verification': company_verification, |
|
'career_prediction': career_prediction |
|
} |
|
|
|
def generate_career_advice(resume_text, job_title, found_skills, missing_skills): |
|
if qwen_model is None or qwen_tokenizer is None: |
|
return "Career advice model not available. Please check the model installation." |
|
|
|
|
|
prompt = f""" |
|
You are a professional career advisor. Based on the resume and the target job position, |
|
provide personalized advice on skills to develop and suggest projects that would help the candidate |
|
become a better fit for the position. |
|
|
|
Resume summary: {resume_text[:1000]}... |
|
|
|
Target position: {job_title} |
|
|
|
Job requirements: {job_descriptions[job_title]['description']} |
|
|
|
Skills the candidate has: {', '.join(found_skills)} |
|
|
|
Skills the candidate needs to develop: {', '.join(missing_skills)} |
|
|
|
Provide the following: |
|
1. Specific advice on how to develop the missing skills |
|
2. 3-5 project ideas that would showcase these skills |
|
3. Resources for learning (courses, books, websites) |
|
""" |
|
|
|
|
|
try: |
|
inputs = qwen_tokenizer(prompt, return_tensors="pt").to(qwen_model.device) |
|
with torch.no_grad(): |
|
outputs = qwen_model.generate( |
|
**inputs, |
|
max_new_tokens=1024, |
|
temperature=0.7, |
|
top_p=0.9, |
|
do_sample=True |
|
) |
|
advice = qwen_tokenizer.decode(outputs[0][inputs.input_ids.shape[1]:], skip_special_tokens=True) |
|
return advice |
|
except Exception as e: |
|
return f"Failed to generate career advice: {str(e)}" |
|
|
|
|
|
st.title("📄 Resume Screener & Skill Extractor") |
|
|
|
|
|
st.markdown(""" |
|
This app helps recruiters analyze resumes by: |
|
- Extracting relevant skills for specific job positions |
|
- Generating a concise summary of the candidate's background |
|
- Identifying skill gaps for the selected role |
|
- Providing personalized career advice and project recommendations |
|
""") |
|
|
|
|
|
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"**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..."): |
|
|
|
text = extract_text_from_pdf(uploaded_file) |
|
|
|
|
|
resume_data = analyze_resume(text, job_title) |
|
|
|
|
|
missing_skills = [skill for skill in job_descriptions[job_title]["skills"] |
|
if skill not in resume_data['found_skills']] |
|
|
|
|
|
tab1, tab2, tab3, tab4, tab5, tab6 = st.tabs([ |
|
"📊 Skills Match", |
|
"📝 Resume Summary", |
|
"🎯 Skills Gap", |
|
"👨💼 Career Path", |
|
"🔍 Authentication", |
|
"🚀 Career Advice" |
|
]) |
|
|
|
with tab1: |
|
|
|
col1, col2 = st.columns(2) |
|
|
|
with col1: |
|
|
|
st.subheader("🎯 Matched Skills") |
|
if resume_data['found_skills']: |
|
for skill in resume_data['found_skills']: |
|
|
|
level = resume_data['skill_levels'].get(skill, 'intermediate') |
|
level_emoji = "🟢" if level == 'advanced' else "🟡" if level == 'intermediate' else "🟠" |
|
st.success(f"{level_emoji} {skill.title()} ({level.title()})") |
|
|
|
|
|
match_percentage = len(resume_data['found_skills']) / len(job_descriptions[job_title]["skills"]) * 100 |
|
st.metric("Skills Match", f"{match_percentage:.1f}%") |
|
else: |
|
st.warning("No direct skill matches found.") |
|
|
|
with col2: |
|
|
|
st.subheader("💡 Semantic Match") |
|
st.metric("Overall Match Score", f"{resume_data['match_score']:.1f}%") |
|
|
|
|
|
must_have_skills = job_descriptions[job_title]["must_have"] |
|
must_have_count = sum(1 for skill in must_have_skills if skill in resume_data['found_skills']) |
|
must_have_percentage = (must_have_count / len(must_have_skills)) * 100 |
|
|
|
st.write("Must-have skills:") |
|
st.progress(must_have_percentage / 100) |
|
st.write(f"{must_have_count} out of {len(must_have_skills)} ({must_have_percentage:.1f}%)") |
|
|
|
|
|
st.subheader("🧠 Seniority Assessment") |
|
st.info(f"**{resume_data['seniority']}** ({resume_data['years_experience']:.1f} years equivalent experience)") |
|
st.write(job_descriptions[job_title]["seniority_levels"][resume_data['seniority']]) |
|
|
|
with tab2: |
|
|
|
st.subheader("📝 Resume Summary") |
|
st.write(resume_data['summary']) |
|
|
|
|
|
st.subheader("⏳ Experience Timeline") |
|
if resume_data['experiences']: |
|
|
|
exp_data = [] |
|
for exp in resume_data['experiences']: |
|
if 'start_date' in exp and 'end_date' in exp: |
|
exp_data.append({ |
|
'Company': exp['company'], |
|
'Role': exp['role'], |
|
'Start Date': exp['start_date'].strftime('%b %Y') if exp['start_date'] else 'Unknown', |
|
'End Date': exp['end_date'].strftime('%b %Y') if exp['end_date'] != datetime.now() else 'Present', |
|
'Duration (months)': exp.get('duration_months', 'Unknown') |
|
}) |
|
else: |
|
exp_data.append({ |
|
'Company': exp['company'], |
|
'Role': exp['role'], |
|
'Duration': exp.get('duration', 'Unknown') |
|
}) |
|
|
|
if exp_data: |
|
exp_df = pd.DataFrame(exp_data) |
|
st.dataframe(exp_df) |
|
|
|
|
|
timeline_data = [exp for exp in resume_data['experiences'] if 'start_date' in exp and 'end_date' in exp] |
|
if timeline_data: |
|
|
|
timeline_data = sorted(timeline_data, key=lambda x: x['start_date']) |
|
|
|
|
|
fig = go.Figure() |
|
|
|
for i, exp in enumerate(timeline_data): |
|
fig.add_trace(go.Bar( |
|
x=[(exp['end_date'] - exp['start_date']).days / 30], |
|
y=[exp['company']], |
|
orientation='h', |
|
name=exp['role'], |
|
hovertext=f"{exp['role']} at {exp['company']}<br>{exp['start_date'].strftime('%b %Y')} - {exp['end_date'].strftime('%b %Y') if exp['end_date'] != datetime.now() else 'Present'}<br>Duration: {exp.get('duration_months', 0)} months", |
|
marker=dict(color=px.colors.qualitative.Plotly[i % len(px.colors.qualitative.Plotly)]) |
|
)) |
|
|
|
fig.update_layout( |
|
title="Career Timeline", |
|
xaxis_title="Duration (months)", |
|
yaxis_title="Company", |
|
height=400, |
|
margin=dict(l=0, r=0, b=0, t=30) |
|
) |
|
|
|
st.plotly_chart(fig, use_container_width=True) |
|
else: |
|
st.warning("No work experience data could be extracted.") |
|
|
|
with tab3: |
|
|
|
st.subheader("📌 Skills to Develop") |
|
|
|
|
|
col1, col2 = st.columns(2) |
|
|
|
with col1: |
|
|
|
if missing_skills: |
|
for skill in missing_skills: |
|
st.warning(f"➖ {skill.title()}") |
|
else: |
|
st.success("Great! The candidate has all the required skills!") |
|
|
|
with col2: |
|
|
|
st.subheader("🔍 Gap Analysis") |
|
|
|
|
|
missing_must_have = [skill for skill in job_descriptions[job_title]["must_have"] |
|
if skill not in resume_data['found_skills']] |
|
|
|
if missing_must_have: |
|
st.error("**Critical Skills Missing:**") |
|
for skill in missing_must_have: |
|
st.write(f"- {skill.title()}") |
|
|
|
st.markdown("These are must-have skills for this position.") |
|
else: |
|
st.success("Candidate has all the must-have skills for this position!") |
|
|
|
|
|
missing_nice_to_have = [skill for skill in job_descriptions[job_title]["nice_to_have"] |
|
if skill not in resume_data['found_skills']] |
|
|
|
if missing_nice_to_have: |
|
st.warning("**Nice-to-Have Skills Missing:**") |
|
for skill in missing_nice_to_have: |
|
st.write(f"- {skill.title()}") |
|
else: |
|
st.success("Candidate has all the nice-to-have skills!") |
|
|
|
with tab4: |
|
|
|
st.subheader("👨💼 Career Trajectory") |
|
|
|
|
|
st.info(resume_data['career_prediction']) |
|
|
|
|
|
st.subheader("📈 Experience Analysis") |
|
|
|
|
|
if len(resume_data['experiences']) >= 3: |
|
|
|
durations = [exp.get('duration_months', 0) for exp in resume_data['experiences'] |
|
if 'duration_months' in exp] |
|
|
|
if durations: |
|
avg_duration = sum(durations) / len(durations) |
|
|
|
if avg_duration < 12: |
|
st.warning(f"🚩 **Frequent Job Changes**: Average job duration is only {avg_duration:.1f} months") |
|
elif avg_duration < 24: |
|
st.warning(f"⚠️ **Moderate Job Hopping**: Average job duration is {avg_duration:.1f} months") |
|
else: |
|
st.success(f"✅ **Stable Employment**: Average job duration is {avg_duration:.1f} months") |
|
|
|
|
|
if resume_data['inconsistencies']: |
|
st.subheader("⚠️ Timeline Inconsistencies") |
|
for issue in resume_data['inconsistencies']: |
|
if issue['type'] == 'overlap': |
|
st.warning(issue['description']) |
|
elif issue['type'] == 'gap': |
|
st.info(issue['description']) |
|
|
|
with tab5: |
|
|
|
st.subheader("🔍 Resume Authentication") |
|
|
|
|
|
st.write("**Company Verification Results:**") |
|
|
|
if resume_data['company_verification']: |
|
|
|
suspicious_count = sum(1 for v in resume_data['company_verification'] |
|
if v['status'] == 'suspicious') |
|
|
|
if suspicious_count == 0: |
|
st.success("✅ All companies mentioned in the resume passed basic verification") |
|
else: |
|
st.warning(f"⚠️ {suspicious_count} companies require further verification") |
|
|
|
|
|
verification_data = [{ |
|
'Company': v['company'], |
|
'Status': v['status'].title(), |
|
'Notes': v['reason'] |
|
} for v in resume_data['company_verification']] |
|
|
|
st.dataframe(pd.DataFrame(verification_data)) |
|
else: |
|
st.info("No company information found for verification.") |
|
|
|
|
|
st.write("**Timeline Consistency Check:**") |
|
|
|
if not resume_data['inconsistencies']: |
|
st.success("✅ No timeline inconsistencies detected") |
|
else: |
|
st.warning(f"⚠️ {len(resume_data['inconsistencies'])} timeline inconsistencies found") |
|
for issue in resume_data['inconsistencies']: |
|
st.write(f"- {issue['description']}") |
|
|
|
with tab6: |
|
|
|
st.subheader("🚀 Career Advice and Project Recommendations") |
|
|
|
if st.button("Generate Career Advice"): |
|
with st.spinner("Generating personalized career advice..."): |
|
advice = generate_career_advice(text, job_title, resume_data['found_skills'], missing_skills) |
|
st.markdown(advice) |
|
|
|
except Exception as e: |
|
st.error(f"An error occurred while processing the resume: {str(e)}") |
|
|
|
|
|
st.markdown("---") |
|
st.markdown("Made with ❤️ using Streamlit and Hugging Face") |
|
|
|
|
|
def semantic_matching(resume_text, job_title): |
|
job_desc = job_descriptions[job_title]["description"] |
|
|
|
|
|
resume_embedding = sentence_model.encode(resume_text, convert_to_tensor=True) |
|
job_embedding = sentence_model.encode(job_desc, convert_to_tensor=True) |
|
|
|
|
|
cos_sim = cosine_similarity( |
|
resume_embedding.cpu().numpy().reshape(1, -1), |
|
job_embedding.cpu().numpy().reshape(1, -1) |
|
)[0][0] |
|
|
|
return cos_sim * 100 |
|
|
|
|
|
def extract_experience(text): |
|
|
|
|
|
exp_pattern = r"(?i)(.*?(?:inc|llc|ltd|company|corp|corporation|group)?)\s*(?:[|•-]\s*)?(.*?)(?:[|•-]\s*)((?:jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)[\w\s,]*\d{4}\s*(?:-|to|–)\s*(?:(?:jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)[\w\s,]*\d{4}|present))" |
|
|
|
experiences = [] |
|
for match in re.finditer(exp_pattern, text, re.IGNORECASE): |
|
company = match.group(1).strip() |
|
role = match.group(2).strip() |
|
duration = match.group(3).strip() |
|
|
|
|
|
try: |
|
date_range = duration.split('-') if '-' in duration else duration.split('to') if 'to' in duration else duration.split('–') |
|
start_date = dateparser.parse(date_range[0].strip()) |
|
|
|
if 'present' in date_range[1].lower(): |
|
end_date = datetime.now() |
|
else: |
|
end_date = dateparser.parse(date_range[1].strip()) |
|
|
|
if start_date and end_date: |
|
|
|
months = (end_date.year - start_date.year) * 12 + (end_date.month - start_date.month) |
|
|
|
experiences.append({ |
|
'company': company, |
|
'role': role, |
|
'start_date': start_date, |
|
'end_date': end_date, |
|
'duration_months': months |
|
}) |
|
except: |
|
|
|
experiences.append({ |
|
'company': company, |
|
'role': role, |
|
'duration': duration |
|
}) |
|
|
|
return experiences |
|
|
|
|
|
def estimate_seniority(experiences, found_skills, job_title): |
|
|
|
total_months = sum(exp.get('duration_months', 0) for exp in experiences if 'duration_months' in exp) |
|
total_years = total_months / 12 |
|
|
|
|
|
leadership_keywords = ['lead', 'senior', 'manager', 'head', 'principal', 'architect', 'director'] |
|
leadership_count = 0 |
|
|
|
for exp in experiences: |
|
role = exp.get('role', '').lower() |
|
for keyword in leadership_keywords: |
|
if keyword in role: |
|
leadership_count += 1 |
|
break |
|
|
|
|
|
must_have_skills = job_descriptions[job_title]["must_have"] |
|
must_have_count = sum(1 for skill in must_have_skills if skill in [s.lower() for s in found_skills]) |
|
must_have_percentage = (must_have_count / len(must_have_skills)) * 100 if must_have_skills else 0 |
|
|
|
|
|
if total_years < 3: |
|
seniority = "Junior" |
|
elif total_years < 6: |
|
seniority = "Mid-level" |
|
else: |
|
seniority = "Senior" |
|
|
|
|
|
if leadership_count >= 2 and seniority != "Senior": |
|
seniority = "Senior" if total_years >= 4 else seniority |
|
if must_have_percentage < 50 and seniority == "Senior": |
|
seniority = "Mid-level" |
|
|
|
return seniority, total_years, leadership_count, must_have_percentage |
|
|
|
|
|
def check_timeline_inconsistencies(experiences): |
|
if not experiences: |
|
return [] |
|
|
|
inconsistencies = [] |
|
sorted_experiences = sorted( |
|
[exp for exp in experiences if 'start_date' in exp and 'end_date' in exp], |
|
key=lambda x: x['start_date'] |
|
) |
|
|
|
for i in range(len(sorted_experiences) - 1): |
|
current = sorted_experiences[i] |
|
next_exp = sorted_experiences[i + 1] |
|
|
|
|
|
if current['end_date'] > next_exp['start_date']: |
|
overlap_months = (current['end_date'].year - next_exp['start_date'].year) * 12 + \ |
|
(current['end_date'].month - next_exp['start_date'].month) |
|
|
|
if overlap_months > 1: |
|
inconsistencies.append({ |
|
'type': 'overlap', |
|
'description': f"Overlapping roles: {current['company']} and {next_exp['company']} " + |
|
f"overlap by {overlap_months} months" |
|
}) |
|
|
|
|
|
for i in range(len(sorted_experiences) - 1): |
|
current = sorted_experiences[i] |
|
next_exp = sorted_experiences[i + 1] |
|
|
|
gap_months = (next_exp['start_date'].year - current['end_date'].year) * 12 + \ |
|
(next_exp['start_date'].month - current['end_date'].month) |
|
|
|
if gap_months > 3: |
|
inconsistencies.append({ |
|
'type': 'gap', |
|
'description': f"Employment gap of {gap_months} months between " + |
|
f"{current['company']} and {next_exp['company']}" |
|
}) |
|
|
|
return inconsistencies |
|
|
|
|
|
def verify_companies(experiences): |
|
verification_results = [] |
|
|
|
for exp in experiences: |
|
company = exp.get('company', '') |
|
if not company: |
|
continue |
|
|
|
|
|
if len(company) < 3: |
|
verification_results.append({ |
|
'company': company, |
|
'status': 'suspicious', |
|
'reason': 'Company name too short' |
|
}) |
|
continue |
|
|
|
|
|
fake_patterns = ['abc company', 'xyz corp', 'my company', 'personal project'] |
|
if any(pattern in company.lower() for pattern in fake_patterns): |
|
verification_results.append({ |
|
'company': company, |
|
'status': 'suspicious', |
|
'reason': 'Matches pattern of fake company names' |
|
}) |
|
continue |
|
|
|
|
|
|
|
verification_results.append({ |
|
'company': company, |
|
'status': 'verified', |
|
'reason': 'Passed basic verification checks' |
|
}) |
|
|
|
return verification_results |
|
|
|
|
|
def extract_skill_levels(text, skills): |
|
skill_levels = {} |
|
proficiency_indicators = { |
|
'basic': ['basic', 'familiar', 'beginner', 'fundamentals', 'exposure'], |
|
'intermediate': ['intermediate', 'proficient', 'experienced', 'competent', 'skilled'], |
|
'advanced': ['advanced', 'expert', 'mastery', 'specialist', 'lead', 'senior'] |
|
} |
|
|
|
for skill in skills: |
|
|
|
sentences = re.findall(r'[^.!?]*%s[^.!?]*[.!?]' % re.escape(skill), text.lower()) |
|
|
|
|
|
level = 'intermediate' |
|
|
|
|
|
years_pattern = re.compile(r'(\d+)\s*(?:\+)?\s*years?(?:\s+of)?\s+(?:experience|exp)?\s+(?:with|in|using)?\s+%s' % re.escape(skill), re.IGNORECASE) |
|
for sentence in sentences: |
|
years_match = years_pattern.search(sentence) |
|
if years_match: |
|
years = int(years_match.group(1)) |
|
if years < 2: |
|
level = 'basic' |
|
elif years < 5: |
|
level = 'intermediate' |
|
else: |
|
level = 'advanced' |
|
break |
|
|
|
|
|
if level == 'intermediate': |
|
for level_name, indicators in proficiency_indicators.items(): |
|
for indicator in indicators: |
|
pattern = re.compile(r'%s\s+(?:\w+\s+){0,3}%s' % (indicator, re.escape(skill)), re.IGNORECASE) |
|
if any(pattern.search(sentence) for sentence in sentences): |
|
level = level_name |
|
break |
|
if level != 'intermediate': |
|
break |
|
|
|
skill_levels[skill] = level |
|
|
|
return skill_levels |
|
|
|
|
|
def predict_career_trajectory(experiences, seniority, job_title): |
|
if not experiences: |
|
return "Unable to predict trajectory due to insufficient experience data." |
|
|
|
|
|
roles = [exp.get('role', '').lower() for exp in experiences if 'role' in exp] |
|
|
|
|
|
if len(roles) < 2: |
|
if seniority == "Junior": |
|
next_role = "Mid-level " + job_title |
|
elif seniority == "Mid-level": |
|
next_role = "Senior " + job_title |
|
else: |
|
leadership_titles = { |
|
"Software Engineer": "Technical Lead or Engineering Manager", |
|
"Data Scientist": "Lead Data Scientist or Data Science Manager", |
|
"Interaction Designer": "Design Lead or UX Director", |
|
"Product Manager": "Senior Product Manager or Director of Product", |
|
"DevOps Engineer": "DevOps Lead or Infrastructure Architect" |
|
} |
|
next_role = leadership_titles.get(job_title, f"Director of {job_title}") |
|
|
|
return f"Based on current seniority level, the next logical role could be: {next_role}" |
|
|
|
|
|
progression_indicators = ['junior', 'senior', 'lead', 'manager', 'director', 'vp', 'head', 'chief'] |
|
current_level = -1 |
|
|
|
for role in roles: |
|
for i, indicator in enumerate(progression_indicators): |
|
if indicator in role: |
|
if i > current_level: |
|
current_level = i |
|
|
|
|
|
if current_level < len(progression_indicators) - 1: |
|
next_level = progression_indicators[current_level + 1] |
|
|
|
|
|
if next_level == 'senior' and 'senior' not in roles[-1].lower(): |
|
next_role = f"Senior {job_title}" |
|
elif next_level == 'lead': |
|
next_role = f"{job_title} Lead" |
|
elif next_level == 'manager': |
|
if job_title == "Software Engineer": |
|
next_role = "Engineering Manager" |
|
else: |
|
next_role = f"{job_title} Manager" |
|
elif next_level == 'director': |
|
next_role = f"Director of {job_title}s" |
|
elif next_level == 'vp': |
|
next_role = f"VP of {job_title}s" |
|
elif next_level == 'head': |
|
next_role = f"Head of {job_title}" |
|
elif next_level == 'chief': |
|
if job_title == "Software Engineer": |
|
next_role = "CTO (Chief Technology Officer)" |
|
elif job_title == "Data Scientist": |
|
next_role = "Chief Data Officer" |
|
elif job_title == "Product Manager": |
|
next_role = "Chief Product Officer" |
|
else: |
|
next_role = f"Chief {job_title} Officer" |
|
else: |
|
next_role = f"{next_level.title()} {job_title}" |
|
else: |
|
next_role = "Executive Leadership or Strategic Advisory roles" |
|
|
|
return f"Based on career progression, the next logical role could be: {next_role}" |