You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

317 line
14KB

  1. using Services.ReActAgent;
  2. using System.Text.RegularExpressions;
  3. using System.Xml.Linq;
  4. using ToolsServices;
  5. namespace Services
  6. {
  7. public static class GenererCVService
  8. {
  9. #region Méthodes publiques
  10. public static async Task<bool> GenerateOneCV(string fullnameCV, string fullnameTemplateCV, string fullPathOutput, string titreCV, int nbrCompetencesMax, bool isCVAnonyme, bool isUsingIA)
  11. {
  12. LoggerService.LogInfo("GenererCVService.GenerateOneCV");
  13. try
  14. {
  15. var reActRagAgent = new Services.ReActAgent.ReActAgent();
  16. #region Logs + vérification de l'existence des fichiers
  17. LoggerService.LogInfo($"Génération d'un CV");
  18. LoggerService.LogInfo($"Fichier CV : {fullnameCV}");
  19. if (!File.Exists(fullnameCV))
  20. {
  21. LoggerService.LogWarning($"Le fichier CV n'existe pas : {fullnameCV}.");
  22. return false;
  23. }
  24. if (!File.Exists(fullnameTemplateCV))
  25. {
  26. LoggerService.LogWarning($"Le fichier Template de CV n'existe pas : {fullnameTemplateCV}.");
  27. return false;
  28. }
  29. if (!Directory.Exists(fullPathOutput))
  30. {
  31. LoggerService.LogWarning($"Le dossier des exports des nouveaux CV n'existe pas : {fullPathOutput}.");
  32. return false;
  33. }
  34. #endregion
  35. string nameNewFileXML = "";
  36. if (isUsingIA)
  37. {
  38. #region Vérification si injection de prompt
  39. bool isPromptInjectionCV = false;
  40. var cvText = reActRagAgent.CleanUserInput(FilesService.ExtractText(fullnameCV), out isPromptInjectionCV);
  41. if (isPromptInjectionCV)
  42. {
  43. LoggerService.LogWarning("Attention, le texte CV contient des éléments suspects");
  44. }
  45. #endregion
  46. #region Appel au LLM pour extraire les données et les adapter
  47. /*
  48. var prompt1 = $@"
  49. Tu es un assistant RH.
  50. Voici un CV brut :
  51. <<<
  52. {cvText}
  53. >>>
  54. Voici une offre d’emploi :
  55. <<<
  56. {ficheMissionText}
  57. >>>
  58. Ta mission :
  59. - Réécris le CV de façon à ce qu’il corresponde au mieux à l’offre
  60. - Mets en avant les compétences pertinentes
  61. - Réorganise les expériences importantes
  62. - Reformule le résumé professionnel avec les bons mots-clés
  63. - Supprime les parties non utiles pour ce poste
  64. Rends un CV **entièrement réécrit** au format texte brut. Ne commente pas.
  65. ";
  66. */
  67. var prompt1 = PromptService.GetPrompt(PromptService.ePrompt.GenererCVService_GenerateOneCV_Extract, cvText);
  68. LoggerService.LogInfo($"Génération du CV - phase A (extraction des infos) : {fullnameCV}");
  69. var (reponse1, m1) = await reActRagAgent.AppelerLLMAsync(ModelsUseCases.TypeUseCase.CV_To_Xml, true, prompt1, "Génération de 1 CV - phase A");
  70. var reponseCompleteClean1 = reponse1.Replace("\\n", "\n").Replace("\\r", "\r");
  71. #endregion
  72. #region Appel au LLM pour mettre au format XML
  73. /*
  74. var prompt2 = $@"
  75. Voici un CV rédigé en texte. Reformate-le en XML structuré selon ce modèle :
  76. <CV>
  77. <Identite>
  78. <Nom></Nom>
  79. <Prenom></Prenom>
  80. <Email></Email>
  81. <Telephone></Telephone>
  82. <Adresse></Adresse>
  83. <DateDeNaissance></DateDeNaissance>
  84. <Nationalite></Nationalite>
  85. </Identite>
  86. <Profil></Profil>
  87. <Competences>
  88. <Competence nom="" niveau="" />
  89. ...
  90. </Competences>
  91. <Langues>
  92. <Langue nom="" niveau="" />
  93. ...
  94. </Langues>
  95. <Experiences>
  96. <Experience>
  97. <Poste></Poste>
  98. <Entreprise></Entreprise>
  99. <Lieu></Lieu>
  100. <DateDebut></DateDebut>
  101. <DateFin></DateFin>
  102. <Description></Description>
  103. </Experience>
  104. ...
  105. </Experiences>
  106. <Formations>
  107. <Formation>
  108. <Diplome></Diplome>
  109. <Etablissement></Etablissement>
  110. <DateDebut></DateDebut>
  111. <DateFin></DateFin>
  112. </Formation>
  113. ...
  114. </Formations>
  115. <Certifications>
  116. <Certification>
  117. <Nom></Nom>
  118. <Organisme></Organisme>
  119. <Date></Date>
  120. </Certification>
  121. ...
  122. </Certifications>
  123. </CV>
  124. Si une information est absente, laisse le champ vide. Ne commente pas.
  125. CV :
  126. <<<
  127. {reponseCompleteClean1}
  128. >>>
  129. ";
  130. */
  131. var prompt2 = PromptService.GetPrompt(PromptService.ePrompt.GenererCVService_GenerateOneCV_Format, reponseCompleteClean1);
  132. LoggerService.LogInfo($"Génération du CV - phase B (formattage en XML) : {fullnameCV}");
  133. var (reponse,m2) = await reActRagAgent.AppelerLLMAsync(ModelsUseCases.TypeUseCase.CV_To_Xml, true, prompt2, "Génération de 1 CV - phase B");
  134. var reponseCompleteClean = reponse.Replace("\\n", "\n").Replace("\\r", "\r");
  135. reponseCompleteClean = reponseCompleteClean.Replace("&", " et ");
  136. reponseCompleteClean = reponseCompleteClean.Replace("</start_of_turn>", "");
  137. #endregion
  138. #region Enregistrer le XML
  139. // Création du XML
  140. nameNewFileXML = $"{fullnameCV}_{DateTime.Now.ToString("yyyyMMdd_hhmmss")}.xml";
  141. if (File.Exists(nameNewFileXML))
  142. File.Delete(nameNewFileXML);
  143. System.IO.File.WriteAllText(nameNewFileXML, reponseCompleteClean);
  144. #endregion
  145. }
  146. else
  147. {
  148. LoggerService.LogInfo($"Pas de modèle IA utilisé");
  149. #region Extraction du texte + générer un XML
  150. var cv = FilesService.ExtractText(fullnameCV);
  151. cv = Regex.Replace(cv, @"\s{2,}", " ").Trim();
  152. var xml = GenerateCvXml(cv);
  153. nameNewFileXML = $"{fullnameCV}_{DateTime.Now.ToString("yyyyMMdd_hhmmss")}.xml";
  154. if (File.Exists(nameNewFileXML))
  155. File.Delete(nameNewFileXML);
  156. xml.Save(nameNewFileXML);
  157. #endregion
  158. }
  159. #region Génération du CV sur la base du XML et d'un template
  160. // Génération du Word
  161. var t1 = System.IO.Path.GetFileNameWithoutExtension(fullnameCV);
  162. var t2 = System.IO.Path.GetFileNameWithoutExtension(fullnameTemplateCV);
  163. var fullnameNewCV = System.IO.Path.Combine(fullPathOutput, $"{t2}_{t1}.docx");
  164. var b = DocxService.Genere_Docx(fullnameTemplateCV, nameNewFileXML, fullnameNewCV, titreCV, nbrCompetencesMax, isCVAnonyme);
  165. if (File.Exists(nameNewFileXML))
  166. File.Delete(nameNewFileXML);
  167. #endregion
  168. return b;
  169. }
  170. catch (Exception ex)
  171. {
  172. LoggerService.LogError($"Erreur dans la génération du CV (GenererCVService.GenerateOneCV) : {ex.Message}");
  173. return false;
  174. }
  175. }
  176. #endregion
  177. #region Méthodes privées
  178. private static XElement GenerateCvXml(string text)
  179. {
  180. LoggerService.LogInfo("GenererCVService.GenerateCvXml");
  181. // === Nom / Prénom ===
  182. var nameMatch = Regex.Match(text, @"\b([A-Z][a-zéèàêâïî\-']+)\s+([A-ZÉÈÊËÀÂÄÎÏÔÖÙÛÜÇ\-']{2,})\b");
  183. string prenom = nameMatch.Success ? nameMatch.Groups[1].Value : "";
  184. string nom = nameMatch.Success ? nameMatch.Groups[2].Value : "";
  185. // === Email ===
  186. var emailMatch = Regex.Match(text, @"[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-z]{2,}");
  187. string email = emailMatch.Success ? emailMatch.Value : "";
  188. // === Téléphone ===
  189. var telMatch = Regex.Match(text, @"0[1-9](?:[\s.-]?\d{2}){4}");
  190. string telephone = telMatch.Success ? telMatch.Value : "";
  191. // === Profil ===
  192. string profil = ExtractSection(text, "PROFIL", "FORMATIONS");
  193. // === Compétences ===
  194. var competenceMatches = Regex.Matches(text, @"•\s?(.*?)(?=•|PROJECT MANAGER|EXPERIENCES CLES|PROFIL|$)");
  195. var competences = new XElement("Competences");
  196. foreach (Match match in competenceMatches)
  197. {
  198. string label = match.Groups[1].Value.Trim();
  199. if (!string.IsNullOrWhiteSpace(label) && label.Contains(":"))
  200. {
  201. string domaine = label.Split(":")[0].Trim();
  202. competences.Add(new XElement("Competence",
  203. new XAttribute("nom", domaine),
  204. new XAttribute("niveau", "Non précisé")
  205. ));
  206. }
  207. }
  208. // === Langues ===
  209. var langues = new XElement("Langues");
  210. if (text.Contains("Anglais")) langues.Add(new XElement("Langue", new XAttribute("nom", "Anglais"), new XAttribute("niveau", "Professionnel (Bulats B1)")));
  211. if (text.Contains("Espagnol")) langues.Add(new XElement("Langue", new XAttribute("nom", "Espagnol"), new XAttribute("niveau", "Scolaire")));
  212. // === Expériences ===
  213. var experiences = new XElement("Experiences");
  214. var expMatches = Regex.Matches(text, @"(\d{4})\s*-\s*(.+?)\s+([A-Z].*?)\s+-\s+(.+?)\s+(.*?)\s(?=\d{4}\s*-|$)", RegexOptions.Singleline);
  215. foreach (Match match in expMatches)
  216. {
  217. experiences.Add(new XElement("Experience",
  218. new XElement("Poste", match.Groups[2].Value.Trim()),
  219. new XElement("Entreprise", match.Groups[3].Value.Trim()),
  220. new XElement("Lieu", match.Groups[4].Value.Trim()),
  221. new XElement("DateDebut", match.Groups[1].Value.Trim()),
  222. new XElement("DateFin", ""), // à adapter si fin détectable
  223. new XElement("Description", match.Groups[5].Value.Trim())
  224. ));
  225. }
  226. // === Formations ===
  227. var formations = new XElement("Formations");
  228. var formationMatches = Regex.Matches(text, @"(\d{4})\s*–\s*(\d{4})\s*---\s*(.+?)\((\d{2})\)");
  229. foreach (Match match in formationMatches)
  230. {
  231. formations.Add(new XElement("Formation",
  232. new XElement("Diplome", "Non précisé"),
  233. new XElement("Etablissement", match.Groups[3].Value.Trim()),
  234. new XElement("DateDebut", match.Groups[1].Value.Trim()),
  235. new XElement("DateFin", match.Groups[2].Value.Trim())
  236. ));
  237. }
  238. // Ajout manuel du Master si non capturé
  239. if (!text.Contains("IAE de Montpellier"))
  240. {
  241. formations.Add(new XElement("Formation",
  242. new XElement("Diplome", "Master 2 : Administration des Entreprises"),
  243. new XElement("Etablissement", "IAE de Montpellier"),
  244. new XElement("DateDebut", "2009"),
  245. new XElement("DateFin", "2011")
  246. ));
  247. }
  248. return new XElement("CV",
  249. new XElement("Identite",
  250. new XElement("Nom", nom),
  251. new XElement("Prenom", prenom),
  252. new XElement("Email", email),
  253. new XElement("Telephone", telephone),
  254. new XElement("Adresse", ""),
  255. new XElement("DateDeNaissance", ""),
  256. new XElement("Nationalite", "")
  257. ),
  258. new XElement("Profil", profil),
  259. competences,
  260. langues,
  261. experiences,
  262. formations,
  263. new XElement("Certifications")
  264. );
  265. }
  266. private static string ExtractSection(string text, string startLabel, string endLabel)
  267. {
  268. LoggerService.LogInfo("GenererCVService.ExtractSection");
  269. int start = text.IndexOf(startLabel, StringComparison.OrdinalIgnoreCase);
  270. int end = text.IndexOf(endLabel, start + startLabel.Length, StringComparison.OrdinalIgnoreCase);
  271. if (start >= 0 && end > start)
  272. {
  273. return text.Substring(start + startLabel.Length, end - start - startLabel.Length).Trim();
  274. }
  275. return "";
  276. }
  277. #endregion
  278. }
  279. }