File size: 4,125 Bytes
a8ee0db |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 |
import json
from datetime import datetime
# Pondérations basées sur la fiche projet
CONTEXT_WEIGHTS = {
"formations": 0.3,
"projets": 0.6,
"expériences": 0.8,
"multiple": 1.0
}
# Facteurs pour la formule de scoring
ALPHA = 0.5 # Poids du contexte
BETA = 0.3 # Poids de la fréquence
GAMMA = 0.2 # Poids de la profondeur (durée)
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
# 2. Fréquence de mention
frequency_score = self.full_text.count(skill_lower)
# 3. Profondeur d'utilisation (durée max en années)
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
# Normalisation simple (peut être affinée)
normalized_frequency = 1 - (1 / (1 + frequency_score))
normalized_depth = 1 - (1 / (1 + depth_score))
# Calcul du score final
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)
}
})
# Trier par score décroissant
scored_skills.sort(key=lambda x: x["score"], reverse=True)
return {"analyse_competences": scored_skills}
|