hiresense-api / backend /services /learning_predictor.py
hemish22's picture
Super-squash branch 'main' using huggingface_hub
953f021
Raw History Blame Contribute Delete
7 kB
"""
HireSense AI — Learning Ability Predictor
Estimates how quickly a candidate can learn missing skills.
"""
from backend.services.skill_dictionary import get_skill_category, SKILL_CATEGORIES
class LearningPredictor:
"""Predicts learning ability based on skill graph and activity patterns."""
def predict(
self,
candidate_skills: list[str],
missing_skills: list[str],
github_data: dict = None,
leetcode_data: dict = None,
) -> dict:
"""
Predict learning ability and time-to-learn for missing skills.
"""
if not missing_skills:
return {
"learning_score": 95.0,
"predictions": [],
"factors": {
"base_score": 50,
"skill_breadth_bonus": 20,
"consistency_bonus": 15,
"problem_solving_bonus": 10,
},
"explanation": "Candidate already has all required skills. Excellent readiness.",
}
# Per-skill predictions
predictions = []
for skill in missing_skills:
pred = self._estimate_learning_time(skill, candidate_skills)
predictions.append(pred)
# Calculate factor bonuses
factors = self._calculate_factors(candidate_skills, github_data, leetcode_data)
# Overall learning score
learning_score = self._calculate_learning_score(predictions, factors)
explanation = self._generate_explanation(learning_score, predictions, factors)
return {
"learning_score": round(learning_score, 1),
"predictions": predictions,
"factors": factors,
"explanation": explanation,
}
def _estimate_learning_time(self, skill: str, existing_skills: list[str]) -> dict:
"""Estimate learning time for a specific skill."""
target_category = get_skill_category(skill)
# Count existing skills in the same category
related = [s for s in existing_skills if get_skill_category(s) == target_category]
if len(related) >= 3:
months = 1.5
confidence = "high"
reason = (
f"Already proficient in {len(related)} skills in '{target_category}' "
f"({', '.join(related[:3])}). Transfer learning will be fast."
)
elif len(related) >= 1:
months = 3.0
confidence = "medium"
reason = (
f"Has {len(related)} related skill(s) in '{target_category}' "
f"({', '.join(related)}). Foundation exists to build upon."
)
else:
# Check cross-category transfers
unique_categories = set(get_skill_category(s) for s in existing_skills)
if len(unique_categories) >= 4:
months = 4.0
confidence = "medium"
reason = (
f"No direct '{target_category}' experience, but has skills across "
f"{len(unique_categories)} categories — demonstrated versatility suggests adaptability."
)
else:
months = 6.0
confidence = "low"
reason = (
f"No experience in '{target_category}' and limited breadth. "
f"Will require structured learning and mentorship."
)
return {
"skill": skill,
"estimated_months": months,
"confidence": confidence,
"reason": reason,
"related_existing": related[:5],
}
def _calculate_factors(
self,
candidate_skills: list[str],
github_data: dict = None,
leetcode_data: dict = None,
) -> dict:
"""Calculate bonus factors for learning score."""
# Skill breadth bonus
unique_categories = set(get_skill_category(s) for s in candidate_skills)
skill_breadth_bonus = min(20, len(unique_categories) * 4)
# GitHub consistency bonus
consistency_bonus = 0
if github_data:
gh_scores = github_data.get("scores", {})
consistency = gh_scores.get("consistency", 0)
if consistency >= 70:
consistency_bonus = 15
elif consistency >= 40:
consistency_bonus = 8
# LeetCode problem-solving bonus
problem_solving_bonus = 0
if leetcode_data:
lc_scores = leetcode_data.get("scores", {})
lc_overall = lc_scores.get("overall", 0)
if lc_overall >= 70:
problem_solving_bonus = 15
elif lc_overall >= 40:
problem_solving_bonus = 8
return {
"base_score": 50,
"skill_breadth_bonus": skill_breadth_bonus,
"consistency_bonus": consistency_bonus,
"problem_solving_bonus": problem_solving_bonus,
}
def _calculate_learning_score(self, predictions: list, factors: dict) -> float:
"""Calculate the overall learning ability score."""
score = factors["base_score"]
score += factors["skill_breadth_bonus"]
score += factors["consistency_bonus"]
score += factors["problem_solving_bonus"]
# Penalty for hard-to-learn skills
for pred in predictions:
if pred["estimated_months"] > 4:
score -= 5
elif pred["estimated_months"] > 2:
score -= 2
return max(0, min(100, score))
def _generate_explanation(
self, score: float, predictions: list, factors: dict
) -> str:
"""Generate human-readable learning ability explanation."""
if score >= 80:
verdict = "High learning potential"
elif score >= 60:
verdict = "Good learning potential"
elif score >= 40:
verdict = "Moderate learning potential"
else:
verdict = "Learning may be challenging"
fast = [p for p in predictions if p["estimated_months"] <= 2]
slow = [p for p in predictions if p["estimated_months"] > 4]
parts = [f"{verdict} ({score:.0f}/100)."]
if fast:
parts.append(
f"Can quickly pick up: {', '.join(p['skill'] for p in fast)}."
)
if slow:
parts.append(
f"Will need more time for: {', '.join(p['skill'] for p in slow)}."
)
bonuses = []
if factors["consistency_bonus"] > 0:
bonuses.append("consistent GitHub activity")
if factors["problem_solving_bonus"] > 0:
bonuses.append("strong problem-solving skills")
if factors["skill_breadth_bonus"] >= 16:
bonuses.append("diverse skill set")
if bonuses:
parts.append(f"Positive signals: {', '.join(bonuses)}.")
return " ".join(parts)