Spaces:
Sleeping
Sleeping
| 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} | |