using Services.ReActAgent; using System.Text.RegularExpressions; using System.Xml.Linq; using ToolsServices; namespace Services { public static class GenererCVService { #region Méthodes publiques public static async Task GenerateOneCV(string fullnameCV, string fullnameTemplateCV, string fullPathOutput, string titreCV, int nbrCompetencesMax, bool isCVAnonyme, bool isUsingIA) { LoggerService.LogInfo("GenererCVService.GenerateOneCV"); try { var reActRagAgent = new Services.ReActAgent.ReActAgent(); #region Logs + vérification de l'existence des fichiers LoggerService.LogInfo($"Génération d'un CV"); LoggerService.LogInfo($"Fichier CV : {fullnameCV}"); if (!File.Exists(fullnameCV)) { LoggerService.LogWarning($"Le fichier CV n'existe pas : {fullnameCV}."); return false; } if (!File.Exists(fullnameTemplateCV)) { LoggerService.LogWarning($"Le fichier Template de CV n'existe pas : {fullnameTemplateCV}."); return false; } if (!Directory.Exists(fullPathOutput)) { LoggerService.LogWarning($"Le dossier des exports des nouveaux CV n'existe pas : {fullPathOutput}."); return false; } #endregion string nameNewFileXML = ""; if (isUsingIA) { #region Vérification si injection de prompt bool isPromptInjectionCV = false; var cvText = reActRagAgent.CleanUserInput(FilesService.ExtractText(fullnameCV), out isPromptInjectionCV); if (isPromptInjectionCV) { LoggerService.LogWarning("Attention, le texte CV contient des éléments suspects"); } #endregion #region Appel au LLM pour extraire les données et les adapter /* var prompt1 = $@" Tu es un assistant RH. Voici un CV brut : <<< {cvText} >>> Voici une offre d’emploi : <<< {ficheMissionText} >>> Ta mission : - Réécris le CV de façon à ce qu’il corresponde au mieux à l’offre - Mets en avant les compétences pertinentes - Réorganise les expériences importantes - Reformule le résumé professionnel avec les bons mots-clés - Supprime les parties non utiles pour ce poste Rends un CV **entièrement réécrit** au format texte brut. Ne commente pas. "; */ var prompt1 = PromptService.GetPrompt(PromptService.ePrompt.GenererCVService_GenerateOneCV_Extract, cvText); LoggerService.LogInfo($"Génération du CV - phase A (extraction des infos) : {fullnameCV}"); var (reponse1, m1) = await reActRagAgent.AppelerLLMAsync(ModelsUseCases.TypeUseCase.CV_To_Xml, true, prompt1, "Génération de 1 CV - phase A"); var reponseCompleteClean1 = reponse1.Replace("\\n", "\n").Replace("\\r", "\r"); #endregion #region Appel au LLM pour mettre au format XML /* var prompt2 = $@" Voici un CV rédigé en texte. Reformate-le en XML structuré selon ce modèle : ... ... ... ... ... Si une information est absente, laisse le champ vide. Ne commente pas. CV : <<< {reponseCompleteClean1} >>> "; */ var prompt2 = PromptService.GetPrompt(PromptService.ePrompt.GenererCVService_GenerateOneCV_Format, reponseCompleteClean1); LoggerService.LogInfo($"Génération du CV - phase B (formattage en XML) : {fullnameCV}"); var (reponse,m2) = await reActRagAgent.AppelerLLMAsync(ModelsUseCases.TypeUseCase.CV_To_Xml, true, prompt2, "Génération de 1 CV - phase B"); var reponseCompleteClean = reponse.Replace("\\n", "\n").Replace("\\r", "\r"); reponseCompleteClean = reponseCompleteClean.Replace("&", " et "); reponseCompleteClean = reponseCompleteClean.Replace("", ""); #endregion #region Enregistrer le XML // Création du XML nameNewFileXML = $"{fullnameCV}_{DateTime.Now.ToString("yyyyMMdd_hhmmss")}.xml"; if (File.Exists(nameNewFileXML)) File.Delete(nameNewFileXML); System.IO.File.WriteAllText(nameNewFileXML, reponseCompleteClean); #endregion } else { LoggerService.LogInfo($"Pas de modèle IA utilisé"); #region Extraction du texte + générer un XML var cv = FilesService.ExtractText(fullnameCV); cv = Regex.Replace(cv, @"\s{2,}", " ").Trim(); var xml = GenerateCvXml(cv); nameNewFileXML = $"{fullnameCV}_{DateTime.Now.ToString("yyyyMMdd_hhmmss")}.xml"; if (File.Exists(nameNewFileXML)) File.Delete(nameNewFileXML); xml.Save(nameNewFileXML); #endregion } #region Génération du CV sur la base du XML et d'un template // Génération du Word var t1 = System.IO.Path.GetFileNameWithoutExtension(fullnameCV); var t2 = System.IO.Path.GetFileNameWithoutExtension(fullnameTemplateCV); var fullnameNewCV = System.IO.Path.Combine(fullPathOutput, $"{t2}_{t1}.docx"); var b = DocxService.Genere_Docx(fullnameTemplateCV, nameNewFileXML, fullnameNewCV, titreCV, nbrCompetencesMax, isCVAnonyme); if (File.Exists(nameNewFileXML)) File.Delete(nameNewFileXML); #endregion return b; } catch (Exception ex) { LoggerService.LogError($"Erreur dans la génération du CV (GenererCVService.GenerateOneCV) : {ex.Message}"); return false; } } #endregion #region Méthodes privées private static XElement GenerateCvXml(string text) { LoggerService.LogInfo("GenererCVService.GenerateCvXml"); // === Nom / Prénom === var nameMatch = Regex.Match(text, @"\b([A-Z][a-zéèàêâïî\-']+)\s+([A-ZÉÈÊËÀÂÄÎÏÔÖÙÛÜÇ\-']{2,})\b"); string prenom = nameMatch.Success ? nameMatch.Groups[1].Value : ""; string nom = nameMatch.Success ? nameMatch.Groups[2].Value : ""; // === Email === var emailMatch = Regex.Match(text, @"[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-z]{2,}"); string email = emailMatch.Success ? emailMatch.Value : ""; // === Téléphone === var telMatch = Regex.Match(text, @"0[1-9](?:[\s.-]?\d{2}){4}"); string telephone = telMatch.Success ? telMatch.Value : ""; // === Profil === string profil = ExtractSection(text, "PROFIL", "FORMATIONS"); // === Compétences === var competenceMatches = Regex.Matches(text, @"•\s?(.*?)(?=•|PROJECT MANAGER|EXPERIENCES CLES|PROFIL|$)"); var competences = new XElement("Competences"); foreach (Match match in competenceMatches) { string label = match.Groups[1].Value.Trim(); if (!string.IsNullOrWhiteSpace(label) && label.Contains(":")) { string domaine = label.Split(":")[0].Trim(); competences.Add(new XElement("Competence", new XAttribute("nom", domaine), new XAttribute("niveau", "Non précisé") )); } } // === Langues === var langues = new XElement("Langues"); if (text.Contains("Anglais")) langues.Add(new XElement("Langue", new XAttribute("nom", "Anglais"), new XAttribute("niveau", "Professionnel (Bulats B1)"))); if (text.Contains("Espagnol")) langues.Add(new XElement("Langue", new XAttribute("nom", "Espagnol"), new XAttribute("niveau", "Scolaire"))); // === Expériences === var experiences = new XElement("Experiences"); var expMatches = Regex.Matches(text, @"(\d{4})\s*-\s*(.+?)\s+([A-Z].*?)\s+-\s+(.+?)\s+(.*?)\s(?=\d{4}\s*-|$)", RegexOptions.Singleline); foreach (Match match in expMatches) { experiences.Add(new XElement("Experience", new XElement("Poste", match.Groups[2].Value.Trim()), new XElement("Entreprise", match.Groups[3].Value.Trim()), new XElement("Lieu", match.Groups[4].Value.Trim()), new XElement("DateDebut", match.Groups[1].Value.Trim()), new XElement("DateFin", ""), // à adapter si fin détectable new XElement("Description", match.Groups[5].Value.Trim()) )); } // === Formations === var formations = new XElement("Formations"); var formationMatches = Regex.Matches(text, @"(\d{4})\s*–\s*(\d{4})\s*---\s*(.+?)\((\d{2})\)"); foreach (Match match in formationMatches) { formations.Add(new XElement("Formation", new XElement("Diplome", "Non précisé"), new XElement("Etablissement", match.Groups[3].Value.Trim()), new XElement("DateDebut", match.Groups[1].Value.Trim()), new XElement("DateFin", match.Groups[2].Value.Trim()) )); } // Ajout manuel du Master si non capturé if (!text.Contains("IAE de Montpellier")) { formations.Add(new XElement("Formation", new XElement("Diplome", "Master 2 : Administration des Entreprises"), new XElement("Etablissement", "IAE de Montpellier"), new XElement("DateDebut", "2009"), new XElement("DateFin", "2011") )); } return new XElement("CV", new XElement("Identite", new XElement("Nom", nom), new XElement("Prenom", prenom), new XElement("Email", email), new XElement("Telephone", telephone), new XElement("Adresse", ""), new XElement("DateDeNaissance", ""), new XElement("Nationalite", "") ), new XElement("Profil", profil), competences, langues, experiences, formations, new XElement("Certifications") ); } private static string ExtractSection(string text, string startLabel, string endLabel) { LoggerService.LogInfo("GenererCVService.ExtractSection"); int start = text.IndexOf(startLabel, StringComparison.OrdinalIgnoreCase); int end = text.IndexOf(endLabel, start + startLabel.Length, StringComparison.OrdinalIgnoreCase); if (start >= 0 && end > start) { return text.Substring(start + startLabel.Length, end - start - startLabel.Length).Trim(); } return ""; } #endregion } }