hiresense-api / backend /services /scoring_engine.py
hemish22's picture
Super-squash branch 'main' using huggingface_hub
953f021
Raw History Blame Contribute Delete
6.9 kB
"""
HireSense AI — Scoring Engine Orchestrator
Combines all ML scoring services into a unified candidate score.
"""
from backend.services.skill_matcher import SkillMatcher
from backend.services.learning_predictor import LearningPredictor
from backend.services.inflation_detector import InflationDetector
class ScoringEngine:
"""Orchestrates all scoring services to produce a unified candidate assessment."""
WEIGHTS = {
"match_score": 0.40,
"github_score": 0.20,
"leetcode_score": 0.15,
"learning_score": 0.10,
"credibility_score": 0.15,
}
RECOMMENDATION_THRESHOLDS = {
80: "Strong Hire",
60: "Hire",
40: "Maybe",
0: "Pass",
}
def __init__(self):
self.skill_matcher = SkillMatcher()
self.learning_predictor = LearningPredictor()
self.inflation_detector = InflationDetector()
def score(
self,
parsed_resume: dict,
job_description: str,
github_data: dict = None,
leetcode_data: dict = None,
) -> dict:
"""
Run all scoring services and produce a unified assessment.
"""
candidate_skills = parsed_resume.get("skills", [])
# 1. Skill matching
github_languages = None
github_repo_context = None
if github_data and not github_data.get("user_not_found"):
github_languages = github_data.get("details", {}).get("languages")
github_repo_context = github_data.get("repo_context")
skill_match_result = self.skill_matcher.match(
candidate_skills=candidate_skills,
job_description=job_description,
github_languages=github_languages,
github_repo_context=github_repo_context,
)
# 2. Learning ability prediction
missing_skills = skill_match_result.get("missing_skills", [])
learning_result = self.learning_predictor.predict(
candidate_skills=candidate_skills,
missing_skills=missing_skills,
github_data=github_data if github_data and not github_data.get("user_not_found") else None,
leetcode_data=leetcode_data if leetcode_data and not leetcode_data.get("user_not_found") else None,
)
# 3. Inflation detection
inflation_result = self.inflation_detector.detect(
resume_skills=candidate_skills,
github_data=github_data if github_data and not github_data.get("user_not_found") else None,
leetcode_data=leetcode_data if leetcode_data and not leetcode_data.get("user_not_found") else None,
)
# 4. Extract sub-scores
match_score = skill_match_result.get("match_score", 0)
github_score = (
github_data.get("scores", {}).get("overall", 0)
if github_data and not github_data.get("user_not_found")
else 0
)
leetcode_score = (
leetcode_data.get("scores", {}).get("overall", 0)
if leetcode_data and not leetcode_data.get("user_not_found")
else 0
)
learning_score = learning_result.get("learning_score", 0)
credibility_score = inflation_result.get("credibility_score", 0)
# 5. Compute dynamic weights (adjust if data is missing)
weights = dict(self.WEIGHTS)
if not github_data or github_data.get("user_not_found"):
# Redistribute GitHub weight
weights["github_score"] = 0
weights["match_score"] += 0.10
weights["credibility_score"] += 0.05
weights["learning_score"] += 0.05
if not leetcode_data or leetcode_data.get("user_not_found"):
# Redistribute LeetCode weight
weights["leetcode_score"] = 0
weights["match_score"] += 0.08
weights["learning_score"] += 0.07
# 6. Weighted overall score
overall_score = (
match_score * weights["match_score"]
+ github_score * weights["github_score"]
+ leetcode_score * weights["leetcode_score"]
+ learning_score * weights["learning_score"]
+ credibility_score * weights["credibility_score"]
)
# 7. Generate recommendation
recommendation = self._get_recommendation(overall_score)
# 8. Overall explanation
explanation = self._generate_explanation(
overall_score, recommendation, match_score, github_score,
leetcode_score, learning_score, credibility_score,
)
return {
"overall_score": round(overall_score, 1),
"recommendation": recommendation,
"scores": {
"match_score": round(match_score, 1),
"github_score": round(github_score, 1),
"leetcode_score": round(leetcode_score, 1),
"learning_score": round(learning_score, 1),
"credibility_score": round(credibility_score, 1),
},
"skill_match": skill_match_result,
"learning_analysis": learning_result,
"credibility_analysis": inflation_result,
"explanation": explanation,
}
def _get_recommendation(self, score: float) -> str:
"""Get recommendation label based on overall score."""
for threshold in sorted(self.RECOMMENDATION_THRESHOLDS.keys(), reverse=True):
if score >= threshold:
return self.RECOMMENDATION_THRESHOLDS[threshold]
return "Pass"
def _generate_explanation(
self,
overall: float,
recommendation: str,
match: float,
github: float,
leetcode: float,
learning: float,
credibility: float,
) -> str:
"""Generate narrative summary of the candidate assessment."""
parts = [f"Overall assessment: {recommendation}."]
# Highlight strengths
strengths = []
if match >= 70:
strengths.append("strong skill match")
if github >= 70:
strengths.append("solid GitHub presence")
if leetcode >= 70:
strengths.append("strong problem-solving ability")
if learning >= 80:
strengths.append("high learning potential")
if credibility >= 85:
strengths.append("high resume credibility")
if strengths:
parts.append(f"Key strengths: {', '.join(strengths)}.")
# Highlight concerns
concerns = []
if match < 40:
concerns.append("significant skill gaps")
if github > 0 and github < 30:
concerns.append("limited GitHub activity")
if credibility < 50:
concerns.append("credibility concerns")
if concerns:
parts.append(f"Areas of concern: {', '.join(concerns)}.")
return " ".join(parts)