Akshay Chame
π Add LinkedIn Profile Enhancer Streamlit app with all agents and dependencies
035c4af
# Job Matching Logic | |
from typing import Dict, Any, List, Tuple | |
import re | |
from collections import Counter | |
class JobMatcher: | |
"""Utility class for matching LinkedIn profiles with job descriptions""" | |
def __init__(self): | |
self.weight_config = { | |
'skills': 0.4, | |
'experience': 0.3, | |
'keywords': 0.2, | |
'education': 0.1 | |
} | |
self.skill_synonyms = { | |
'javascript': ['js', 'ecmascript', 'node.js', 'nodejs'], | |
'python': ['py', 'django', 'flask', 'fastapi'], | |
'react': ['reactjs', 'react.js'], | |
'angular': ['angularjs', 'angular.js'], | |
'machine learning': ['ml', 'ai', 'artificial intelligence'], | |
'database': ['db', 'sql', 'mysql', 'postgresql', 'mongodb'] | |
} | |
def calculate_match_score(self, profile_data: Dict[str, Any], job_description: str) -> Dict[str, Any]: | |
""" | |
Calculate comprehensive match score between profile and job | |
Args: | |
profile_data (Dict[str, Any]): Cleaned profile data | |
job_description (str): Job description text | |
Returns: | |
Dict[str, Any]: Match analysis with scores and details | |
""" | |
job_requirements = self._parse_job_requirements(job_description) | |
# Calculate individual scores | |
skills_score = self._calculate_skills_match( | |
profile_data.get('skills', []), | |
job_requirements['skills'] | |
) | |
experience_score = self._calculate_experience_match( | |
profile_data.get('experience', []), | |
job_requirements | |
) | |
keywords_score = self._calculate_keywords_match( | |
profile_data, | |
job_requirements['keywords'] | |
) | |
education_score = self._calculate_education_match( | |
profile_data.get('education', []), | |
job_requirements | |
) | |
# Calculate weighted overall score | |
overall_score = ( | |
skills_score['score'] * self.weight_config['skills'] + | |
experience_score['score'] * self.weight_config['experience'] + | |
keywords_score['score'] * self.weight_config['keywords'] + | |
education_score['score'] * self.weight_config['education'] | |
) | |
return { | |
'overall_score': round(overall_score, 2), | |
'breakdown': { | |
'skills': skills_score, | |
'experience': experience_score, | |
'keywords': keywords_score, | |
'education': education_score | |
}, | |
'recommendations': self._generate_match_recommendations( | |
skills_score, experience_score, keywords_score, education_score | |
), | |
'job_requirements': job_requirements | |
} | |
def find_skill_gaps(self, profile_skills: List[str], job_requirements: List[str]) -> Dict[str, List[str]]: | |
""" | |
Identify skill gaps between profile and job requirements | |
Args: | |
profile_skills (List[str]): Current profile skills | |
job_requirements (List[str]): Required job skills | |
Returns: | |
Dict[str, List[str]]: Missing and matching skills | |
""" | |
profile_skills_lower = [skill.lower() for skill in profile_skills] | |
job_skills_lower = [skill.lower() for skill in job_requirements] | |
# Find exact matches | |
matching_skills = [] | |
missing_skills = [] | |
for job_skill in job_skills_lower: | |
if job_skill in profile_skills_lower: | |
matching_skills.append(job_skill) | |
else: | |
# Check for synonyms | |
found_synonym = False | |
for profile_skill in profile_skills_lower: | |
if self._are_skills_similar(profile_skill, job_skill): | |
matching_skills.append(job_skill) | |
found_synonym = True | |
break | |
if not found_synonym: | |
missing_skills.append(job_skill) | |
return { | |
'matching_skills': matching_skills, | |
'missing_skills': missing_skills, | |
'match_percentage': len(matching_skills) / max(len(job_skills_lower), 1) * 100 | |
} | |
def suggest_profile_improvements(self, match_analysis: Dict[str, Any]) -> List[str]: | |
""" | |
Generate specific improvement suggestions based on match analysis | |
Args: | |
match_analysis (Dict[str, Any]): Match analysis results | |
Returns: | |
List[str]: Improvement suggestions | |
""" | |
suggestions = [] | |
breakdown = match_analysis['breakdown'] | |
# Skills suggestions | |
if breakdown['skills']['score'] < 70: | |
missing_skills = breakdown['skills']['details']['missing_skills'][:3] | |
if missing_skills: | |
suggestions.append( | |
f"Add these high-priority skills: {', '.join(missing_skills)}" | |
) | |
# Experience suggestions | |
if breakdown['experience']['score'] < 60: | |
suggestions.append( | |
"Highlight more relevant experience in your current/previous roles" | |
) | |
suggestions.append( | |
"Add quantified achievements that demonstrate impact" | |
) | |
# Keywords suggestions | |
if breakdown['keywords']['score'] < 50: | |
suggestions.append( | |
"Incorporate more industry-specific keywords throughout your profile" | |
) | |
# Education suggestions | |
if breakdown['education']['score'] < 40: | |
suggestions.append( | |
"Consider adding relevant certifications or courses" | |
) | |
return suggestions | |
def _parse_job_requirements(self, job_description: str) -> Dict[str, Any]: | |
"""Parse job description to extract requirements""" | |
requirements = { | |
'skills': [], | |
'keywords': [], | |
'experience_years': 0, | |
'education_level': '', | |
'industry': '', | |
'role_type': '' | |
} | |
# Extract skills (common technical skills) | |
skill_patterns = [ | |
r'\b(python|javascript|java|react|angular|node\.?js|sql|aws|docker|kubernetes)\b', | |
r'\b(machine learning|ai|data science|devops|full.?stack)\b', | |
r'\b(project management|agile|scrum|leadership)\b' | |
] | |
for pattern in skill_patterns: | |
matches = re.findall(pattern, job_description, re.IGNORECASE) | |
requirements['skills'].extend([match.lower() for match in matches]) | |
# Extract experience years | |
exp_pattern = r'(\d+)\+?\s*years?\s*(?:of\s*)?experience' | |
exp_matches = re.findall(exp_pattern, job_description, re.IGNORECASE) | |
if exp_matches: | |
requirements['experience_years'] = int(exp_matches[0]) | |
# Extract keywords (all meaningful words) | |
keywords = re.findall(r'\b[a-zA-Z]{3,}\b', job_description) | |
stop_words = {'the', 'and', 'for', 'with', 'you', 'will', 'are', 'have'} | |
requirements['keywords'] = [ | |
word.lower() for word in keywords | |
if word.lower() not in stop_words | |
] | |
# Remove duplicates | |
requirements['skills'] = list(set(requirements['skills'])) | |
requirements['keywords'] = list(set(requirements['keywords'])) | |
return requirements | |
def _calculate_skills_match(self, profile_skills: List[str], job_skills: List[str]) -> Dict[str, Any]: | |
"""Calculate skills match score""" | |
if not job_skills: | |
return {'score': 100, 'details': {'matching_skills': [], 'missing_skills': []}} | |
skill_gap_analysis = self.find_skill_gaps(profile_skills, job_skills) | |
return { | |
'score': skill_gap_analysis['match_percentage'], | |
'details': skill_gap_analysis | |
} | |
def _calculate_experience_match(self, profile_experience: List[Dict], job_requirements: Dict) -> Dict[str, Any]: | |
"""Calculate experience match score""" | |
score = 0 | |
details = { | |
'relevant_roles': 0, | |
'total_experience': 0, | |
'required_experience': job_requirements.get('experience_years', 0) | |
} | |
# Calculate total years of experience | |
total_years = 0 | |
relevant_roles = 0 | |
for exp in profile_experience: | |
duration_info = exp.get('duration_info', {}) | |
if duration_info.get('duration_months'): | |
total_years += duration_info['duration_months'] / 12 | |
# Check if role is relevant (simple keyword matching) | |
role_text = f"{exp.get('title', '')} {exp.get('description', '')}".lower() | |
job_keywords = job_requirements.get('keywords', []) | |
if any(keyword in role_text for keyword in job_keywords[:10]): | |
relevant_roles += 1 | |
details['total_experience'] = round(total_years, 1) | |
details['relevant_roles'] = relevant_roles | |
# Calculate score based on experience and relevance | |
if job_requirements.get('experience_years', 0) > 0: | |
exp_ratio = min(total_years / job_requirements['experience_years'], 1.0) | |
score = exp_ratio * 70 + (relevant_roles / max(len(profile_experience), 1)) * 30 | |
else: | |
score = 80 # Default good score if no specific experience required | |
return { | |
'score': round(score, 2), | |
'details': details | |
} | |
def _calculate_keywords_match(self, profile_data: Dict, job_keywords: List[str]) -> Dict[str, Any]: | |
"""Calculate keywords match score""" | |
if not job_keywords: | |
return {'score': 100, 'details': {'matched': 0, 'total': 0}} | |
# Extract all text from profile | |
profile_text = "" | |
for key, value in profile_data.items(): | |
if isinstance(value, str): | |
profile_text += f" {value}" | |
elif isinstance(value, list): | |
for item in value: | |
if isinstance(item, dict): | |
profile_text += f" {' '.join(str(v) for v in item.values())}" | |
else: | |
profile_text += f" {item}" | |
profile_text = profile_text.lower() | |
# Count keyword matches | |
matched_keywords = 0 | |
for keyword in job_keywords: | |
if keyword.lower() in profile_text: | |
matched_keywords += 1 | |
score = (matched_keywords / len(job_keywords)) * 100 | |
return { | |
'score': round(score, 2), | |
'details': { | |
'matched': matched_keywords, | |
'total': len(job_keywords), | |
'percentage': round(score, 2) | |
} | |
} | |
def _calculate_education_match(self, profile_education: List[Dict], job_requirements: Dict) -> Dict[str, Any]: | |
"""Calculate education match score""" | |
score = 70 # Default score | |
details = { | |
'has_degree': len(profile_education) > 0, | |
'degree_count': len(profile_education) | |
} | |
if profile_education: | |
score = 85 # Boost for having education | |
# Check for relevant fields | |
job_keywords = job_requirements.get('keywords', []) | |
for edu in profile_education: | |
edu_text = f"{edu.get('degree', '')} {edu.get('field', '')}".lower() | |
if any(keyword in edu_text for keyword in job_keywords[:5]): | |
score = 95 | |
break | |
return { | |
'score': score, | |
'details': details | |
} | |
def _are_skills_similar(self, skill1: str, skill2: str) -> bool: | |
"""Check if two skills are similar using synonyms""" | |
skill1_lower = skill1.lower() | |
skill2_lower = skill2.lower() | |
# Check direct synonyms | |
for main_skill, synonyms in self.skill_synonyms.items(): | |
if ((skill1_lower == main_skill or skill1_lower in synonyms) and | |
(skill2_lower == main_skill or skill2_lower in synonyms)): | |
return True | |
# Check partial matches | |
if skill1_lower in skill2_lower or skill2_lower in skill1_lower: | |
return True | |
return False | |
def _generate_match_recommendations(self, skills_score: Dict, experience_score: Dict, | |
keywords_score: Dict, education_score: Dict) -> List[str]: | |
"""Generate recommendations based on individual scores""" | |
recommendations = [] | |
if skills_score['score'] < 60: | |
recommendations.append("Focus on developing missing technical skills") | |
if experience_score['score'] < 50: | |
recommendations.append("Highlight more relevant work experience") | |
if keywords_score['score'] < 40: | |
recommendations.append("Optimize profile with job-specific keywords") | |
if education_score['score'] < 60: | |
recommendations.append("Consider additional certifications or training") | |
return recommendations | |