| |
| 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) |
| |
| |
| 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 |
| ) |
| |
| |
| 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] |
| |
| |
| matching_skills = [] |
| missing_skills = [] |
| |
| for job_skill in job_skills_lower: |
| if job_skill in profile_skills_lower: |
| matching_skills.append(job_skill) |
| else: |
| |
| 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'] |
| |
| |
| 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)}" |
| ) |
| |
| |
| if breakdown['experience']['score'] < 60: |
| suggestions.append( |
| "Highlight more relevant experience in your current/previous roles" |
| ) |
| suggestions.append( |
| "Add quantified achievements that demonstrate impact" |
| ) |
| |
| |
| if breakdown['keywords']['score'] < 50: |
| suggestions.append( |
| "Incorporate more industry-specific keywords throughout your profile" |
| ) |
| |
| |
| 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': '' |
| } |
| |
| |
| 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]) |
| |
| |
| 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]) |
| |
| |
| 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 |
| ] |
| |
| |
| 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) |
| } |
| |
| |
| 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 |
| |
| |
| 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 |
| |
| |
| 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 |
| |
| 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}} |
| |
| |
| 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() |
| |
| |
| 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 |
| details = { |
| 'has_degree': len(profile_education) > 0, |
| 'degree_count': len(profile_education) |
| } |
| |
| if profile_education: |
| score = 85 |
| |
| |
| 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() |
| |
| |
| 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 |
| |
| |
| 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 |
|
|