Spaces:
Sleeping
Sleeping
| from typing import Dict, List | |
| import numpy as np | |
| from sklearn.metrics.pairwise import cosine_similarity | |
| class RankingEngine: | |
| """Deterministic, weighted, rule-based scoring engine""" | |
| # Scoring weights | |
| WEIGHTS = { | |
| 'skills': 0.40, # 40% - Most important | |
| 'experience': 0.30, # 30% | |
| 'education': 0.15, # 15% | |
| 'projects': 0.15 # 15% | |
| } | |
| def calculate_semantic_similarity(resume_embedding: np.ndarray, job_embedding: np.ndarray) -> float: | |
| """Calculate cosine similarity between embeddings""" | |
| if resume_embedding.ndim == 1: | |
| resume_embedding = resume_embedding.reshape(1, -1) | |
| if job_embedding.ndim == 1: | |
| job_embedding = job_embedding.reshape(1, -1) | |
| similarity = cosine_similarity(resume_embedding, job_embedding)[0][0] | |
| return float(similarity) | |
| def score_skills(resume_chunks: List[Dict], job_chunks: List[Dict], | |
| resume_embeddings: np.ndarray, job_embeddings: np.ndarray) -> float: | |
| """Score skills match between resume and job""" | |
| # Filter skill-related chunks | |
| resume_skill_chunks = [c for c in resume_chunks if c['section'] == 'skills'] | |
| job_skill_chunks = [c for c in job_chunks if 'skill' in c.get('section', '').lower() or | |
| 'requirement' in c.get('section', '').lower()] | |
| if not resume_skill_chunks or not job_skill_chunks: | |
| return 0.0 | |
| # Get corresponding embeddings | |
| resume_skill_indices = [i for i, c in enumerate(resume_chunks) if c['section'] == 'skills'] | |
| job_skill_indices = [i for i, c in enumerate(job_chunks) if 'skill' in c.get('section', '').lower() or | |
| 'requirement' in c.get('section', '').lower()] | |
| resume_skill_emb = resume_embeddings[resume_skill_indices] | |
| job_skill_emb = job_embeddings[job_skill_indices] | |
| # Calculate average similarity | |
| similarities = [] | |
| for r_emb in resume_skill_emb: | |
| for j_emb in job_skill_emb: | |
| sim = RankingEngine.calculate_semantic_similarity(r_emb, j_emb) | |
| similarities.append(sim) | |
| avg_similarity = np.mean(similarities) if similarities else 0.0 | |
| # Convert to 0-100 scale | |
| return float(avg_similarity * 100) | |
| def score_experience(resume_chunks: List[Dict], job_chunks: List[Dict], | |
| resume_embeddings: np.ndarray, job_embeddings: np.ndarray) -> float: | |
| """Score experience match""" | |
| resume_exp_chunks = [c for c in resume_chunks if c['section'] == 'experience'] | |
| job_exp_chunks = [c for c in job_chunks if 'experience' in c.get('section', '').lower() or | |
| 'responsibility' in c.get('section', '').lower()] | |
| if not resume_exp_chunks: | |
| return 0.0 | |
| resume_exp_indices = [i for i, c in enumerate(resume_chunks) if c['section'] == 'experience'] | |
| resume_exp_emb = resume_embeddings[resume_exp_indices] | |
| if not job_exp_chunks: | |
| # If no specific experience section in job, compare with all job chunks | |
| job_exp_emb = job_embeddings | |
| else: | |
| job_exp_indices = [i for i, c in enumerate(job_chunks) if 'experience' in c.get('section', '').lower() or | |
| 'responsibility' in c.get('section', '').lower()] | |
| job_exp_emb = job_embeddings[job_exp_indices] | |
| similarities = [] | |
| for r_emb in resume_exp_emb: | |
| for j_emb in job_exp_emb: | |
| sim = RankingEngine.calculate_semantic_similarity(r_emb, j_emb) | |
| similarities.append(sim) | |
| avg_similarity = np.mean(similarities) if similarities else 0.0 | |
| return float(avg_similarity * 100) | |
| def score_education(resume_chunks: List[Dict], job_chunks: List[Dict], | |
| resume_embeddings: np.ndarray, job_embeddings: np.ndarray) -> float: | |
| """Score education match""" | |
| resume_edu_chunks = [c for c in resume_chunks if c['section'] == 'education'] | |
| if not resume_edu_chunks: | |
| return 50.0 # Neutral score if no education section | |
| resume_edu_indices = [i for i, c in enumerate(resume_chunks) if c['section'] == 'education'] | |
| resume_edu_emb = resume_embeddings[resume_edu_indices] | |
| # Compare with all job chunks | |
| similarities = [] | |
| for r_emb in resume_edu_emb: | |
| for j_emb in job_embeddings: | |
| sim = RankingEngine.calculate_semantic_similarity(r_emb, j_emb) | |
| similarities.append(sim) | |
| avg_similarity = np.mean(similarities) if similarities else 0.5 | |
| return float(avg_similarity * 100) | |
| def score_projects(resume_chunks: List[Dict], job_chunks: List[Dict], | |
| resume_embeddings: np.ndarray, job_embeddings: np.ndarray) -> float: | |
| """Score projects match""" | |
| resume_proj_chunks = [c for c in resume_chunks if c['section'] == 'projects'] | |
| if not resume_proj_chunks: | |
| return 50.0 # Neutral score if no projects section | |
| resume_proj_indices = [i for i, c in enumerate(resume_chunks) if c['section'] == 'projects'] | |
| resume_proj_emb = resume_embeddings[resume_proj_indices] | |
| similarities = [] | |
| for r_emb in resume_proj_emb: | |
| for j_emb in job_embeddings: | |
| sim = RankingEngine.calculate_semantic_similarity(r_emb, j_emb) | |
| similarities.append(sim) | |
| avg_similarity = np.mean(similarities) if similarities else 0.5 | |
| return float(avg_similarity * 100) | |
| def calculate_overall_score(breakdown: Dict[str, float]) -> float: | |
| """Calculate weighted overall score""" | |
| overall = sum(breakdown[key] * RankingEngine.WEIGHTS[key] for key in RankingEngine.WEIGHTS) | |
| return round(overall, 2) | |
| def rank_resume(resume_chunks: List[Dict], job_chunks: List[Dict], | |
| resume_embeddings: np.ndarray, job_embeddings: np.ndarray) -> Dict: | |
| """Main ranking function""" | |
| breakdown = { | |
| 'skills': RankingEngine.score_skills(resume_chunks, job_chunks, resume_embeddings, job_embeddings), | |
| 'experience': RankingEngine.score_experience(resume_chunks, job_chunks, resume_embeddings, job_embeddings), | |
| 'education': RankingEngine.score_education(resume_chunks, job_chunks, resume_embeddings, job_embeddings), | |
| 'projects': RankingEngine.score_projects(resume_chunks, job_chunks, resume_embeddings, job_embeddings) | |
| } | |
| overall_score = RankingEngine.calculate_overall_score(breakdown) | |
| return { | |
| 'score': overall_score, | |
| 'breakdown': breakdown | |
| } | |