|
|
import json |
|
|
from datetime import datetime |
|
|
|
|
|
|
|
|
CONTEXT_WEIGHTS = { |
|
|
"formations": 0.3, |
|
|
"projets": 0.6, |
|
|
"expériences": 0.8, |
|
|
"multiple": 1.0 |
|
|
} |
|
|
|
|
|
|
|
|
ALPHA = 0.5 |
|
|
BETA = 0.3 |
|
|
GAMMA = 0.2 |
|
|
|
|
|
class ContextualScoringEngine: |
|
|
def __init__(self, cv_data: dict): |
|
|
self.cv_data = cv_data.get("candidat", {}) |
|
|
self.full_text = self._get_full_text_from_cv() |
|
|
|
|
|
def _get_full_text_from_cv(self) -> str: |
|
|
"""Concatène tout le contenu textuel du CV pour le comptage de fréquence.""" |
|
|
return json.dumps(self.cv_data, ensure_ascii=False).lower() |
|
|
|
|
|
def _parse_date(self, date_str: str) -> datetime: |
|
|
"""Parse une date, en gérant les cas spéciaux comme 'Aujourd'hui'.""" |
|
|
if not date_str or date_str.lower() == "non spécifié": |
|
|
return None |
|
|
if date_str.lower() == "aujourd'hui": |
|
|
return datetime.now() |
|
|
try: |
|
|
return datetime.strptime(date_str, "%Y") |
|
|
except ValueError: |
|
|
return None |
|
|
|
|
|
def _calculate_duration_in_years(self, start_date_str: str, end_date_str: str) -> float: |
|
|
"""Calcule la durée d'une expérience en années.""" |
|
|
start_date = self._parse_date(start_date_str) |
|
|
end_date = self._parse_date(end_date_str) |
|
|
if start_date and end_date: |
|
|
return abs((end_date - start_date).days / 365.25) |
|
|
return 0.5 |
|
|
|
|
|
def calculate_scores(self) -> dict: |
|
|
"""Calcule les scores pondérés pour toutes les hard skills.""" |
|
|
skills = self.cv_data.get("compétences", {}).get("hard_skills", []) |
|
|
if not skills: |
|
|
return {} |
|
|
|
|
|
scored_skills = [] |
|
|
for skill in skills: |
|
|
skill_lower = skill.lower() |
|
|
contexts = [] |
|
|
if skill_lower in json.dumps(self.cv_data.get("formations", []), ensure_ascii=False).lower(): |
|
|
contexts.append(CONTEXT_WEIGHTS["formations"]) |
|
|
if skill_lower in json.dumps(self.cv_data.get("projets", []), ensure_ascii=False).lower(): |
|
|
contexts.append(CONTEXT_WEIGHTS["projets"]) |
|
|
if skill_lower in json.dumps(self.cv_data.get("expériences", []), ensure_ascii=False).lower(): |
|
|
contexts.append(CONTEXT_WEIGHTS["expériences"]) |
|
|
|
|
|
if len(contexts) > 1: |
|
|
context_score = CONTEXT_WEIGHTS["multiple"] |
|
|
elif contexts: |
|
|
context_score = contexts[0] |
|
|
else: |
|
|
context_score = 0.1 |
|
|
|
|
|
|
|
|
frequency_score = self.full_text.count(skill_lower) |
|
|
|
|
|
|
|
|
max_duration = 0 |
|
|
for exp in self.cv_data.get("expériences", []): |
|
|
if skill_lower in json.dumps(exp, ensure_ascii=False).lower(): |
|
|
duration = self._calculate_duration_in_years(exp.get("start_date"), exp.get("end_date")) |
|
|
if duration > max_duration: |
|
|
max_duration = duration |
|
|
depth_score = max_duration |
|
|
|
|
|
|
|
|
normalized_frequency = 1 - (1 / (1 + frequency_score)) |
|
|
normalized_depth = 1 - (1 / (1 + depth_score)) |
|
|
|
|
|
|
|
|
final_score = (ALPHA * context_score) + \ |
|
|
(BETA * normalized_frequency) + \ |
|
|
(GAMMA * normalized_depth) |
|
|
|
|
|
scored_skills.append({ |
|
|
"skill": skill, |
|
|
"score": round(final_score, 2), |
|
|
"details": { |
|
|
"context_score": context_score, |
|
|
"frequency": frequency_score, |
|
|
"max_duration_years": round(depth_score, 1) |
|
|
} |
|
|
}) |
|
|
|
|
|
|
|
|
scored_skills.sort(key=lambda x: x["score"], reverse=True) |
|
|
return {"analyse_competences": scored_skills} |
|
|
|