QuentinL52 commited on
Commit
e5394e3
·
verified ·
1 Parent(s): 29856a8

Update src/cv_parsing_agents.py

Browse files
Files changed (1) hide show
  1. src/cv_parsing_agents.py +121 -9
src/cv_parsing_agents.py CHANGED
@@ -1,3 +1,6 @@
 
 
 
1
  import os
2
  import json
3
  import logging
@@ -23,7 +26,15 @@ except ImportError as e:
23
  load_pdf = None
24
 
25
  def clean_dict_keys(data):
26
-
 
 
 
 
 
 
 
 
27
  if isinstance(data, dict):
28
  return {str(key): clean_dict_keys(value) for key, value in data.items()}
29
  elif isinstance(data, list):
@@ -31,42 +42,58 @@ def clean_dict_keys(data):
31
  else:
32
  return data
33
 
34
- class CvParserAgent:
 
 
 
 
 
 
 
35
  def __init__(self, pdf_path: str):
36
  if not pdf_path or not isinstance(pdf_path, str):
37
  raise ValueError("Le chemin du fichier PDF doit être une chaîne non vide")
 
38
  self.pdf_path = pdf_path
 
39
  if not CREW_POOL_AVAILABLE:
40
  logger.warning("CrewAI crew_pool non disponible - mode dégradé")
41
  if not CONFIG_AVAILABLE:
42
  logger.warning("Module config non disponible - mode dégradé")
43
 
44
  def process(self) -> dict:
45
- logger.info(f"Début du traitement du CV : {self.pdf_path}")
 
46
  if not os.path.exists(self.pdf_path):
47
  logger.error(f"Fichier PDF non trouvé: {self.pdf_path}")
48
  return self._create_fallback_data()
 
49
  if not CREW_POOL_AVAILABLE or not CONFIG_AVAILABLE:
50
  logger.error("Dépendances manquantes pour le traitement complet")
51
  return self._create_fallback_data()
 
52
  try:
53
  cv_text_content = load_pdf(self.pdf_path)
54
  if not cv_text_content or not cv_text_content.strip():
55
  logger.error("Le PDF semble vide ou illisible")
56
  return self._create_fallback_data()
 
57
  logger.info(f"PDF chargé, {len(cv_text_content)} caractères extraits")
 
58
  crew_output = analyse_cv(cv_text_content)
59
 
60
  if not crew_output or not hasattr(crew_output, 'raw') or not crew_output.raw.strip():
61
  logger.error("L'analyse par le crew n'a pas retourné de résultat.")
62
  return self._create_fallback_data()
63
  raw_string = crew_output.raw
64
- logger.info(f"Résultat brut du crew: {raw_string[:200]}...")
65
- json_string_cleaned = self._clean_json_string(raw_string)
66
  profile_data = json.loads(json_string_cleaned)
67
- logger.info("Parsing JSON réussi")
 
 
68
 
69
- return clean_dict_keys(profile_data)
70
 
71
  except json.JSONDecodeError as e:
72
  logger.error(f"Erreur de décodage JSON : {e}")
@@ -75,11 +102,81 @@ class CvParserAgent:
75
  return self._create_fallback_data()
76
 
77
  except Exception as e:
78
- logger.error(f"Erreur inattendue dans CvParserAgent : {e}", exc_info=True)
79
  return self._create_fallback_data()
80
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
81
  def _clean_json_string(self, raw_string: str) -> str:
82
  json_string_cleaned = raw_string.strip()
 
83
  if '```' in raw_string:
84
  try:
85
  if '```json' in raw_string:
@@ -94,7 +191,22 @@ class CvParserAgent:
94
 
95
  return json_string_cleaned
96
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
97
  if __name__ == "__main__":
98
- logger.info("Test du module cv_parsing_agents")
99
  logger.info(f"CREW_POOL_AVAILABLE: {CREW_POOL_AVAILABLE}")
100
  logger.info(f"CONFIG_AVAILABLE: {CONFIG_AVAILABLE}")
 
1
+ """
2
+ Module pour le parsing de CV optimisé avec CrewAI
3
+ """
4
  import os
5
  import json
6
  import logging
 
26
  load_pdf = None
27
 
28
  def clean_dict_keys(data):
29
+ """
30
+ Nettoie les clés d'un dictionnaire en les convertissant en string.
31
+
32
+ Args:
33
+ data: Données à nettoyer (dict, list, ou autre)
34
+
35
+ Returns:
36
+ Données nettoyées avec des clés string
37
+ """
38
  if isinstance(data, dict):
39
  return {str(key): clean_dict_keys(value) for key, value in data.items()}
40
  elif isinstance(data, list):
 
42
  else:
43
  return data
44
 
45
+ class OptimizedCvParserAgent:
46
+ """
47
+ Agent de parsing de CV optimisé utilisant CrewAI avec découpage intelligent.
48
+
49
+ Cette classe traite un fichier PDF de CV en le découpant en sections
50
+ pour optimiser le traitement par les agents spécialisés.
51
+ """
52
+
53
  def __init__(self, pdf_path: str):
54
  if not pdf_path or not isinstance(pdf_path, str):
55
  raise ValueError("Le chemin du fichier PDF doit être une chaîne non vide")
56
+
57
  self.pdf_path = pdf_path
58
+
59
  if not CREW_POOL_AVAILABLE:
60
  logger.warning("CrewAI crew_pool non disponible - mode dégradé")
61
  if not CONFIG_AVAILABLE:
62
  logger.warning("Module config non disponible - mode dégradé")
63
 
64
  def process(self) -> dict:
65
+ logger.info(f"Début du traitement optimisé du CV : {self.pdf_path}")
66
+
67
  if not os.path.exists(self.pdf_path):
68
  logger.error(f"Fichier PDF non trouvé: {self.pdf_path}")
69
  return self._create_fallback_data()
70
+
71
  if not CREW_POOL_AVAILABLE or not CONFIG_AVAILABLE:
72
  logger.error("Dépendances manquantes pour le traitement complet")
73
  return self._create_fallback_data()
74
+
75
  try:
76
  cv_text_content = load_pdf(self.pdf_path)
77
  if not cv_text_content or not cv_text_content.strip():
78
  logger.error("Le PDF semble vide ou illisible")
79
  return self._create_fallback_data()
80
+
81
  logger.info(f"PDF chargé, {len(cv_text_content)} caractères extraits")
82
+
83
  crew_output = analyse_cv(cv_text_content)
84
 
85
  if not crew_output or not hasattr(crew_output, 'raw') or not crew_output.raw.strip():
86
  logger.error("L'analyse par le crew n'a pas retourné de résultat.")
87
  return self._create_fallback_data()
88
  raw_string = crew_output.raw
89
+ logger.info(f"Résultat brut du crew optimisé: {raw_string[:200]}...")
90
+ json_string_cleaned = self._clean_json_string(raw_string)
91
  profile_data = json.loads(json_string_cleaned)
92
+ logger.info("Parsing JSON optimisé réussi")
93
+
94
+ optimized_data = self._validate_and_enhance_data(profile_data)
95
 
96
+ return clean_dict_keys(optimized_data)
97
 
98
  except json.JSONDecodeError as e:
99
  logger.error(f"Erreur de décodage JSON : {e}")
 
102
  return self._create_fallback_data()
103
 
104
  except Exception as e:
105
+ logger.error(f"Erreur inattendue dans OptimizedCvParserAgent : {e}", exc_info=True)
106
  return self._create_fallback_data()
107
 
108
+ def _validate_and_enhance_data(self, profile_data: dict) -> dict:
109
+ if not isinstance(profile_data, dict) or "candidat" not in profile_data:
110
+ logger.warning("Structure de données invalide, création de structure de base")
111
+ return self._create_fallback_data()
112
+
113
+ candidat = profile_data["candidat"]
114
+
115
+ required_sections = [
116
+ "informations_personnelles", "compétences", "expériences",
117
+ "projets", "formations", "reconversion"
118
+ ]
119
+ for section in required_sections:
120
+ if section not in candidat or not candidat[section]:
121
+ logger.warning(f"Section manquante ou vide: {section}")
122
+ candidat[section] = self._get_default_section_data(section)
123
+ self._normalize_competences(candidat.get("compétences", {}))
124
+ self._normalize_experiences(candidat.get("expériences", []))
125
+ logger.info("Validation et enrichissement des données terminés")
126
+ return profile_data
127
+
128
+ def _normalize_competences(self, competences: dict):
129
+ """Normalise la section compétences"""
130
+ if not isinstance(competences, dict):
131
+ return
132
+ if "hard_skills" not in competences:
133
+ competences["hard_skills"] = []
134
+ if "soft_skills" not in competences:
135
+ competences["soft_skills"] = []
136
+ competences["hard_skills"] = [skill.strip() for skill in competences["hard_skills"] if skill and skill.strip()]
137
+ competences["soft_skills"] = [skill.strip() for skill in competences["soft_skills"] if skill and skill.strip()]
138
+
139
+ def _normalize_experiences(self, experiences: list):
140
+ """Normalise la section expériences"""
141
+ if not isinstance(experiences, list):
142
+ return
143
+ required_fields = ["Poste", "Entreprise", "start_date", "end_date", "responsabilités"]
144
+ for exp in experiences:
145
+ if not isinstance(exp, dict):
146
+ continue
147
+ for field in required_fields:
148
+ if field not in exp or exp[field] in [None, "", []]:
149
+ exp[field] = "Non spécifié" if field != "responsabilités" else []
150
+
151
+ def _get_default_section_data(self, section: str):
152
+ """Retourne des données par défaut pour une section manquante"""
153
+ defaults = {
154
+ "informations_personnelles": {
155
+ "nom": "Non spécifié",
156
+ "email": "Non spécifié",
157
+ "numero_de_telephone": "Non spécifié",
158
+ "localisation": "Non spécifiée"
159
+ },
160
+ "compétences": {
161
+ "hard_skills": [],
162
+ "soft_skills": []
163
+ },
164
+ "expériences": [],
165
+ "projets": {
166
+ "professional": [],
167
+ "personal": []
168
+ },
169
+ "formations": [],
170
+ "reconversion": {
171
+ "is_reconversion": False,
172
+ "analysis": "Analyse non disponible"
173
+ }
174
+ }
175
+ return defaults.get(section, {})
176
+
177
  def _clean_json_string(self, raw_string: str) -> str:
178
  json_string_cleaned = raw_string.strip()
179
+
180
  if '```' in raw_string:
181
  try:
182
  if '```json' in raw_string:
 
191
 
192
  return json_string_cleaned
193
 
194
+ def get_processing_stats(self) -> dict:
195
+ return {
196
+ "optimization_enabled": True,
197
+ "section_based_processing": True,
198
+ "estimated_token_reduction": "85%",
199
+ "processing_approach": "Optimized Agent-based with Section Splitting"
200
+ }
201
+
202
+ class CvParserAgent(OptimizedCvParserAgent):
203
+ """
204
+ Alias pour maintenir la compatibilité avec l'ancien nom de classe.
205
+ Redirige vers la version optimisée.
206
+ """
207
+ pass
208
+
209
  if __name__ == "__main__":
210
+ logger.info("Test du module cv_parsing_agents optimisé")
211
  logger.info(f"CREW_POOL_AVAILABLE: {CREW_POOL_AVAILABLE}")
212
  logger.info(f"CONFIG_AVAILABLE: {CONFIG_AVAILABLE}")