QuentinL52 commited on
Commit
3be1ab7
·
verified ·
1 Parent(s): e5394e3

Update src/cv_parsing_agents.py

Browse files
Files changed (1) hide show
  1. src/cv_parsing_agents.py +115 -63
src/cv_parsing_agents.py CHANGED
@@ -7,23 +7,8 @@ import logging
7
 
8
  logger = logging.getLogger(__name__)
9
 
10
- try:
11
- from src.crew.crew_pool import analyse_cv
12
- CREW_POOL_AVAILABLE = True
13
- logger.info("✅ crew_pool importé avec succès")
14
- except ImportError as e:
15
- logger.error(f"❌ Erreur import crew_pool: {e}")
16
- CREW_POOL_AVAILABLE = False
17
- analyse_cv = None
18
-
19
- try:
20
- from src.config import load_pdf
21
- CONFIG_AVAILABLE = True
22
- logger.info("✅ config importé avec succès")
23
- except ImportError as e:
24
- logger.error(f"❌ Erreur import config: {e}")
25
- CONFIG_AVAILABLE = False
26
- load_pdf = None
27
 
28
  def clean_dict_keys(data):
29
  """
@@ -51,64 +36,76 @@ class OptimizedCvParserAgent:
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}")
100
- if 'crew_output' in locals():
101
- logger.error(f"Données brutes reçues : {crew_output.raw}")
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
 
@@ -116,12 +113,15 @@ class OptimizedCvParserAgent:
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
 
@@ -129,10 +129,12 @@ class OptimizedCvParserAgent:
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
 
@@ -140,10 +142,13 @@ class OptimizedCvParserAgent:
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 []
@@ -175,6 +180,41 @@ class OptimizedCvParserAgent:
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:
@@ -192,6 +232,12 @@ class OptimizedCvParserAgent:
192
  return json_string_cleaned
193
 
194
  def get_processing_stats(self) -> dict:
 
 
 
 
 
 
195
  return {
196
  "optimization_enabled": True,
197
  "section_based_processing": True,
@@ -208,5 +254,11 @@ class CvParserAgent(OptimizedCvParserAgent):
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}")
 
 
 
 
 
 
 
7
 
8
  logger = logging.getLogger(__name__)
9
 
10
+ from src.crew.crew_pool import analyse_cv
11
+ from src.config import load_pdf
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
 
13
  def clean_dict_keys(data):
14
  """
 
36
  """
37
 
38
  def __init__(self, pdf_path: str):
39
+ """
40
+ Initialise l'agent de parsing de CV optimisé.
41
+
42
+ Args:
43
+ pdf_path (str): Chemin vers le fichier PDF à traiter
44
+
45
+ Raises:
46
+ ValueError: Si le chemin du fichier est invalide
47
+ """
48
  if not pdf_path or not isinstance(pdf_path, str):
49
  raise ValueError("Le chemin du fichier PDF doit être une chaîne non vide")
50
 
51
  self.pdf_path = pdf_path
 
 
 
 
 
52
 
53
  def process(self) -> dict:
54
+ """
55
+ Traite le fichier PDF pour en extraire le contenu sous forme de JSON optimisé.
56
+
57
+ Returns:
58
+ dict: Dictionnaire contenant les données extraites du CV
59
+
60
+ Raises:
61
+ FileNotFoundError: Si le fichier PDF n'existe pas
62
+ ValueError: Si le PDF est vide ou illisible
63
+ json.JSONDecodeError: Si le résultat n'est pas un JSON valide
64
+ Exception: Pour toute autre erreur de traitement
65
+ """
66
  logger.info(f"Début du traitement optimisé du CV : {self.pdf_path}")
67
 
68
  if not os.path.exists(self.pdf_path):
69
+ raise FileNotFoundError(f"Fichier PDF non trouvé: {self.pdf_path}")
70
+
71
+ cv_text_content = load_pdf(self.pdf_path)
72
+ if not cv_text_content or not cv_text_content.strip():
73
+ raise ValueError("Le PDF semble vide ou illisible")
74
+
75
+ logger.info(f"PDF chargé, {len(cv_text_content)} caractères extraits")
76
+
77
+ crew_output = analyse_cv(cv_text_content)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
78
 
79
+ if not crew_output or not hasattr(crew_output, 'raw') or not crew_output.raw.strip():
80
+ raise Exception("L'analyse par le crew n'a pas retourné de résultat.")
 
 
 
81
 
82
+ raw_string = crew_output.raw
83
+ logger.info(f"Résultat brut du crew optimisé: {raw_string[:200]}...")
84
+
85
+ json_string_cleaned = self._clean_json_string(raw_string)
86
+
87
+ profile_data = json.loads(json_string_cleaned)
88
+ logger.info("Parsing JSON optimisé réussi")
89
+
90
+ optimized_data = self._validate_and_enhance_data(profile_data)
91
+
92
+ return clean_dict_keys(optimized_data)
93
 
94
  def _validate_and_enhance_data(self, profile_data: dict) -> dict:
95
+ """
96
+ Valide et enrichit les données extraites du CV.
97
+
98
+ Args:
99
+ profile_data (dict): Données brutes extraites
100
+
101
+ Returns:
102
+ dict: Données validées et enrichies
103
+
104
+ Raises:
105
+ ValueError: Si la structure de données est invalide
106
+ """
107
  if not isinstance(profile_data, dict) or "candidat" not in profile_data:
108
+ raise ValueError("Structure de données invalide - clé 'candidat' manquante")
 
109
 
110
  candidat = profile_data["candidat"]
111
 
 
113
  "informations_personnelles", "compétences", "expériences",
114
  "projets", "formations", "reconversion"
115
  ]
116
+
117
  for section in required_sections:
118
  if section not in candidat or not candidat[section]:
119
  logger.warning(f"Section manquante ou vide: {section}")
120
  candidat[section] = self._get_default_section_data(section)
121
+
122
  self._normalize_competences(candidat.get("compétences", {}))
123
  self._normalize_experiences(candidat.get("expériences", []))
124
+
125
  logger.info("Validation et enrichissement des données terminés")
126
  return profile_data
127
 
 
129
  """Normalise la section compétences"""
130
  if not isinstance(competences, dict):
131
  return
132
+
133
  if "hard_skills" not in competences:
134
  competences["hard_skills"] = []
135
  if "soft_skills" not in competences:
136
  competences["soft_skills"] = []
137
+
138
  competences["hard_skills"] = [skill.strip() for skill in competences["hard_skills"] if skill and skill.strip()]
139
  competences["soft_skills"] = [skill.strip() for skill in competences["soft_skills"] if skill and skill.strip()]
140
 
 
142
  """Normalise la section expériences"""
143
  if not isinstance(experiences, list):
144
  return
145
+
146
  required_fields = ["Poste", "Entreprise", "start_date", "end_date", "responsabilités"]
147
+
148
  for exp in experiences:
149
  if not isinstance(exp, dict):
150
  continue
151
+
152
  for field in required_fields:
153
  if field not in exp or exp[field] in [None, "", []]:
154
  exp[field] = "Non spécifié" if field != "responsabilités" else []
 
180
  return defaults.get(section, {})
181
 
182
  def _clean_json_string(self, raw_string: str) -> str:
183
+ """
184
+ Nettoie une chaîne JSON brute en supprimant les blocs de code markdown.
185
+
186
+ Args:
187
+ raw_string (str): Chaîne brute à nettoyer
188
+
189
+ Returns:
190
+ str: Chaîne JSON nettoyée
191
+ """
192
+ json_string_cleaned = raw_string.strip()
193
+
194
+ if '```' in raw_string:
195
+ try:
196
+ if '```json' in raw_string:
197
+ json_part = raw_string.split('```json')[1].split('```')[0]
198
+ json_string_cleaned = json_part.strip()
199
+ else:
200
+ parts = raw_string.split('```')
201
+ if len(parts) >= 3:
202
+ json_string_cleaned = parts[1].strip()
203
+ except IndexError:
204
+ logger.warning("Format de code block détecté mais mal formé")
205
+
206
+ return json_string_cleaned
207
+
208
+ def _clean_json_string(self, raw_string: str) -> str:
209
+ """
210
+ Nettoie une chaîne JSON brute en supprimant les blocs de code markdown.
211
+
212
+ Args:
213
+ raw_string (str): Chaîne brute à nettoyer
214
+
215
+ Returns:
216
+ str: Chaîne JSON nettoyée
217
+ """
218
  json_string_cleaned = raw_string.strip()
219
 
220
  if '```' in raw_string:
 
232
  return json_string_cleaned
233
 
234
  def get_processing_stats(self) -> dict:
235
+ """
236
+ Retourne des statistiques sur l'optimisation du traitement.
237
+
238
+ Returns:
239
+ dict: Statistiques d'optimisation
240
+ """
241
  return {
242
  "optimization_enabled": True,
243
  "section_based_processing": True,
 
254
 
255
  if __name__ == "__main__":
256
  logger.info("Test du module cv_parsing_agents optimisé")
257
+
258
+ try:
259
+ agent = OptimizedCvParserAgent("/tmp/test.pdf")
260
+ stats = agent.get_processing_stats()
261
+ logger.info("✅ OptimizedCvParserAgent créé avec succès")
262
+ logger.info(f"✅ Statistiques d'optimisation: {stats}")
263
+ except Exception as e:
264
+ logger.error(f"❌ Erreur création OptimizedCvParserAgent: {e}")