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}